From 62b3faacd5fa07743291cce11c52162d7e352a11 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:07:22 -0400 Subject: [PATCH 001/121] Refactor: update examples to reflect new 'allow_cell_edits' sort_enable is on by default, so we can remove that. renamed `edit_enable` -> allow_cell_edits --- examples/Flatfile_examples/csv_test.py | 2 +- examples/MSAccess_examples/journal_msaccess.py | 2 +- examples/MySQL_examples/journal_mysql_docker.py | 2 +- examples/PostgreSQL_examples/journal_postgres_docker.py | 2 +- examples/SQLite_examples/checkbox_behavior.py | 2 +- examples/SQLite_examples/journal_external.py | 2 +- examples/SQLite_examples/journal_internal.py | 2 +- examples/SQLite_examples/journal_with_data_manipulation.py | 2 +- examples/SQLite_examples/password_callback.py | 2 +- examples/SQLite_examples/selectors_demo.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/Flatfile_examples/csv_test.py b/examples/Flatfile_examples/csv_test.py index dd43a1e2..deb90c5c 100644 --- a/examples/Flatfile_examples/csv_test.py +++ b/examples/Flatfile_examples/csv_test.py @@ -13,7 +13,7 @@ # Create a simple layout for working with our flatfile data. # Note that you can set a specific table name to use, but here I am just using the defaul 'Flatfile' # Lets also use some sortable headers so that we can rearrange the flatfile data when saving -headings=ss.TableHeadings(sort_enable=True) +headings=ss.TableHeadings() headings.add_column('name', 'Name', width=12) headings.add_column('address', 'Address', width=25) headings.add_column('phone', 'Phone #', width=10) diff --git a/examples/MSAccess_examples/journal_msaccess.py b/examples/MSAccess_examples/journal_msaccess.py index 3b5598fe..a1bb5093 100644 --- a/examples/MSAccess_examples/journal_msaccess.py +++ b/examples/MSAccess_examples/journal_msaccess.py @@ -16,7 +16,7 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column("title", "Title", width=40) headings.add_column("entry_date", "Date", width=10) headings.add_column("mood_id", "Mood", width=20) diff --git a/examples/MySQL_examples/journal_mysql_docker.py b/examples/MySQL_examples/journal_mysql_docker.py index 9e33d309..198875bb 100644 --- a/examples/MySQL_examples/journal_mysql_docker.py +++ b/examples/MySQL_examples/journal_mysql_docker.py @@ -25,7 +25,7 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column("title", "Title", width=40) headings.add_column("entry_date", "Date", width=10) headings.add_column("mood_id", "Mood", width=20) diff --git a/examples/PostgreSQL_examples/journal_postgres_docker.py b/examples/PostgreSQL_examples/journal_postgres_docker.py index 868e8a2d..ee5847b6 100644 --- a/examples/PostgreSQL_examples/journal_postgres_docker.py +++ b/examples/PostgreSQL_examples/journal_postgres_docker.py @@ -25,7 +25,7 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column("title", "Title", width=40) headings.add_column("entry_date", "Date", width=10) headings.add_column("mood_id", "Mood", width=20) diff --git a/examples/SQLite_examples/checkbox_behavior.py b/examples/SQLite_examples/checkbox_behavior.py index 662785de..5d95e9b3 100644 --- a/examples/SQLite_examples/checkbox_behavior.py +++ b/examples/SQLite_examples/checkbox_behavior.py @@ -32,7 +32,7 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Create a table heading object -headings = ss.TableHeadings(sort_enable=True, edit_enable=True) +headings = ss.TableHeadings(allow_cell_edits=True) # Add columns to the table heading headings.add_column('id', 'id', width=5) diff --git a/examples/SQLite_examples/journal_external.py b/examples/SQLite_examples/journal_external.py index 03f44abb..5f60125d 100644 --- a/examples/SQLite_examples/journal_external.py +++ b/examples/SQLite_examples/journal_external.py @@ -12,7 +12,7 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column("title", "Title", width=40) headings.add_column("entry_date", "Date", width=10) headings.add_column("mood_id", "Mood", width=20) diff --git a/examples/SQLite_examples/journal_internal.py b/examples/SQLite_examples/journal_internal.py index 205d5f93..3a210c39 100644 --- a/examples/SQLite_examples/journal_internal.py +++ b/examples/SQLite_examples/journal_internal.py @@ -51,7 +51,7 @@ # Define the columns for the table selector using the TableHeading convenience class. headings = ss.TableHeadings( sort_enable=True, # Click a header to sort - edit_enable=True # Double-click a cell to make edits + allow_cell_edits=True # Double-click a cell to make edits ) headings.add_column('title', 'Title', width=40) headings.add_column('entry_date', 'Date', width=10) diff --git a/examples/SQLite_examples/journal_with_data_manipulation.py b/examples/SQLite_examples/journal_with_data_manipulation.py index b757b602..21c5546c 100644 --- a/examples/SQLite_examples/journal_with_data_manipulation.py +++ b/examples/SQLite_examples/journal_with_data_manipulation.py @@ -37,7 +37,7 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column('title', 'Title', width=40) headings.add_column('entry_date', 'Date', width=10) headings.add_column('mood_id', 'Mood', width=20) diff --git a/examples/SQLite_examples/password_callback.py b/examples/SQLite_examples/password_callback.py index 138f27b0..f3c40f90 100644 --- a/examples/SQLite_examples/password_callback.py +++ b/examples/SQLite_examples/password_callback.py @@ -50,7 +50,7 @@ def disable(db, win): # Set our callbacks # See documentation for a full list of callbacks supported -frm.set_callback('edit_enable', enable) +frm.set_callback('allow_cell_edits', enable) frm.set_callback('edit_disable', disable) while True: diff --git a/examples/SQLite_examples/selectors_demo.py b/examples/SQLite_examples/selectors_demo.py index 3db067fc..78a4bb38 100644 --- a/examples/SQLite_examples/selectors_demo.py +++ b/examples/SQLite_examples/selectors_demo.py @@ -35,7 +35,7 @@ """ # PySimpleGUI™ layout code -headings = ss.TableHeadings(sort_enable=True) +headings = ss.TableHeadings() headings.add_column('name', 'Name', width=10) headings.add_column('example', 'Example', width=40) headings.add_column('primary_color', 'Primary Color?', width=15) From 7e5763655451db08895ff3d4cc252e86aa73869d Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:08:04 -0400 Subject: [PATCH 002/121] Ruff: ignore `value == ""` rule --- ruff.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.toml b/ruff.toml index afbab857..dff3c660 100644 --- a/ruff.toml +++ b/ruff.toml @@ -53,6 +53,7 @@ select = [ ignore = [ "D211", #No blank lines allowed before class docstring "D212", #Multi-line docstring summary should start at the first line + "PLC1901", #We compare to "" alot, and for good reason. # Will do below later ] From c0d116c8328d4a9fca260a2e4d655f1a95ff9925 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:08:32 -0400 Subject: [PATCH 003/121] Create jackcess-4.0.5.jar As an option, if someone has a newer msaccess file that has 'attachment' column --- .../lib/jackcess-4.0.5.jar | Bin 0 -> 1316903 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pysimplesql/lib/UCanAccess-5.0.1.bin/lib/jackcess-4.0.5.jar diff --git a/pysimplesql/lib/UCanAccess-5.0.1.bin/lib/jackcess-4.0.5.jar b/pysimplesql/lib/UCanAccess-5.0.1.bin/lib/jackcess-4.0.5.jar new file mode 100644 index 0000000000000000000000000000000000000000..82c784093a561f0eff89557a66e42886b388f869 GIT binary patch literal 1316903 zcmbTd19WA});64^la4yr{I+qUgw$F|dP$F^e#kzTVMK~d+t5o{oen+XZ&-G zy+`f6s^+Sic&g^CS~B9FK%syjARvJ33fPn^ir*Q%L4bg`KLY_F0RaI?3M%kYib@Gl zOY%yI3JJ<9P)Q1x{sqreul~KDTO4veLVzbw! zfxLS%i>8!=DK!U*M0t!gX_GL^6F-84-k$MbmK$V%&N!uAYxIG%R_Nf5(yMEQio~0J zQ$7ho=-N*{gi$4xWT;FW_M@m3gN&h7HL(o&bj&6Ybr>3&4H1e*TVp=<1?`9}XQen+ z6~{#$c^~a4Id6g_O6(9Zv2r-}5WJFj>l;DlJ%uYN!_f#a2Z%{zRV~wT!*a=NaCNAl zc%~SMg{)@E-OiPsJ13?a-ffZQax(XFuAP5jPmir`+qGoI3PBD_WEHb+pWc&u7hYdq znj`zFRsm+ZgOr;?=AoVM@moN}s^aAMvZ$g@7ly)T+awHAg>r(?-1#ngj z>`xuNx^saTI`cQWX@`jZZ112-Oph|u!q~%t+YVUR6fe0pD#X-8*_g5gOgmnMn<|1u z0c`4=%Y9y0zz%l~F84Zjpk(%3ilU1T_lL7B*-K{1=ktbb{$h>C8XH&j@ChLn3Z94t zL+L!xP|}tH>DH=c9SbeNxWVuI5lU#R}1P;=sM;_GB24fBZu%0cVf+uvHy$9DiIW`a5u{NyC*zOhl-*2Q37pPZl$y92iAAN}+s)N4yk^B9++V(A68n zONe8i(1qUwf+i&KqefzD1^e3bmV+&WXY?$@=Ruos$U!98;av@@)?HL414X*MJo&!r>YEG zhilJNtChs5n`SKzUE!ei?iPkc(7i9@Jjolr!9PB=tCovdzZisdl z>JQ65F4qWpAD0A*AYanMubbn$B(e-iIHdi&+qDp;n)z-vMVVv71tc*rLX%3g!_<4ZXFGS7*#;OMMNn)r}!w zQ4E%_ahTLDY~=5y?z$V;t>UB0>SKq^npz1|`V|-=Th-o*?xLgWyQ7**D~tzGncHv| zcsXMjXx)|G2w$LY98nZ?UUc%ZvZ{s@19ukZA-`ESd7!=S6HK3SALfdNp&c;&;-GI! zyC$_}pj`!s5bWL6+s}cGIlNM9%kGZgxYIOO^hs;wg4@G`^k+)I+H3+x6Hp-R$AeUD zb1>zW+VHb?%_5jcJPA`CXzs}*uzw(HL5dj@_(bbXQMEYHOuctL{N zc4NDMR0|^x79!eRHo-UEg>dovNr@nqhQ209$awXtU&zhtwh%lm4zU3jd{6Rpk$`LU z!+gir?k&yP3CE;c^P-JPjtQgnhki1l5nO?%Uwy*by2VX5FH2VrCJU+$bT}8UJpk3U z*qa%HX3J`-`x4_I?0&M{dtq(jz1APcA|PB6q2kh`6@%r<3*goz-h-?GlbUc4+W{?| zS^8@m4}dC+o3%c_Cdb##1pnqgGXeV)@_h*Qb8y5rocg6jieoE6uY!Yd_1>27Q0f}z zW!wZD({aM^X&`I@7fQrexZiI7SLgn>+y91V{sV-yxes`j|8V`^sQ@t zRQf+aIZaMdewqi(b1k0%BUlF-iJR;SuQr@DlLU*pklw*(Oy$Hw)oIG#podWX7T0AD zpB{(9^+LGlJRqD#jtb}MtY!S<+1>nZzrt!An4_x)$jOG92l@V!gzlp7azB)Axl9wv zAkVJ<*vRu5G)Noq$cbToW%q)&h>&S88P9T>3EycF(-Id6g@sVyRt|&%%H3M~3&m^| zz$dK?IIB6uL&k4W1oPG~7N^i%8;dZMAzPw4UPo{(qN^DD$mfC~S``B}PUM6J`7x^6 ze`ZJo83)RRP*K+JT&V9zK{HmrL86Lg5~|=pJ$?0Se_m_^IF}M+TGh2cBFo9LMvC+P z5QUc7ud#-e zC&V&1^ek_tM)(Rd7(Js7lqgLP~GOLIt>BuOrG<3 zSH5<1;G4gYaU7y(<5S>|!VRDMAyyCA+%<}4MR427V1YYVk9!2$m20?wauTxpVqFi~ zhu2-BZJBP_LHD59jE~Qf_AT_dAUA6%wkPeK#CFUuT|HRBe2EApLbq7z+mNL-c&0m0@?L zzt=zh0SB$WVnE>#IV3JbGFK}c&@jC%Ex#7_zFKjhQ# z^w^e{IGc-C0O;8UNfLY99_zRp?1CHuk}*$S>*2Hx&81n#_tJpXMQ|zRy`E7wXXiTh z2P8%JrAZVWwQ2wnAvjY;)i4#I9Yz|WJJtM8XRVpkTC;sb&J>kf0S*%YEp!|gZsXNb z^(KNGsZaN5bIQVlZof;&`#ofhvAPA^P3)*yQu z)xdnKeA!2W3l~tzo?AoPU0s*lzPVYt079a%L24O>xrw}@kx+IUs z*Xp4=R#IUM#OW_l*?wT_r|iQ_ZDLhiN!~goTkBdaY)0+4xACV1jB<38IqswWHi5q? z`rjt-dx|)1Rm-FO0t7Tb3IxRTcf4Fc*Irjo*ABqvU}A0nu>HdrdXpfXF?*5w-?z;u zT|9chyJ&al1fsA;5Fp?O0Q)p7!ZsozdNgJ(7T=?Y1l0l?YyEAJjE$lF`KD1^Vj z!WUr4kc=UP)k$wG4>b=~`K(CuJggk^;*`EqeBUhTkgj2`q3^ig8y#`8nwM^S>*NPQ z3049NY)uU5Z*uMqapWl4AcIqE@9Uj!dc<_UXFwgdzLca&-`!%RYVXx-A9Y&aY&o~R z}ye+S(e2(z@ynOlW$5Zg^4bT1R@(?BW)q4t(721$mhcQF;kspIFhl z)Wd^Q{i%%nvK%6)q3Sn}^wJ+fe`(9AwSVkY1TW)vp;tt@mA&v?OrQvV2z1CT@cCKE zzq^^oW8TpsVGadSaQSm#mW8k37fO_in?8p!FAqWv@^AIFnwYfNt2t5MB4A-nF8wSa z7#4HN>gye^$;jCz)# z=r7v`ekMq!MVv>LmGWrH!C@X)Qm21T2PpNZ8B~@n{|vJKaDxb13w+CX&p;=ra?7KjZM)3W6F2uol-~|%d8Y#!pRTVk3N*(Yxijt z+G9M^!zo$3_%Q^=wpx!hu*Y& zx1Aq`S#;@cyNCS*)U@sS%nQa@X(pRNfP!uForPc1WaJBVSQ4*yfVy{9_3}3w2d3lf znfpEePG+ZiVJ4OFeBWhORRJb`CJRCgGz686lx`*i=_*?%nF#bOW5O&5xJw@-rY~D* zG(^2Y3Nz~o_gwMaWRj>V8qP?>NyBi=2Z;Jc-rVQxU0m^){aii@9Zu*>19Yji-A-e{2?(t1ZnaZf=47GCh$74{B-hS!1|Y_1diOuW_{wpreF~WVRaYfzFcP4?m_Ly&mWju zc;QO>M>*z67Q!IvIH3XavL_@h^G!g9b$&bP1%Zr*D*=48-s#Lz{8!3E*k)!s=s$%6 z%i4<3WRK65I9@_dlKY#JU!jy}tRzWc(PXdN@`;9dTbwn(0!d@-g@w0xSD#8ZSmsGC zNQX44!AlMVMS2OGW6!Bj5;GdcWvV`xDBA1f1=9p5mTsAnjLF(iH7k_%vLPQ&mgy0U zDcgXRD^@|KD7J#1D^>-TaB8%rpHuxZWSgy5SI;2|Agw0rE(wOpBF~mXR?JrG6;vtv ziGhp@Uq^lyY*JkK^|sg;J}j6;40*bDve>x0w>YtDbTm6X^;u!{TZ-NffejR7tDa?1 zSW5)sq0?29A1p%1{lQ7(Tw&~Bh70tn*z@2yu?~Xf3EBYe)5y*CA+C?K_Q1Do!y>)< zM1Hc~SPg=RRPn=}nHSgy(?MZ{=C7HQM$gEKSb>m=Sk!y68)Q4~l~7z%d*8dIpJhQw zrP#l=yDv};g0%Ib#(s`A6US*EyDgHS#7%ZDljL`lOH*1LVK;xNdk;+bHt(L$Eg(cJ z8T7fW9wPN@I6jIVX577tot2w_C}FsEg1#JmcTQ9*nLfkFoz>=fMs`9u$lW%!6fz1u zovZYPX&P2|?hBYyp{|URryhh2inSisq2V68c+v|GkAA^3-PEcvi-g7xCA<2FjXFIIn=^O zT3hUaZqhlomHZ%-tKZ1v6W(Au@pyeOHSGYdl_xaQa(vEpZ=Up@2}ddbhpbzokEMA8 zc&Q26jt!!opzKp*=(4jn9Ap;gbP@1|`czB6g``^}{%64~R#Eeeb8&KLE_oC}T4YgC z%0qyixG8j(JcG|5r~}UyH^(+J)b=yf(JwADLP;ob7_%s{Etw_#)HnfX!jqiUZW%A@ z@(6L5vQk?*<_8S1GEHGaM4$(}$da=G$^{#l3x zPca9gMfv_}rddcu>0TN7Q?#*qMi>&JU9quR2m&=a^mj3~QVZ&kjwY}wp>uJ%?5nx} z)k>$Y=F+e#D?UUzjgr$TU@bv9%=dIr%CAx^M^=_*r$%bGQ2{zgy>l zsS9GZC6@2L{V>Mnue0mt!V|3-;Ube`E$^Paa$#NbH``$?aQYUq$d7S>SA6+=E-abp z&;@FGFF#szSYzLq9^>Az$lY5ZdTC~jZBc~p(jOgM%5TLowib=JQc?sniZ%@`%Xzhd`5C9%Qh) zM>>JA2rBF2O2Dm4JAp?i_>O@`5I54y9Fr^@itG-jbR3iP9ZakpAx~oVc7swym%;Ri zlfj74^`i!tsf?|0iQ2Ug?n*tKmBiGs)9k~w%Jpd&5cXtB5hT%`1*mxM~A zxYjv>4fP_&-dMPz!~hbGtGo{?(~p|ov=1ck3s{wxime%bun}lHWoQy#n`tn4=L>d*m~PL1&RvCk*s7PnmPs|~G{gt8HQr9FF+BS&SxS?}xO5d4q&J#?vVY}Q!ahLNF@ewBJUqiVX4 zw89pR*e2NR#7dwNN*>Bj`DucnY$Qezxp9VZGplri@rXZUevg&pka3U{!ALQ=7 zBn+Wb*%jaF^nJmV7`?zl_es3n)hOMSn$w4!>mfsFd!D#O;4rOXGcX2bec<1j>95?9 z3J$(Ap|g?Bmn@%J3Uv=gLQqgD>Ie7v;AmvljSd( zF9L2}A<#($8sv+g)U9IP-=jGwVL(OTcSwDZKB?`EM8-Z;uQc3&)mvG6h8$Azcmsn2 zBidK`0;hMnvvH2+3Hht!*LKqB0 zc*J%7oL*(uM}P5!+u`Y@?08-i zHFO0NwSxEnnB*vW42IUx;sBRNOttOqFZ#>t@m-{b59po&8EIy!CvvVC(0&!$$IUC* zWv37yD@_CcLYj=cUXFn!N4{7JF%+VZt%#wp-&cQQmr=aikS~+3;sunjr6nu>eV9JA z>F>)+A?2PW!TXVp?(JFNEhZ?O$OY_oMEO@0{abmz^F{~i6sX`I358g2ARx-Wqda~q za|eq*cYhR@c1eOdBN8vAzY8U=$0EkB5I)J zLSY+%&M1Lqp%LJ|@L&)YV@NDl}WBZzjcoz(~PF+;A7` zwa0Orcub(#7?v(1;~JJV*21K=E6HTRb3sYRXA!+-tum6ogviV;0lWU-;J&s-uF zwm!8Tg%SS=<5$LfFsYtHn#dRrx+S{M%FGzX05BL zeOuPF(ijUZvWN?roE=*6aO=rZP>>>cNU0(GTJ418amTOdYi8nvptal-vRYbvb`!nT zR9W+(Xw>giS#9DNq_qBs0Mj7hsvdJoHH-cfkZY}9sRlDaj<6_8`ieecP^E5B#m8Y4_PS4l&d{67qV$V3D5t8>{gPl*J{YOi1=B|B zdB8ybVNyl;mW-l~H})_=?*|M|K!b0C)JGWJLm4ECN}?mTWzk4r12qq|B8ZH;#UNz3 zGU8^fz4Zwdx0@g+LeqU~kJG_suGi=SD^5$KfL5a)Z?dijYh-vNq4JvzfGb}frSmhe2Ea|Q|d4BfQ$%&U&MtD$`+&mxOYqdiQi zea#?}8lAxuUGQq1d66WEV+e(wU`LA5gm_?rG8{A45ED}Ha+%Iot_LM7WXY4E;Lz9H zXm`NB@#C*!`1k03;|Epi#&p;R_&cp8 zw8=jkCGb}mv@{UEO!^MBS+Gygj3g%UdQeIe;m$Kw6&JrNc-$&Ky>8#a z@PL__5LOv?M?(&yC{dImR(oI7Ssx9-)$Xq;NkiK6CbC?vd_%HSL6uS_LI);4kAo~C zI6b;ZjQl>Qwk5j|ZjE{8aA4M8)2K;8DAnpFI51ONLe-MSzPtXDJuvPtnZzY(!{G(z zXHaF)C%!!5w(wPjZpaku%?3p@kur^6)$f`e*}ez8Xp<5@5Z& z>vc;Q@VYtP9*Os|A<}RQs0cuz>?3&g(gd^2x5jUSe(x{sWhoG2=bR9qp&z8hE;1V- z{nn${zV1(vKhj~x7=~_$Hg`g!bV6e0tD53fX+%AF2laQ2XD?L(5q?i5mQodJL0d zhwA1*O1+IqTu^cTEZ$HRvH;-&4|DaahLEyW-FLPjrI>{qvUk~f**-eWU2 zkU32QZO!I_HCQT5n7FB04~TchWL$1OU3>wl{;V_ZU^_XL0qIoht)k+9pD42^FY$i9 z5?MkpK!{vfW1gUsrzsLXAQT}`Uza5(9`%hy}>JI_%fC2qJ>+!$t(*Z23?Oo_-XlbZ)_4N(( zPSYTrvHBhMwQm^=f3UjpL~a2`Z~X`qm?UIC1NlZ4#P9={NA6pfe5?U&wsn+G=yxEI zC=|hNHpGx;BBmG?+EW($6M?l-<$8CIrH6$Fxf9a0y030!zgSP=kVvF*e_@hnBQ9^A zlpGZ&oh5bgBI5HLydNJP&MV$(wUu*~v@|D!PI^yGipFdqIx4T!}7VLA)d%BI6q_n@U zuD=WtXsbN?MwD~jcf$2V%$z&ly5V|lW<4HmCP!GgliMA&tFPO;t_HSyJ@Fj|-@ll5 zDdVzTqH?>x97H+2KQm{jz3xb*`oK+(E zeeUcxb>3|JWCzuC?t$gA@AqFrp?ETX5pWk`7XIV*$G5~l1f8-E(Hk3n-FJ&@H(!D8 zb&OF)ubmvh6CBk)XGBsJKvxUn(hY>d#nb^vI#naYvQd(m_+oJ|UIIm-m5S|jdhJkO z?BtMTqfuqM5b8rA0Fq4zapnXR1l<%5*Nud+0pe2pdl*p&v`q2malxcU`ho;otv>|` zupFtm`CI@1`f(RUVgjEv48jPLuEsW~>rrGyPYza~cCznNtV5kHDt; z7oO=gCL{$7Ca7r}E4yCMX$Ggd;nd34EvgkM;Wc?UT4Lzc3Hsoy8B?WkG^bgCaq~T; zbqV|csZZv@YT6>ttZ}j62c%|aa>LG{DEt1nnc#5|Ip&Yw(zdW%4=y7q>~*6C3r?zFILY-ge3Te8(!>S=6LZEajXMT$2z>Lf`H#zs*(p*b9cz+|fx-?HH>H(STK6i)Hi;J7%Pp5}JRK)iHI0~P zzA@S|+A}MAsVNej@hSqJ%pxnP>Qd|n>ymKhmef@1A?=v1NtKryemA)PL0q@$F+vz*r{SWpN zCforlWtuMf_v*{^eq|Yg`F-J84}U4Pa$Kk3z#0A{?_&tKwj*y_lRF!yH{TQMI~(&b zVJYM$qi?tdaMucJ{S!{4BhyCrE?3f{^gDqM>)``|wIfGHp2=>ZsYK_rS3^E4Pzj+S zDHfmcop+%B*>yV%@t3M%X-kMu?e_%v^7jO559PVdT@hL-#1u7@?Rw~m)8*@Bu!Eo7 zb4Sfj9|MTTz3bqNL`5LKqT*R-yWi*_yW|oVZrs5?$rA70RI^8&e|~Pb$7Ojr5Bpw#;F@Bpb`Ezr*RNa>`O4v>F#86OKcjj(zdH=e7&0?cWm3#A4L)DX zK+6JG#Tcq{Uq~3NHKOA`a`cg|*Q-Wnp`U3#r>*-X*FSc6qIl>!^Dd%O3hHBt+YTZxRLu~vGJQ4%MrG;yM@YSUf*YbX*+P8 z*s~3%afiH`mU1egJd53!-_##;2&IR$GTO=;;G6x?XaxJ}`ByVyUw)TR_?4=@!Chn` zqYzLcyxCsZLUxfoKJ=A$apq^+2v1kjl#HyETniaKUsxvjO;H6J%0sl-6?eGWJyXUniy0OVN7fVgVk_M>^}L{&-Z^cbsA z25edd|GkZIp55ePlw=1_f9iH5_K;ri^p~pMTZ**FA`0nZ`k^Z@Y&+j@*73D;c?@Dh zU8cPr4)SMm_b6B=k8zi5F`ml(ti6S3l{|TLbp-W3{%*1m%81p%J^)s-OrAWlIyrcK z=Ppvo7K1qJD`I$AFeMyg%9Lz;g6Z&cE%jP8=XlZ1RUx8MQ3A54g*(My2z1REmzH4@ zl5|NEwnp=_!DG=)-q-w&PFGHd{BP<+3R# zY1v~R_6qx zqk+&8-J_{??HP2cpeCdLT;C_gUDtY$v=Xz;StHm%q7YVqN(F#3eVNaaN>8+5G4QjM=*&HHAuaN~#mjOK zg+s@QB{;EsLCwxMF=3(xoN@kw#XjBLh>HArx<$o0Rh1gEV=5|H#V-2A)S~6o)s{R2i4jmRcGIoWWLeZ%CQj4~F-sUnpm)*oX)r4q zFYEZ3t{P^wtw3Cd%4Dh2){ZlGgAlA_E;H7yn)>A;nA(d6*0YCA82V{8pYp5MAgF~- z6sz;sGGb&6LYS;_Wm}{mteN|LUe(J!btG8P^($+J5nMJTFsJRdXjT+dPeV8gs}yah zF53L-7u%hdBUA4}!Xh2vBs}zPl z&0V&yg(grDfW@v$^I zhB|ux^H=8z_^sEUKXlFresLReH#J0aVV{Q!Z)Mnqv+Rx;>01T83f}}BTphgsG>>p6 zdAZ)yd1x4p2CS2FDmT~N2xr0hpFw)CInz9BN9Pa`5j~$qfRkExnRebkugQ$WXp6+0 z@BRXNRkK(WA8&iG?a7JLWrT^5Y&ir<%SKp|=6vB6={HGij!kVphb70d%mEc7C4ecY z3CMUVOK`${yc>pj$Jdgbrs+rPIi2ApF{XZpt6b!q5rw;~4G}USVJtB}%k9<`rb@9` zG}n5*e5(2+-LayRzim{LmcU0f43E82#4MkrTeeS>v-&KrAWE0%v&13^cYLCkMpZu} z46T@)`2-gOF)TfRovSV1!@FL&?$GFsy=JQjD;?-7U6{4V%&?19I?VRaU7*=NVowg6 z&uS3MAAb2lb9-d5YpVI?z=(AP18zs9Y^0e_&2j5^cvLKYHgjxCV6*8HXL-!BG0$SYw6Y*o&4&F zv#q@2O(L_s_ncOcoufm;oG<~HIZG$x#^HuV=9&FqdIsIk;Le=)*Arbysavb|KWfl- zzHkko|11K)j~H5C#B{8`zf-l(R5G`3$+E@`t=-m!)Uf6;l}(l`go;=RX+z0=B9VIm zX$uQncJDDf%}ylgKqIdS1}}6*Pt4K7ZBdl;?|E8zswSbG&2x;!^LP-I+muP8M@gJm z@KpR_+mjwmYsRPFvM9rx;GVG-L_$kU%rIBndZZjMC%;)qW%QGn!NBqBxl*z+zWCIsg)C4`grk=6m&bV!& z?(We>ccSq|y^4V9n217KVqA8VDV#lBj%cvW;77jZ z2ea+HjNA#v$(ROHNI7{*i?gUu<3{+QyyDmXf$u-vf?*dWz7sD*nWReWYGkl^3{r%o zU8mTg#~RQt+>vO@2IA7+8g8V2+QBPQE{j2+s?~DHnzKJAL`A-frCaEKyECB9OFmnb z@aJzkf1v@(3DZEL9MTAe3d}wKLJMa1CVn95VKB6NxITpOx=^!!JF75eOphtK?nEX6 zv-?!s%_)k?InLA=g5xxf+3TKWEGQ`#S4GTVE+z62Lmb2@i2L&oITfdNZtwz1rBjkG zwz?5VX;tYm^ia=XnmH*FC&k?$!Q%?U_`?}8Vn&>DJ`P_DZo2sr7k&goSp?LO>Ou?T zX`w2)_}N%`E$N96X%RsXJA$#v5Z|eNd`-Nq65cHj zG}}ieX!OsrQY-J!jq zm-01XfiA7uxyj$5G5knil}=+D_bU#5hxIfqL<8 zu>bsLZ-FYnm7iWZqW7J@AbGu6Z9a1Ud50#X^ZGB~ZI?Ixo^Xj3;dr_K&XoRV2Z4ZU zApSis0PS&oFnbqD-=_V!|NbYV&J*zQ_dk2_qiH;j>s|T+DB{DQ~U4VUBdgFKc2Vi{Pz3pmw(U*{>5L~JZtn_du@8WbtK1JsH9I9ZNQ6*dIt{u zwaWj?me~dor|0edef0WW9ON%uTm1BwaoORDzXT8zVC%K+zf+y^Ru6vdh>y8Ii(&(g zetj$X=o9f;JOWW^4)>Rju-!$g4$V3R(V3AdNDf_h-$VpEPd7?26JgZMJ#w@)RjnxD zsW33P$XCdvbReMi$_pGj2SK{n5%zH31i-ZOO&Tx&oVO~?pFRVAh%|m583WT4ayLyy z1)3tBYfwh6^agkFGyh)_Fa+Z}u@1#j7=Jh0s$j!)WJ?RG{Zl$X)||RQ6+*kU#59A~ z?{ZLbMXnRn!fO4ZbCQ(M%Vr676mjov_e+-B-sbW^LuMS29$V7+WIrr9;7O;Dc$je@ z9vIB0oilSYy?s9Z8)0I$CXiBRyM{k*1hv?`fj5#8u0Dk{%=hmyT^c=HQzEuwi$NcY@on8(`(+X!t=MvXt+bS ze*L~XmeK3U$Q1Sab+_O2!vvoeTWE+xnvOMZS{HPL%RI{3GRfrki&}rsMYGS-zg^*; z_x0a)`mC}Q>DvG98^1ibgdfjNHvgWHw|fUah`0Q{UWVmf@~kD2By|)w+M4{tHNEe+ znV&6w{q#O8v~&AzG;`WI4r9nTaJol*f7xm7H?fXKF*seh%HcEQNR=H(fO0Ul<4P*o z^Kw4aG|^)Gv$@W^Y8HFL`$2P|LDG5 z!mxAT%E+!NKbDy&_Sm>WfGT#GH6w*e-_M@-x`3#(MwWVMxs9!U25(&2AkMc`z682w z7QMiZOLvQ^#*WKEo$B4k2Qg}U%ST4u2e^8nrL*k7`cI?FhsG1Q6Zgt$eS0^YqW)7z z^gSnsf)nYm1amv0V|*6!ubQ; zkX!aDZ(qy3Y4a2InL<6n%5az4fs|<$1Gd6E;40Xdvn+46HZl+j%1-1bPQHi0teC~7 z(W;5C_fyYI24j?h&9u+)NN8$&6^y=dL6J8~_Bv_*yNb6g`kXE$WT z1fFTOQ2LVxO~4VE;H*mkEPR>#=X94}X=~$ebhS^_L$ENOI8;IEw7)?xBU=8FnpGn zy}VnF$;Jz|mtTq3x*^ZM=LAGD(W-7Tla2S$Ht$fL-DpXn-BK9Wn`|K|iLo0k7Au92 z_$nr(yFPmtXj{_AFT&1|iuv~KXx*9^k)=?a%ajmXVehA?4?s@ar_Kt^q6RkDkquQa z!?}DxURAk*S?z>$s`f@<8%)VHmtESWl{M%mW2pM^mFmWVP@{;PSzEwTY19kwec75T^g3}Cb zW1u4V;x9nSF!5T8YF!F*6PNa*geYfm{hFuJxdMzMwfu)d)Z?~aac1j|oH@gfUOCC? zGKPJ9T2PnO$_9A33hwsg8O27&ikrTl;?-q@7W4mLz;~*3*hV*b+ziy? z6_>824Z7{HWIDd;dc3f7Uz=#4^C{&?~HM{tN~lXv1`2vDIOhpeVoZNpf^B8HTd%a3)}BPr;R6zquXI zvp1)pUtC}AXs0QEn)6+M^Dg`?z3jW&Q=pLa{uq!zD zl-}BXcl9H6Q?sLnTvLpO+L?K#{8oYjleOhq z2LO4RsfpCQMR~>Ql$!Q_I93B^o5ODYO#aTELNQkxQJrWw<}oWiO1oA6#(;75?vC*| zkJGrd-hSs~S4N1x)l!sw(c;rU-zcX1=xyB~%;6m6APghp;T%kgx+$}z2r*{NAtfW@ zpn>LgP6Mu~X%lBjoI?CL4wmQffQ+y*9OjE_KlN~ql2CgZLBFzj6s1I$atO++U&LnM zqudb!*YGf2uRf(Vdfn7#?C2ZOLm!%RnPF6&h$-@PC3zU>hKt0#?~^@GYz{4KY8jgv zuyzkPTtMf0?~brVn~|&Tit$Q^um|w>GrIXHwKLsvjM{uBTe`@BH&?pPEVz}=4>MD_ zT*t*pAxIhNC^}_ht@csIWN^cLbtA$_D z5(LA8lz~EVbfTE73`KpC#aQ)~8KUXLoV(w~^ZI8X^R*urN-BsE-I=%t6L@azL&!t4 z9P;P~XnpNt+oY_Uuy(VQ(~4oA_e>WOdXDVp1|D-+=Q%Z)BvZ|3+1I8u`3}XX^IpeJ zRJppI+BmpNsg$H1hmPz785F`-n^QQ^HlXu)%k9E0THUJKQ(jwB9Ci?g zi+_YB5Ru*rs~#D6G~G~|ibfm{@maRHsgk^i>&55i>xi1h)-IwN&be}0g^-O^M0wDO z%h?H}5Mvwyq)h-eNknwpe}$>}V5P@jt8`s~;CL;1;L5h`le}}s zXww!|9wy@)dR5*hZh+UMb|Zjzsm#LJNB{*JF@vb)MKOeMxQY zVGaeDi9xMfWyQWSt`4@T`P%df!xbbMj_K0k%2Xm5iMVAxd*Om1My#@6l7D>fdq^43 zyRw=iljdf=g<~aYlA8hkO%QFC+039r{B1cMh8p||LS{e3k!NrHViW+ft`(p6rt348 zlI(<9SYB5!UC9}ZC#*XCW#{F(tJ}eoC#%6-*%4gE8Ioh`34Xt&m5obZHeBBP=tSx!rIL;J@H$6 zP%?G6{~9OpiQ(jwg8Y<5%t|He#0W`;L3KOp>=ar#@+hPCUR#f^d*<1JbnV^UWfgPP z-Ok>VI;@vBb;UC{I0(RMT|9^LHITg-9IoOF7rjP~G2(Q2wEx#;S5#L{j(g$>#wq8d z9b85Nn-Dvq8{4Db3?o~#h~jmUv9i^^b^k@tgbHR*e?;SPLVyE4wf+QcLq=hcr2!S9l1SqJtXde|jew#gwh4@)B%0;xKPz`&pgUUk{r541-;}7o zuvL+>J^PN?=xG#_=ni5?S^Re10=dxv{LS6LbF<_DTKiP{!4eo^-2-T|1^t`56R2Os z8MQkzkl#7gkKGR8%@%Hii&f%Ej|K|!bq^4Bw;CRzt5rqP%T=|+{T5x?ai^MWd^fml z2NkkE9tlMAX(5}N?juv|ahk^~$_Ke?nRxakdj&=r7$z2){v5LrMxqu5Pd-#q9v5Dx zxUtThdl)V1D@2CX~%PF}`%ihtT z%)gp;KMn5s;C4te+OFIx%dn24APxE~MMv#C6pB;dUD4*L9jsr$CbZU?cR&B!v2vFt zFrQ`a*8w~382Ii~G5r1Kz8Ma?Q&G6?G1t7Zb5~MIWntC3N7cOPM%uIJnIbv6gvmA& z+j^0aWIfukbCzd?(ssKoRg`;_VbwbY|I6Bm{Pu!wocvTfa)EW_AWAvAr}kjX3{6-= z3Zz&o$0T*i)Gk;i&HPyF91xVil6iv<`Rq#q*cll;kU?Hz8L1;`O>1t11}z#Qi6Qen^6+ z6oX(NjKSD(edT40le8|8#?T zS+LzN`>(_;P`+>EQ$pEt+d636b+ztun8IKWAC$ZdV>sO8D;`^6yXB!Lx{#i1c@(=# z>`uWeR-V1nKx<|OHXydg(JmftG5c3h&Xx+3)?CNhq~^N?-Ru8h>@C3JShlrc0wDy4 zpg}_t+#yJCC%C)2ySqamxVyW%!=S<4-GjRi%*;==oU_lp=X?IkdY`J*Z+A^scXf4D zuc}%=Y?#&{R<&ePzkBMx{}__0`({deD?o`)^KS23cG%A$CN`X_Kf9Y+#H56$i^VN< zbf6xqb|y+Yp(2O4xNIOB-&W>a?4xGT3g%R7JlH*Dy+ll2O5UFVaC%!S}OFT_Q%Dd$@sO_0llkFQgsz4+l%pckUy zPOTQ#?ky)ngFQdsU)keC-{y>ru>yf1W#dA4f9CncY<(-J&k-Ik6B1))X5iB=64*Sq zl)J|SRDM2uD0!%S2z%h=oZu|V;BLnFJ4rS!$sB=3!(5>zqW)K%5*;a*o0WYMclLX{ z_VP}5ouK>13~5=Z`Dn`;Eg8zvLMI|Y6D>jP7MuCBR$JR#P1=kP`4~x(iOqrb5!2*S zmd8T*d+-=9S4&PyIrFf-!s^r0w+@CEzU*s!5V1tW8u)YEr-k)?vsDmq=i{M_CQ==a z#^7%i`0mqdCzDrAA>qD6B!A^@6ZrZT(h_k7p|1iHGu-;D%t9n2LA<6%5E2ClJ{UFHK%trEBoi=&!xjgsH-u<&nO9zlUA1unP1&p`=^!G0oYY`|w_>OiSZZ%_1Qw0Is zv)ec|e!Wgt(Vyri`|{VoE7ay~&*{#XBG544RM}obx6P|v$rb2|e?uM_cVZDm!IaQ< zC1%SKwN>&tacQL0a7ppq$jRQ1{qEn}3o%VQ9q=y%_2A0o`s>iWr%@$$4m16)O`0+< zg#m;@k|eD;gqchg*Pk)VP8QeaZ&|?U|OlN6UyI{qt4wQZ3MnArn@fw>~4Y5ST)+O&ZW=@TE+oK!DL% zNN*vSnDG`;Tms>I(b#Sw_Wvp!U1j}HP%ZykZYgVCJGKMc8Bg7nEYH0^?D3OClU^5Q zsb9seUxS{5yg<)~Ls(6%XN<4k|j;X(GZ%`^B|HT5B+!j8TjZTso zs)C=w0~tTG{X5H0P&41YXF{%kNtqs(d%^prd8s^IIWJ$Z!{)1)YqJu)rT79%lRRR_ zX0#QG9Zq?Cyn$`a1>5hkTOdaWmC&Krt@g}2E#Z@BQNdX(u&cRzBl=fxGq1H94WfL{_^@I^wZ zK6FQ&UFp-roPDzff@|I1&)Yi1+e^ZM!A5h44u;QM%C~SQS#1TB0 zAiLeC7zoy*9NcpFrox|3@oNW}@;@H?ZL+N^jUZ4N~saSn0d z8^I7kAB^XjP2$%suaEty1BfZzWQO8MrH5or0d+p>og3D50Ky0T%{)ITzd1iQzk79n zX@ok260F@$L4?-UZ?!#gLo>vKaP$DtakXrv7uVAcjK+^|!7TduLn0&#yh09mx+TYu zxxa;29(H<#So3bUJgER0uX_Pck*I3~U$w3)N3~9Q!EG{~CYMtS-se{5sLx~Tb7h@? zZIdeRnbPM)=74*U*L@y9$6jv;m4nx5AnFX<57D;U=DycD@j1KZb*ntzf=w}jm}_{h z&ByRo5?vWndr(R4bE);cLVO-?;paE^d|K$dZ3i{@fXH`Nb-c~biM?5Q9M43#@4)BI zyw@3*=|2X*z>QxS5e?@Wk`thrDq>(-$=9ah$rhHN@rso9`}3F_P5F~8q|7_3u>$*v z*c?qSlcY7&4_2>UwkE`kSUEoq1u zZEnWtZ7q{6VFLS9#y@{YyF+)`H@nN3Wza#HgA-swrb`PgGP0FIE}2TuJm z`Og+tx&w8N{UO$j>wHD8Juz1aIz`;Rc+LfH7>Bw7{Y=;4q$<80w>=C0O1sBY%NcJO zI&@AKAJ*c)5f!WQAbuiY495r@l4H$rjJ8Rg7kC&yHz0zLF@g9LH*5^d;5T7Fd}8v0 zWlkI%*rLSe(*-ITow91riWJJkcFLSfxjEwHpXPXbQc-sGjq}l3V-m~upDZk_F41NyR)9}(0<-*Js@nH$l zhNkpXB%pKj&1{{B!uHMz z4XB;vS=*b^4vlpD>6M2m)3;8k;^))>)ENv)6SO7vmttU3$j9&ot8d=HIsUw^?}e{7zu zv@tV)fOXIzgQ^N$yH!)%M>F+0k<>}jY7Rb2MbNzLXzzi?+~5(j$kuV_bQf1``j}h7 zjZ1?lXk?r2R!Z}oy~g#5sS;j=IsV4Pi*q5qqLr~dKpX_FWXPd zJP05p$ytOxf4HR=-_RDnI*~KtIuupbp)b3)JOa!ZPRl4)8#-!6PR3a;y+29FJbjF&I^S{k8uV(?*{kth{bJ2xUbHOYage{9WI-;r zR+;D|m^t4nR)1GEjj!l&yM4k)krOEZYUd&ZQp^tCp;n*1K2^Y8?bwLlWL@X#IT9t93_)g_o{WU%~wji~`OREG? z?bA!22+_`=PaEYkJ0~G^#N*!VPg^g_2LAO)Q{wK@gw~VbQTcR1)X6Wd{Y)?G>{WM4 z1t&{qalCI-FnSYPtfg#3_qtoItl6)$K`=yud*;Xq_IJkF)teFYjN{SUu0PmzFSill zNa*5IJm4h`N*+R8f4a?mJ#*xMpgq;8ik5TV>EBRKtxc`u>O^wkyZP)E4nDsX&P2$1 z>k_0x`*dA9R66lTXLA!zuZ)eS!|GJ9CEMcJ+IMJvM!Yc)VSUOjpz`qtB57$eL6w7Z zj>Fw3`~JL`=jo3MfqlbCq;>^EERLjkjSUv>qHo4&_tLiR#;muFdMTD^DNIYss$?t1 zg_4a}i9?AglyS5{Mzu!9!-f)iDYIijDQ_=ps+Y`cvLP5gtYE`-Zg0V7i*bc}^u3UCkPg5vM^G^jhiQ6r@n9+-iB9Lc1PoJKF*5-=;2 zQXJ_uHrEFn&8d#$q%G-wYM2yZ`BKAcroH;_IT(dnexut&P9cIOc69^H*EjVt+=IM> zdRKBlbXMNdc%wSF8O2;(^aaW8Nq!by_2yI+DgG8UwoivD$O!qNW13KE7)lBZH5q5} z`0D&%>cZ^yxF7N;W9I#BHriP6Jy#W?)0DW&1$a6H)SBs}3bNx3)iFm6 z@uMa5ss2E%^g6Tlk7=HRzZCP_`BPoxYJXDwWJy#$4KX}Ud(|=V;x8dxUu<2kaAJl* zg7xPiZRf-?5zCDE=Oz_0Q#CrXt|pa2w}j?UDl$}zfhC2DW_;TVjn z05I43a15!v(~&6y-9k2QXCb^1{}|}gcsm8h5nFl(DU4buHX{$2e{25zAQ1}e0aXYF zsC6*L+&@DyGJz1`e|6BId@W?YXSx|?lx`Juw#iBD!GUr!_dxL%$F#wWYi7t^t z+l9;YO(H>77j|bJ-T~ckC5hK_Ona{St=ejs4Mm<)N}Eb)Z4T|f1|4?4_>+cY?-(dG zXX$7Pb68_?Z*@q@Q0+th0kU%LbpAXHU+?`k64TIx~%br(kKxsiG!u5VR;)=VmQ z46paS9v#7ayOXy4UCbxoHPYdd?^Wi7l9KiiU`RJnn4w~O#o-Xs(~UC6}j7Tw`h{gd(beA1xtA7l73NDS+X#N!e{GEg8Yr z&Uw%RDAo|cEIoRp0y8z*uo|q^Q&QflmtjYQ#>XsAso7(M(eyqzaz-O~RJ+$0J1K~e zz&42cnb;a`uPj9t|0yy)#M<2g-aP^jR7^hnF{XZCDA(I>9Wg_DTCAueuNt;uvJJjt z@)n`+5-+$jbn%VW;o#)*n}GR=dT)}hs=;l1^&tmF$)YC(-`ZNmNXjypBKXd@6Y9xE zN+CA8pM7fXxQ3PzCxjs)Z(62;9MG8&CA<3&Y;pi{&`e>Sj@~K_3VM8sXB*PjI9lMYJUb%cRctQzoCwIic~3aMg+nE>P@t`lUVQT|6-Cfeld#60a9a z9?TBm-oiz^)DA>vi8kd0+KTkj*j1$7_Vx5RnmRMnxmjD{ z;9BKr>e8w5_f)kq$XWUzvzgLj>Cy*D#X9ve&ap6a9jSz@ANjMXGaHtXowBC9u~Q@F zx%Ee7obJ`@+oo*5S)JU%q+@s9r7Fy2h53`%f{{KQK?}3S#d2na<2dht@_Kw0+!K}s z8!V2=6)V^?VC%ML2n~&@Wu0}xmy{z9S+T^-E4bz)C;WmGv&7L-gA@_-r6A8(?(#DE zR}G@PEY!v=GBKj80!9`}qGgHmOSPr|SRxfQ{E8MIPkq^BGjfhIA5=~`&ki_+z1*p?J&b6${SX<HG`d3&VZG8aykW$wrf4%2;4lTSo5G!J8 zyVYc(=sa>mAdHWjHXy3~8YLtB#g@a$rQ5`gO^8<$f0xC{^T&ve6xd4Mvy|mY5@;t zPqyUbs?g&gWUNhV{KWZY1#b}$f{qd;AoJClW;j!>?NZ+tU$POio^v$BB4^a5rgDM! z?(2_@vsRL@Y!!0b z)@w?C(@E zHl?cyJV^4&aM8uCTlnSJR7~LKdynme>3`;88yut5b{O}leQh6tZ)(esM$?BY8ej{} zSo0JxV%RVZyih+-!^`ukC!UztBx;L5fg+Btu8tCW_?j*F&YS1~7M}Qxw_FeXD*(-w zimy(>1^VW~XHU!rC-^8BnQsrc7k2PjP~L()GA&~7nznUH#-JhTVW zLjh>FTztt%xnQ3w1L>gwmt{#X z{%GMyo1jc}D#W1(=kQ6d6UFmWL{^U40dJso`)$;POB_$I3`S~QG{SDA0U*Zz=R-mx zC>G7re?wB=T@ncpl!-qkhgjP*_~M56zVA@^BMt?e8$!(c#oDBwrkG5qlwd(BzO%qp z*cNmaCARM}U;8qWK<3mhD^v1!-4pVQF1bNrjQg}Za{H0wBf zIgXguwr$7w(b{#jTB9S@XY&C@hu0S5M;x|Byn)Ca+@|e!8=&+Y8lZdJT0pkN021k??i8ur9cH!+dk%nu zulZ|na?8$|L0#a;y8ex7z)sFNFtn)im@|_1;ua=}xEa!Q8~kIzo!9OeYlM?NDp*Q>JmuBI$uR=+8Pn06R{yoiPK(S@B2A8GM)fRo z2^VVJx_-IkZ0%pI6J)@go$D8Z1GxCtXEGn322f9IRE5q7B{YsWoulyS=I0~U3Uuag3U%1<{>aybiDuyd+9(^;! z4nInAlgAQD4F*0P0GFy1RnL3PojuBH4Ko4+F?ixRE9*REXTn4{PML9R5(VWRdg5Db zTjz~G&OQn?(>;fv@T)NjVRzoup)CncJ8@;*Pu5>1#AN%lM<{r#=B6C??z=^a4z-dm zUC=Dvrm(R0`#>yKECa5)UKh14lm^l>E>idL zrYDJ515GiO?U&gqj)!G3xl+pLF;SNEj9fbF?qG|3e7zF*!J4bW`%XNTvfegd*F$(nqYC-B z;c{Rh4|Lh^aA@Hrr6cjv>+xYgB_41ykd%g4wV|)(;{>Xxm*1l3ct=0fu&#sYtaTuu z**8b8voMS{p5TgAO?6NBG~8i++hBgvV1DhyJ-^|uJv_9q_t}B$dbpVnYx4Csy@ijp z=HNZcZ{dZ$!U`QhbK=>IW-R(0EmZ}~UKK9D1Z6B7YHwPpt&s}XrtOR`mi?TeI&=1A za+Q!v78R#orQ@1&X=TkVSEC6gm0S*cDby31ZpICOg!8Ei7!`*^3J^zrx>8gDtq=EW z;^okRVSva{Re_Wb_q>gF{}a~TySwuYdeDA>g^HR)w)dm(MwR_@?*2Z#*1c23^TM!C z#j^zYb89|C1zb>YslTUHP6SdX1U3M#C0ei59eZ*yP`qUHrqo7nv9L4pmNvxOcmP$^ zrgTamY8rL^_2XQu`ExxG(eAx4b%ky09UY$Al$JwnRkLaTMbRD@G-1(cK~q3acw!+| zT*V^##1WVHp<`5McAe7-t9!RWOuA@$v;=C&0Ukt^;ms!9pXWtb$jh0)Al))t5q9L` zHnm|l=Pg6Pi1@D;5N83P6{weTczw`uHIijD1MfWl2mw_+J z-=~t@5jox6YkO7&c|yLcX8+>hkqI#sm3Vndr=HE`AUHn(kXy|7-Yme=&*v=<*RC|%GZ*Hddjx{3LHTqpBfl$kY^1K&me5J<+LJIz zP^ypPBy~T6WJvMT%T>y=7AC8+ZHN5P-1)wl12*#+-io2pf4d|$Y@*{ZH)z&ffGQ8wX@TKeoE?ZSN!AUB9Wqmt<3AJ7K~#eh;-US>O6#F$R_N z?UW|PG${)V>)_qZqkS=jWIizhTtoTh>C=w{R_Z_c+q(3{;E`V8|#+{{(8YtF`w z`u75K3Fz5#yEP~qi!MKHYQGwiLs~9!>43iS&f_mL z2a|5D!|c$0h;QQuVJFOLbalWQ`nw^1YfIzzTDUt#+0}u*Dj3j#K1<^BLG07AUftsf z<|hF!7MyeeDKvc$Uu(^Hpr&79MQ3`hZn_0r!|w0EYPs_J1S~iOEM+@CIkm%DlG?+8 z!SoOa4>oc(oOTyfc-QOEF6i>EHz!@M;+=_ZdVp|Tev>A}9Gie0Umi_Yf=Lqs_Gl)! z-X%LeVmC*IdcvEXZdJSVNt0?$>4Q(NqA-BrpW)7bsqVn1I%|h@e*4+{>l5PZS8{ys zq9H!hSoJ}ek|e{#J`(4vy^8iuT?^*m1@q?xbKrvc!-DzUg89vYIn#_nVF}Yh7ON7s zBh~Pi+xj8JYD&v#vrjVvKprg5wD1bgGI*UL$u4EEl3U7mMlsHpsz2|UF$Y!#7i@ZF zNT7a~I2%%Qh>+BgE*$s~8CL=rB@b$*cqd2JXYzQ9$R0%K%4aN|F z_=4@$bldq4h$nfo0l*`r1(}jD(GD60YCkH*vKqmz=Q`Am;g7Nj6MV^lU(d|^m>`Pb zR5>m4pDtMam0{ac^VG%1EVP6NWff;uWgaU}hF^%V(Z<6qMiacp%5->f%89wp`QrJ; zu%nYd_z{tQP@|INU_uW<6N+=Ja)ilGe)Pw|HDlNx2 z-jb?n%tEd+>}z18+0B8V-dav7e8uO}_$2YC`>5Q0`eGx3*n`pe25Q^bH`){A9j;xN z;OZ~b*=B|=*4>6ND7BF;$F5D8F54sHs*8PSXAB59H{3`Kg&kSYw3i5~cW;^m55E?AnU++Yu#}l2$h#8t zNj4Lp{vq3R82?LZhh;Y!1B$BO4dXWlM~OPaH}vgSl*;NeXmAnOO@<|CIW0;U)HvID z#CNJLRAU@8>JkZjjlQPljMH-+#hB=7H;ed#H;>#RM~`z02Mu9h^N)0UYWaumO2-n{ zO*X2N3slb*G2o+*(hGbIg;6?IUIAK${IN72DTLOCFUWZfcMi#MhSSi@lMsR>V0#Pv zx8%EROLUtR88oSI>9CNJ5Z)NAgIKr0;N0P6sya|A@twaRiC1VftWf-5JSobH^RQw? zCY?}0#Zr>iL`lKz4?+bq#)<;Y8{3yn3L5oe&z@7EU0ncm|76{VE8Ah@?O_t@-SJY4HIpJ6M%FzI&TH zB1nFC9QR07Bt1W(AGn&0z%T^+v7QEb%?UB8S)QAEvw#Nr}YPX_E6o7I7&1BtQS-_}WO z@U*foB5)l2-i6Av?>It>#XYcGTKrlVtx9SCJ|_QN)HPASlce*Vj+o9PRzi-VYni}T zjL7_Oakb+&`&izz`80h83$gIb1o8Q1_2WN6!k%e))SVuvel#i5b|!k?Hym9m;n-BB zdeT;0N*uqN;XPqA3@;b`5iO^Qq|!TP%f^LN#*#dU*RRU%t)m@(3QMp1+Bh4$?5B`T zF@YEh{s7_!2Hc3B{*L2rem8De`MhF2vEa`jF?Z4bAu{euMo;90f<6*(J`%H`_35=m zZXdmOj+kO1uYoe7>yUd0#0mmfqhy|hg<*KbSq)vp_eg*p&bN)aEwe47PoO7?SHzfq z+2^s%eVO_2<1gX!{J?`o{5`5qbo##F`tE;M{KC$%p@fRMa0v_i{Z6uGgo>(g33J7c16hovmP%DgD42&G+N9y? zQEOATEbNT^NBOxyC%A z+Utf7d#?7E@7Z}^_8*mQc=zds0og+Xn{?hCgCp7>H^$@#`U>dbqYO z@V+Hx+xc<(gM7|_ml%i?Xw|;J@P!+bZd(u62i*sq_6dk6o2$w+*v0o|{3*nzHfHZX%bCva3gpXPtOoTPa;>h9=m=W)Q8t0DQ=ZBAy zsiYR2L}yW1;f;H%M~o>q;#riP`PTBxRR9ynrGj0^Tx_knyt2fwxtJO)Wf`eaLwWqI>q;p>QXncZ(r(^9Mk$8cwFvvso!@&6c5tNnNf=u*M)bn5;yP^nSI^fpt52!x4;iH{GTJ2@x@ z;eUpsd>>FH4H#6u_kT~}*}4g5CkU9cIKaRZZD2W>3Sk!`S(przVIR*)?hb2x6 zdo)j9+I2M3P$wDf)s|}}CXCeDt0~is3>&GnypSZ@JE=yX1a%x-c$gDhBH_^8t?E|=Td#g1>|49~miSv4e*0ceZub5Xng$#99y%Nw7X*ERO+cyFx+#Ww3@hV~ z%h2Wf>J1W(nQpIb%o}5*@USg;bS%2!o>Y=(m^a9N1l4A56Ir8KsRUthS%s#%8%YGd zHhP?QSChnGG$N#kS-+M0-Guz#7vGSp*AGLrhRu(NPV{20;TDBtW@_L7^_$Kx{v8|2 zt;)A9{Sq^ud%Ch2fz*^+5pQj(ooY6l?}c-Jy=_kZHgfS^p{2m@-7|*NLa425U_fs9 z47c~f^>iELA>!cZ`w+SYK}`d?23}19x(05I6S@X=jS~7KeCtiq2TDL%ajcYk?V|)A z8X9c&S1I^CViseJnHsyp*rC+G$c#<&#*a8iT}YOEFS8%ozrf851{#W;O|q|h=*y=> zQ^99&kjT7qw-Jc_jHjRm z#7jAf3&qtEYEy49Xj_TkI{U`e3+3P@ruKvw(ystx z$5Mp7+~{|k1xWNpu9UqJEk`Ozaz#w-@nzx%tJx^ONnUU34V%0Oopn8QvwO-@zSD8B zLxm(f9j>{AqSh-#mCU1I1ueHyY8j`!lFHxYK^(i<(Q_I4$qH6|j{Y%pkdt3e7@r>i zp$zKe)mLX9?9brY^U^Mbz1<{*4Y)QQ#H$}WI1k0HFAbS(^hp}cpd_G+bMIs3S8qQH z@t5W-3W=)RG5#Bur~JpuP*>Ny&l%S^#&fWh6u;kZ(O(MCbiRiWi@!AdQHqoa58_h$3kYMq+^#T?4Vmvq{_F|@OrHA5|A;7J|;bHlp zA}Xe`9<>D6Sp**?<#;Mh<@a#MV(y8G)X&Nj4^+hKWu~GDw}oMMT3djybuBgL3e(ku z_0j_&EZVl(2X!^4(hGfI{L;aiFoLqd8U#`0T~*;orI#SCQVtLrX050eU2!mp1umC_ zq8_Qm0(~bugjMn=H**jZ{#1HlAskgnGpqd2P&+AmU1jslJC7@0opv^iQeU`nuGGM~ zqMMr?+cbwxgsMdQ=>ymVzUqC?|T^W1-> zGNME4Q};Y}#Wu1*>(lhSc7-~^O9QEYo_XBo(YH`{D zEL_cef{YJh#$|0aY0b!2FSXq&$h@kbHTg`!WYwJwVE49r-hS*CoWnQ|1xQPIT#oe9C(V&9+K_Nr2xsaxgzfS>hZ296 znGln#lel0Pi3!1Q#GTGUk#aSSRq)BPdS{zDh|hwhmp_@TQ1^B0*U6B%(FIsEyn^H4 z`CPAU2X%$eXYuTz*Li@rkrQ0Kb{TRS$!qEI{IV-*joB~rkz5%DOxej7t)jMG_P%YL zL>E~@$(Z}?1Jyi?lA_XQuLKw_eKl%UTwO+7Ov1)tBCQ0YZmMC#TEK85;T0f^sux#Q zxWkB`ICqf&TXAVD?o#(v4!OH&LI?MIjC%5yvD04ntLjXy^8ZxD@|eTN88~_mkp8XY zUCLdQ=GUwIte`^RFi@$;u!7k9tfuma4L4AVXZ=}A1sD9hlT!^HA^=I267aM{l~ds+ z7r-{;sAf2%-=;%G4dSe#34dm$G;ABL zlbG%0D?PkU=>4-=>aG8blBFD`Fhuo3CkmXw0C-th;}DLUBb!Y+sBPg5rV+IpKhX5` zh_DW_yV)df9);(x3Wy;B@{ET8g=gU;I43v`Z~P^5j3rz7FRJwVbNKSkGN8*oAl?$Q zes|!S?LJW_Fp5DiLD2rdM5#G<_>M2(P{QhJZIFk*UsIlpS&0>?^kh|K$+48Fp=3=Y zg6e1PxBQt4fWuo*#D!yk*|Y8?5r7ntHJbt)K)TQ!uU{R`n=H8*+rNcj$&VV|1&z7q zS&j2`ahQ;13AL~?U~ks1|EF$>H-jIcB;gx{knUMM1}EGX6@NaU;L&gGr|v6nh6ut& zDBi_VB1^In|ADiVi zPT>@Pa>iYb8@4mQKbQO;DorHztMUZaS6W?Ye2=4taK}RdEZg0kZOs_ygH@w_aQIp< zGjW$moE(q_2p}od`4tQUGzJ3#;cp<$H^ANu`tHxcYN7d%=bzOr7J74ESVa&i9Vo=8$s7c%)62q)NO zdj%gsy(1$2Cdx0v5%J>%j%620di+ClZN3wt^6(hn*N)N0%kjrcrI6R7e3y65VgM)B zaU3pR^2t}+7tn8XUz2^nG^A}2-5Qvygu8&sA*D$V7L&psedZ5oF1aT(}-Wjv_Cu9JA?qG zu_-ql(2I|*%sr4($?-w%9(}Ri%d_^{lCM}%KCGv66aI5jaVMfUvim2gPn~iBZ!$zu zrPrlB7v#TF%2M}@aOv|;xPDSPye2^$JoiC>IBckCn}*wAlDF)?L#9I11>*`$IJuE8-7ysY98aKOyjl~4@vwJja^iFNQx;d5nY6>haX#(z zKR7Y+XopAP?7XP1|4n`C$|$ZK9*dK=)o<#`h^HMMETCgiGC){>)Vp@Uz=d_3B1$Qx z;Uv#%37Lok+Cgt}l@RYkNsEHmk!?AS22y-y`2FHu73FzHrrPmznGx&BJyM!AaEZEY z_e&KgD~0wcz=$HVE7)?FcMbW^D8MKuect$-71%1D;6z`-+qo1t`lEW`wc@mbm<+jl z>5-e5HnlJo*=5{=*z>GwDQn4czPUn~NIUuZ?>4abO1MbNaalM-b9O(M2j{k>ctW@Z zXHPqxnuktrME3+QTq~fT&0ygV4Q36H;%=%^;R&A}Q4zo`y3!N-&TK?jgKpV=Dv?hE z+6um&bn<^IA+Lac3OewAOKzBsJ_3kxYYdl>!axYfKxXC8uI-6lB9TuQ(<*cnTP%@F zcc^G?iAKGalM{al{Upxqo;}qa zLII{YWxPB^fvot{mp|j2cmc{F`QJNA;^obRgS`Kr(phX#2KIzMt=X`*0w&_ah%~Ut zA0N}Q9ZeTY%+${o$R&wrrJ~5s7EtDkMBuTQ&lbz&y(muVt7djZ@zRtT@*D+@;)n6m zW_BUo{}8b6|B!5(V7ra#ZimIqukCvHLLtbRpQZz(Lje4z!NDbK+0FsW$mVRtYx&M` z%TnfSC4Vb<@yr$4MRUiCwQ`HI0ln%1Rv_8K!Qm^P~=?reQ}DdqX>)WzoJM#YOA{6=C})ofO#Kvq!V((r8L`KYg6>5Vd}$$d;ZQbrtqvEe-Fbf~2Rz0$6RCcH zAA}C`rOJjsfc7r7Bf4G4EBA=kjT%0aTf&j-LZbPreySDpdpdN#RQwztK8P<*n(Owa z#)li!KdkqEFe?`9dl$)WbZLvoss7;<*#ZR+${qyp*8UO^VVS7@5xgfJp(zgM-2a+sNwW zpf8TX698+Pl&&1@c$gwB-GzqcGL-57sqb__b__FL{|=Oz-BC#Pjk!z<-!Ob8+hI!? z8PrT0Z5aAzAj!a_7YCmvxoFr!hQXRrJrpQKhL_Sj)F(w|&14rBN<7+o$2sX+qye|a zabrZV%SkjpWQT+2lEWUTK`A|*kw|w+m9G_4Ul#3*HEk`nEHp!wT0!a-Hqz1nw1asrnG=?@vl}~;BqTPr8s?#h= zs>^>Fkz6=)9dF^5#iGT;^{2Wpu=3l+>0uj!mrBk#)y-O_-s$_a(M7|bWX!B7HACT2 z#)}p_ZXQ;6H7%iIQkK^G3R*rAahDIYuM+sktFA2Po!#ZQzfc+F5-P6lytl$ina>1I zQqhzv9iW!=Aedq)^R0X``vDt2ih!HaJ4lc=S~K(#=+S)VfAe+N8r7!hG+1x3CYyD1 zN@-l^*Dx9IDr|Eq!K7Ykw>~Vr?c;vsU<wBj<(NB$}E^I?<9o}Ph2cpkNu3tkQC}q>a8Atu+S#1JBLQ?N1Ov?rnfnE}z&I0cZ zc|h9i`8Tf|tdlAAppyl>P&&R3aw&+=Nyv^kk91GTK%?`UZPFKGL552gRqa58^^VBK ze?ya9-nm_#=y878VjlXBJJ2_kq&0z4X?dW&*Z9aa8LmgY%h6-`v#W@P@SW;(oRVP_ zk=Osinn+Zq=wHZm(K&y)n1Sd_Hb#7I_oco&*U5X=$&tz#qF2B_&>8{d74=`RCgR&E z`Wx%4DYQ>di6E~L@QZ3~*!X;=;eAgF@bKxdNJudF5&vVsHN`)BC;+~H{ZtE%r_Gip zuL42i&L!@5l1?L^jaq=s(nizCxyIdn%~0~}xyI2w?d{Xc&0B6mnIPzg7O^~vPFBKE zXY76b=vo|2scmC$$#dmV&&VXen2Pou^~v%hOX$}73as}UcTpiu@{)JOSnpBs(?UY+ zN@5Tq`G$4z#C>h6t7tD8V4s$QBZzhw4l#qd%>m)u!Qlof`_)A62Py}NUIj-$8ASaT z^osb_i2ef&cQr-ClF})D8P%+9g zR5BMo*HUz>^e5ehMSma5fKrzgSZW;o#>WA#ps>H=q(O1-2FFL8sd8V3)+l#$qawOQWDqWzk!u3ocq532_NGp z(SJd31iV4i-#ET8c)`eR%^r}Ug{M)ISfetEc3~j#_)l)p-YfYZoHdh1ZWQg*!2Dnl z{8bCfA1Jt5m3Om_@U(X0Y7*g*DjctfnV^;0Pb_qhm}x0ET%C5bHsg>=9sgfk)0Ygg z|9RXCr;#ch_aABh;KnPEWoCl+u|phw^tSYGt!~>|+p#yXr)v;M&lv3|nmCNqwd^ge zURheZu{Lp~X=F;pnK&HRI#l_M(%Sq=WOEovYuOuHy)sU1PtjQaADp!tv($g#n%Yyy z{v++naW9<4x@6qHfA4SZdA{WGIQeE18puWQW$-it5!JdZ1)a%#@2fBn+F35(SAR+3Lj!53@Mmyq4J zmS_asAWIE(qCCW5b#bR+vCi>at?iy>-9!1Dd*nXz)?)&?(*$;-E|8uag2SFk{jk0N zq>xR}a8HnYZ=bETeSw8o$Y%$ljarI5ic-o_?US}1s9_Mz)_~dz`1C;ZGM6-KQyXR5 z2Vu4r#Y7GD517&IA9Gjsbfm8&y6A6+G#l!SOQ*~|8 zwe-2MDVy;n3@1n6T`?3VRPzVb=*+uizx;c}6&Ko``!%P_IffQXNjgH#`3JpLE`&2mDlKym?UaB39*Ojy=;AWY_HE)0@kQIR|tv&GiAp=T{ z61oJY1f~IgmP|H{&kDL>%Tf`3_Dy&Hl4aESENqEXK5NXfZDqO8fdg*v^@?_TCpdsD zX(-_j>ECTMG*2i|DpXEUH!@c$R?1Npmg&HXOxh%wA6c@A$P2$hWsT=bOU*o$oDeBA zL7Xvo|BLW^jpQxi$LP}oSh0yWAH1)yKXb2mp_KSfw7(GVYIcB`w|#Fi9q`Do(ziC@ z;n+4sZTRJDyMy7l&lkTrSmtcKY>T&L_lEKfOt1LskHbg@yLd8lA=SwV^6?BPmHxUZ z`gNoF-H%O}M+JH!`9{L`SC;@vVUOR2zaoC49YgpJMNV^fc`|#_Y4Sg$u5v4RxK+qq zTDwJ3e6hD?eOK2NuB@#Vzo6ucbgwK`KCO7vYC5WY`i0$J=&?&b{tbOOG-`OYZ_d92 zBxDSe5iff^zZ3rwdS;;C4rxK>9gTX>i|j+a{_LGheYpc1>PVncEbYo+8pv%gQ`8Js z#m>(mV03uFE$Hn%Tj89(S_dXFfr`%$zgrVcn0%fOg!nEY;?Rs7Sn#C5X>7SXS0cf&GQzuGfJ zxJRLLeT5+_y)H?hCb^pX0^8fH9R!{MVIM>RWb#)Se_{X%V}J}Ji3~#p>v#AkLa;Dg zK46fOVYp!Z4nxS9Ei`sN+?b=ERqwJ2_IKN|t|H0wn=o-n&spDN2M(l^sx<;OXp?CVR7@d>=q@zyl*e6IVc)4=leOu?vEL^er+AAT2c{l zOUPbn+7Pl6vhQ0GhB2+gge+0S6lG@^vc$|JJ27Nmn#nq4Ft#zqn3@0Rc9**9y}j>! z|L=S6JD+F#&Uv2S?{|LZJm;Bnjyd1o^X%7V9~0)GO89z(>dIen5>~xje>|p)GR{x3 zlyu>kwe)|mt*^4YTX3{y{zuiKV0pOm>*xu~To>;*57W1-2A}6uySvDw#iFkDN*DZcfyVa^x15}XIwfO~Ph^gaN{5j(tX0|`pnu7>nDWg&44Us{h^}A=k( z_IHZx@5)wz!m&d-juo4BT;qja+iCP^GQ8ks%cAq}rop}~_vJ+eA9CMPiQZ5yeUKHj zxJ65oF@k?sYo5S;EAR+bLs07cCK`s1LZ{K8vvkTUv4tC}9$aJKFe+uw%OBt6KZ70e zR0+bk2nSpNOAsQg-rp6UR-gA=d%*yRuI||^Yr-S4X}_3~z39}DK)+_qckW9wkM6(3 zFO0|wT_;R4#$f=W06rTqd*_WVj|d`dW8rTx50_B)l*b|~R^l=kyX zok8yUAn*T9-us%*4CBTREa*}ebO;ODm}MzO>dfNa6DmU^g0;a(36+lIS?<@w*E*}D z-VDl-dh4Y=1ZKPi25lT&5>yL}*nq0_#ab5;Q0$QWQ!An$rDPiJ#m&viX2{V z{T>jSD`DU3R!;WNX`GqAGYIKKrfk>YcS!&r^oy*XL`SjZuLPLhar} zjeEnBQ8+`wlt)JC+_p=dz3xphDN&?#z^46j{p>{fqsIyNDLP_z5^<+rhuS)Ho^u*W zZ_mBkEAJeEuACxQ=}nmLEj^V%=pQL3rpGm`EoHH6*dl97dv$K;u{wZ9TWHtJ4UtmB z+C|#^k{uikH7Oo~3s*wB6xT^{>nd$en|am#=8bKN?q;aO-JH1B#QSOor%u~Fio!&` z)u@1PV4Kd=#5`Egm(Q1vZMbeYvfU!oOuHPT;H%GDejvwJ^n9$Ukk+GjI9({DRm>nM zX48IP^#!Eof-P)OK&ap6z=WTAaYKD@k$tUJ$!okJ>YULdU#AT@jvI~pV`70Lu0XOL zWVVe6!>#v7fsk?!*upqhg^p+I`I%rV+uC3i=d|gHm(e&i{~rxpH-x}#8#;Ao zYJ#i|Bn!1=gz$`3ir4IDx3V-@is25Juuz|%nrufV-=Z+E)@`q@i&xRJZ05US)*d{$ zDw!D4v1eESLZq+<%=CFu)uaG)HaxKt zA0pLRYhGf!v`Lr1r_#^SkMTU}W;FcNzcwMCE12=7jOa)>jF0=g7QrYxgs||i`9}nN z`u6EJdczx9Dj{s zYAm0=C97K z&*nkC5uikngKGG0lo1xD!{ykjD+b-W-+C4Q-NJWs5yhflVJ8R6JI2 z&H753GSZ!&CP0RaNX4RzURW_Q`!Qonu;HDt_C`sTenBHUV{u$qId*!`QZTX2l6u7m z%-^ZS$R0$xFA?gb!kWZsPu~l5 zidU3}L@m=OkD3=NjloBqiwkHTokEP0)2Kc4L9C-o&Li2dfw@F2>$RZOZRU69I|?P+ zIyIb=-Xs|eicfU=QA~-`ez02iYuXzhNUL zec@av+>A5f(0sn9o(5-HZ$eXB!h1u7-T|EeY+kLY^StZ_-ij{T z-k!8)9ln%}1nk>pY8W=UyJXd7w9L`m(%@m+dUF+;vxmh^67Qp~cV+u9E-duerNy#N z3llIo0yR>*7(EO>wAgDB4$tAT4cXNYbu8xj(NZFTuQqG}IC3uOfIsQ-K{QL0p=^Kk zg+6kt2#IT-X5dLQ{0y?hH2=$ zxzIoNu48?g%EBb4oCAlSM@Eq@Q8`f)7N)U_QDwsvBk*j5RH>IAKb~+A>_#OMeR5EF zw82Gnyc7&oL^D9<&oBVpxdE3ux-ODO8{`a;r87;-0-zK9VoBbzjR!utACK;|ZCEEj^LgIjEj%Zzgd8EHu#Xb$ps3tg%e@Jdo z5EJ{vlLp6_Jk-$faNmFC{nP9-;t#Jh$#c!rtyk*S-lCX&eTZ|ZggZ^wK@A?d>%tpl z+kLanx~ap_cVvmrZy&>R44q7nTI9RTTi*nbDC=z}L=dDms6t;6SY(3kOOOr;^*-(> z>krvS*;j<2pTw0ms)NU$Uwyli#XFIUh_J|)JLq!rm~3=r?Ogm2TC+-X6S_fV#7UJq zCwL&WWM{0G!@H-xMlTG;$%Xb5M4uNHrB2(bZ3N5^8q9~q*_H4K2Bi%y{ur=Bg-x*% zGi7^pM{`h^dBNPlomBeh479sp0w)d%7*r*nug)UdclIGK!$M2K4@9W7X1srbVk|5; zEwl~n6?*cI7l>{IZ^&1N-iUoCbJ$^nIT*sblPKnRt}*Gz$g}%-n~lMZi+Xbz4&Hl1 zbfGt*4mreUI%cZKfSls18YZH!&Wdl{at)J@gJ%~LI;=$D$bxkyrZ1A8>^0g0-wNrC zeXMmhqYj=ptQGI+#O@qzE799D(Rsqq$69t3p3iepVtb~YtI6oIoELL@fuIm`i{$5H zKkmE7aKJNh-!Y&QGij2UG=I+sk9~`O8_34(s^M``6OrZO^5c;rafSH7HekDPVeTd381s(}rGeH3v~_00MJFaOI?wr4^(Z(eY_5IYoxJ-hvcd~3-|N8Y;*?U%DX zZ{LdPzS|Jl=3a^-n}#O@dc8hW&wl8%K=jeFF|Sa@J38kdU*28t=!Dr1PG*~U)Gssx zE}P$*y;r|2YOo5Zcm8zl({RK64;CBRPYT9_3lesN^9BH#Bu`DHehwHxljG?U`M6P%&v_CV`G{(XoqliB*Z0xALjEjh?r%e^7P* zu;G>cI%T>eH%D}BY7FNBy5xK|dhJ#4d9q*rZfw=|$gJ{c33U)E zm`$vH4+&niK2gyJ&T4Eg9apR$J{V$oq&RT@A;@{FT+uGD)ULVs!Zdv6olB8|H?jbm zj_2EGB(_9F;JK2I?(Z2MX*&h80(0m8e39=9R6pH6q-tXEpevyVdp| z3O;YembgyOTgUnkIv751Pco{k(vaneBI^dTL;f1&KjEHV9{tszu7DQg|T_v~yuE4Ic_UI~_#nhgqgK|&7 z_7Cgqc3vzIRc?G2W$Q6eGbeFVJ+=and*0&M`+6JshSP%5XKy8K7B^;$VwD?xGVNRY z3Rt-%4RQB4?e7lXiJS)T=#yrGo_Yr?_tSK4O zKr%oU&mis-asu2(EDUJ2UptIGY-~VfFMT{*8d)!{YDBJR;4lcxo4I*kD0^uIk;BMo zaa9v?YeR%VVBt*4eW9GCUqo^G+j3J!Z|UCWL^iOZ0Ui3lGRbF!2cm9Ewn~)@y$B7| z0ie>8Ej)(2xm>UH<;WkA@4-_oHgzgwiy@G@*Z z{%K}0&y@Dv)M*E9tb}>fuAwJMUN6=1YwRItZm5hoVK?_MSN(xLvD%Y7=N3)hVBf5l zImnWkYUQmnC2WikELqohA==@vj?tcV;fV&8m17Urutq6GSSHG37X*2C<~5Ja`xj%& zs2B^BPo-|YOYY4?%SNyXt!)Mx%K#UqjrdHhiEP_=nnE94ylgCHbj`WSCQK)$aNjwT zO%nS&vbOL*JYhTJpGw=5haNTu?1UxXHPjhDlhO@6b*M4o@~}*3Emd?#FUIpgxGHLi zq7|1LFER4GK~w#LqRyIlQ5MbZ*Y|}U+Xi?XBHVJ4(;=Dl$R8CCyXZyFZqht|N=Cl2 zW%#0AbStXk|l3~xBk);o1g<=6gbHZxC$hOV4aGR?ew;=S+ z%WU6S1KB(T2%4TzvLI`5O$nK_PW1m!yVj$hUPE;6r`3$O_YYOcobMm3BD$v~W*U`0 zCN0@1>pv;kIT;i@kYO}QZUSbWn0D;mIUgidS>bWy(PmR}+I_GI87B#*y4g7vWD-vLR3@CU}%bNCOfI@Flv7<=5> z!GAmcidwJq>!{QD(g8c&LK8=PxCvKy_e|{U8+Lc_4>BIS>v~bHP`NUD_+`%&wwQ88m`_i zd1TE;;Yk&3k^!nxHEF0B6vU->ujJmhp+y1sa;#giy!wOlGSxuMB7J`)uUT~=?pXl7`<@Q3VF{>~VPN#E0F>l5ZLwdFeZ+P}S8 zQfTo)_JuR$#nm%i+}^I|x*AA@>UoArT)XYo2nA_X&Yw$8etL%c%J9r{Ab$fmcm`{^ z9dAOyr5aBGV#7jT4}?naadoEHhML(- zuxedDy0J=YmmACU);rP10@e%)Z{fIpl(kBA*W{{cb|c$`+{5<_cfJw#scc{g0Cn3v z=<`47kFF^SN}4Y6I&BP3y_R;IpKZ{7i`-4$omKUhIMZYS^J1afhN~AoQ%95A`ICI;NBZLuDiV_O%z_IZ9_A!^4E;i3>B6;&Hb>OyXT4MU|#AS zRJ`z{Q>bs282qTIO)ujEu~u8+9qil!3py%Om9|@wZQ9|En%dIN!R9UQK}V&l;&$h5 zp0>HOM{Q}xV2yV94#{0}HXW&TH76s(LWKFY0c7`ux@c_gc_0e!(Hnke8F@QVxbYC+ z?%hx$cCLK;1Mt;4RhyIV>^#Y-n9A^)e;cvhbGvqxnPz>tnQ;&&HAlrr;F#p5jf`$f zP8Z`1z0?t$J8LkTu(7E8qJxB4>tIH7y5E?{IZv7X%|4YH2aw|8TTY~eVvGRp*~xMS zt$gC*=RT(Rkh2dZZ0=yXh2{7fmrP$E&K4K5;x6oWbBsM(a>YN|&3)QW)&D-(S-JkT zZT(?XTvyAm%n?5}O>?#AuVp)(7cRbofx>AdnuW`5{(b)1x0TcRj~MuB6g~Dx7E?KL z@cGn37qx+t+l1mvTB>gDV?R<29+hjBo=z0Yl}1e`h`CAgY>9`j(y#i!P}dV-J~e=B zP^imhuAUtS;D_{Vtkdgiv>7K})!g%y5!ZJXQ$Gga_6wC*#bwNL0KP`gWQ{4}5hw^o?qhYDR#*paFQOmYYFsM+dV@z z1eW3o0k=&u_&aPzYMZPq5ed+&ua(OwYv!VO`G>47&M7erQ{-erCzBbrC@ z{RNvyW1WtJw;W7aFIiG7T~z0c&F?5dVb$&K7s+CmU?-1keKa`JRs)8vr%oU1@O|v8q%AAxz{k-Zwtlf@ z_3feSHPgpNUM}#OPTe@J^69Fa?=o@&S5q4DWl+r#T|a&36#>a4BVCV80^nd`HJv*YtJUbPt78a=nv57rkl zxNRh3r61B7B7aO<*u?YX!SMIhoYhB@cb?X>-aNI*j=0vDzEvk8e^zS8B98=>f$CE@ z%)=IQCzfA&t~FHHiPN97>4XqM)J}i&3G_*X`F5SVKWa)DJ>}7Ov#DrwZOYsYePD$I z;!>P>o~qQ2$2^Nkr0iofzTx3n%qM-=8Z9G2gP7uHFsf+1#yu()!gB z;u0OXYp(U7RmTU`Ih}-~9lA$5b%o=%o%Y;l?|JT|-{7#uzYa=ZO~1ftb|6^;P?&K?fvYu5c%%Nwl+bj&e8v`)P}qU1iQ30e z%USOO$y)hZ#@*=><9ggh2R8ZNzunaE%FWiY44!9U+?^!>Hx}E{FkS(W9S|<_h;%Kf zQ@LofM-sj*m;k#m3%fyr-Jrm3&|x=L=LrkGR4GtZY2SLwayMojTVQ2wRTG<1Wz#e_ z$@-~Zb%@5x;}S?KPd>_1e;0|H4_Hejup*MKUau90?_?FX*k5hhY(~80b=B$3*cB~4 zC9agZY#UM8^M)p@aXmf8nXxuu>PR~d8H$F5#2Jya_4REvGSO6x1i@ zr9ZS+tXj}k;xcx5E+kcLtW#)W^89>Omm^nO+&fhXU97~8cTOML>aw;NL{$hk8b#Ws z*B!oKv-8x{Aqlq|JX=!lTW(N&RkEEs7z)+r@X}L$y<0c3GFA9pFlL{~EWi7_kk#t_ zC~?|!K=<}qH@istD=L1n(%UQz-aI`2{(Q%VM9uqEb*GcK*53{A#i0814kV)*c!M8s z>f;IpvR`iu4TUFXri;gi4trleh_f{2X!JL-bLp|ss6rod3!lDxH`+RYe`wH1i66Z0 zVUt;CMA)-g+q>IQwOZ0ThwfC2w`o?|sVH0lUlzj7S#{u-~(V+8uA+x$s?56utM`&3uSL+gg|FqC0dQKg9DM8Ql?7U;s7<#fX)jBkawRJ!@o5fM=5%$28FnS39-48*-{ z`>n2MahpedP^dq$I;lW~Z_}iMzr=x5*~nzpH%+1MgMuxer>h6|C=1QYbf2BG36_nT zSP)S>a*sW_^lGuX!Z=@{qIpygWq;Yw-W~y|6LIdkR}-U<_F=_~-HB55lL2R&2dP6h zr8%6q4nz>CUG`&+Xk93=`XN_wn>FXm_}%zGb&hJh@y@{~-E7u(g01|D2Bm)U^F+#h zj!CCg5)bCg4!W&%R>12EWeZe{31wp_eZ}r?6J%ao8nZ>f)Bc!aa^)&-cnBl*OzlVZ8IM*)+oPH3EXipi3|V4!_Oc)xYBQOQ6r^O+BZKn zbUGtDY8za%V86K5ou}DXDKoxfDW?*S)!9v=lC;zVdv(?ipOei`@;+1VLZ5MlTadML z#xR)@C>{zZt$19^#ar3Ob-tiurvG_C->ARoAu9h+df&stsbXv; z!gpFqnQdIi&H$ zIc`{(xIjF(J!c_ZeT4s&`Gz0hYxm)IYsRX@-wn~^R*TQReI)ioRqYtd49oCg1ee`= zaoHKxnt|hS@4L=T*{etQgj_mgv>vu}a3~Ll@=qCOY0f8H7^ss*XyhKt_}#q1=JGyL(jUrRBKSXu2A2E z8gNFJ^|fFxl~i20IkK*gEu3fgpt~>TF{;Aj=7)8SY(;$hQmlq9UrKmfzm#$# z#Wgzw+HbH=;uIno1g7!ksX&hAVciye%2unt)Na1s(7F2fFE z2aqqWLTfBgj@l+8F8y%o5{uoXRR+tiFKice25u63x$XV+mb<^u_9mO-4yPEj?p>BXUsTbJ4VRyWZ! zO@XB8?G``Qf2gC>EYl07hZ`$te&(5oA5`F}_RhV7zsTMt^~Q_qB5rF*GtoT}f>kg? z5+QRJ37w_gIbP39ahPY#Vd{xxu?oc!MC?WPGq50(%DXv`{D%U_b>|9p{+D9fYdUT) zyn+1IlM>^#icjxZD84Vlf^@fPg@kVv*sE~vO3Zsv&k#vX^@rhy1#A@>uEcy0)pb`! z>`>r&s-JXN$=0`FDrVs3vuE42%04k-l|Jg~Vo9-PF`ha#fSo+HkI!vNje*2o{F%Sl z@#aDUKwYw-M0Ah#smFIcoO+Lm^*mqA=486G=3g%u$#5^z{uk#Je4%K>;=U97NTsEa5Zx`R@FU#ct=p0NOR zZv@l9hwfGja#u!OvpoAUPvnx()*mG{?1){nS)Cx(IR+h0202$d z?2OnzGC2l)PBOU$cqbW{0mVrs&tQ}DZyIn1?~pL z_I~!TQZ=}@)}1zYUIiciu5qU`A|bcO*fqtQR7+}Z-Fv(_Vb1+PWrwU*r-?= zIU{}KYI>nSW*0lLR(kGrSzmr3HF4-lLW7@cL)XYvQBy^wR8`({IBC7BqJ+zb%vxLe z_274mPmJU{U&|>AO7hF!*r^SoDM0;jsjqvkY?=^!m1?TysO#ue3|DOgB(!M*mDI`v zlxyuBbSJ52ZO42@Cn zLG-=*W$SLyHa7`uoL7nMmPEJEI&A5i8O16ImiXzZ4ecalLr7iHh0efIMkwUxFN*FO zv3L|u98i=TPLy;h&0o{$#%HH=>q0<~Z1CFL;7;w{gALxKz+NySI;W5NM4wEey>k{Ec%>g8K8-9g;Av# zpKx3;N)P!qJpY+1e*!+aWP3+Tzr{J=xZDFl8D%x=;Q0qpOEk=z*@W|5`Cb=^0YSL} zyqz=K=lL}5zU1xA(XfmV5-TiMv+g=DUVQ&m>_ss*$Am$_oUK8ZZ+R%$J)w%Tm$8m0 z4?gXOKFK#VZce#a^5!OGV%qy|^=CVYBOj zp>7!g)5+Wxw@BZ|&s%5>Z+tu5l_-~7{PIqKC(KF3i)Xg=q4!;EKY2y&(2|Q4smEsA zWyYlCbo};?s^NmOKE(;)H=}Xx8j1OC27Bt-ay3h@OuiyMD>3t1OnT+P1=07ue3xQF zj0;dgh(MBq+yX`?42_i~hMbK@RgAMmuIh(*im!{LU z_{4#f!*SRKk%S9$t?l+Cp<4E9Z^J#f2YhZ7-*G;Dk(=}C$-Fx=Rp(a|t*s#rLVTtt z>D}GJHMKRooq`O7`%CjPOyIG z#YHX)UotyO%N@bJLlICMmT&2er)yEri0~N;)Epgo6^V=^Wc6d$H}z5;8~Y1M`lARW zFEX-$3M$D)aKs_7BuZ(31;|203I(i4-neu%-|+KTNvf~A+QyUa$y6YIy_Zy6g@&I*h^n~ zn2wn_Nd>)s?;>U1yD5&u<%px06wZPU_L5abXi9t!96@H#U6Uwm@a9R<@#!*msNmG> zsk`9Dv@U`wCQx$Lyohue8HYzGVH>enZvg^qX>T*y79@qm5-Fh%p^ezt2@4#>xtTP9 zqF-5L)EXB6ZX(h!co2n%X`mb|C+UN56d-c;em@x*MF{8tBH0F^suUCnj*25>#MV%f zdx~F7tDKqhA}2XckhsB;B$Dzh0U<%K1xw-l$>biaVHXmX`T}F9YevY*$R%;+3C! z;U;1OB9z|Glh7Dw%A~{5`0&_09B-+!Td+cl;PCUO<5K!sXTeLw*a$`#dJnKlA&s2| z;-Dd?TodiGFCaMaq)}QY=MPm52yJpQcce`Abw6THw4F zIZ%YI0$G-N(;Bf4P=0ip8wu+_LdT~p;6deA5fNR;{U0!06yFJ;5DcXC27#hC(@F!h z4o|C?mtkt1v0hgQgtIS5B#E~a|M*F&vK9$RQ(eey!txMjNPAx_fgB~l7JC}8U9V6csG*zW>f;8Jj z8A99VkZPf5BSM6ayefJ8YmOI@dc083>nar<4p z!3LB}PzUliGAoO52&kTp4}JxPqAb>6+-Vj>dOSYOu!e*O!VjS>W^L|S6~gCeo07}Cxky9gH56p06TlGHoUzSl&G8yL#w zMMV~1?W-=rECaD+UEFG2@r|=gk0}eh{jw%S4 zg-@bV24zG0MBP$K!amESyQRDc8StjBB|RhhY3;m6vR(4C zcmpL(ca%$wqA4d9`JtwMuz{s=#>=G?;UyPn-{R7^|9tVRmv9LQh~dHEKu>5CvjS)% zdYUpnF`MS=PEu|?hS|n8tbF@;5xR63V;W!)3muxpW1kZmXJ(VicM;U5u>>@gAJsLR zRJV&Dw(fEOW=0UTpVt?KCe4atRo@bO=%q;{AH@E1b^a%Al}mj<)BIE!7x}>gF*D>@ zWEVZjY8MU{kG1duVP>HbEk1P0555>(j$viuF^u7nVbC;q674vParV{1-y>D6^3&q~ zDG|R%ne`zND94tjK|d}nsO$&~5ZbJY)WWVt6rL}G zLPJm2jHz@@Q^zNro2O*#7viM@n#iEeI-DJ*k3y37ZCFH(;Ko$f@1zqiQ79SMo4u&@ zs6_-Ab-BE2A*-Im%K*lh4}sYiNhHRJt_8uRz)(6>8`bCEh5~0YP6oEUMJ#gQ0%Ml-vn&~ z(kbXnG~1#;EtYUpC4q*s=*B{57FaUM!kU1EwUhYPQb7g{REz)y=7U1HP$(%bU~tTY zJ7Jz=j-}HCNhnk;l|b;MQ}7fa>bP0B{+dmsZ?q%oqSLW*)=P_0J<`O3!EZQ z%?XqwU&0I2dIDvF#FhJl!1`p7#_*OJQXDV6>Q zq&ZnK=>8z36h{l3EJZ>6CkePD%I{f1nFERBg{D&by}+uZek@w~53#ef1q78~+zJbD zr~7wa$NR_5E&oBmyFq~gN!X=foPQA-t9HIodJn^GNy_AVRg|@YNx1CkvXsi!5AQEu zQCy@TiL<1LNodMo9n#7oAJ%O+E&b;=4XPEq^I;4 zGXYYIu1EPe1LRB1`^moAMzMn0iz+QMLA4BKd2V0~olu;L(r-pkzuZ@CKaZ;_XkouE z##1HtSJHO_!^iRSe)A1&ABR{a%HYoVpNG~w^sH5qB{t8!TW*~1lhhGvPgH7CL2H6i zL2XD!Y@vIv+{CQJ6MIIIFt15T1vRRmGyGyfjYwN8+`XrG+;sjRB^H#FGb_PJmZf%| z6VzTpIRTUaN~)N3nRoq26Z4;wAF`vM57%=N?FPjeD3^hf{6>0e@bcrcOX;=JbxD=T zN)_}0e|8Exg%QnNeq8Yr&0Z|Vo}vb~&zyP(;W_ z7i?1QqvGn2wJQ<|C+9||Ws|(NpKj9eIl`}by0LZi=UK$FwkQ;lwsx+ z(94djNk8{}lC;K3cQpX-6A$3^JF~J4jbU(eTg0Cwcw{k*t5f+} zxk&|QKi1|x@zDlbug;L1f9BCi1r?tP`M0!yStz6Tcse)XZxdeuEfIOrV1 zmdQ&`b^fz;{n)%mp2Xz=>a!+4s`y2Q?nx;Ael7eHiu()Q(hN)_D&W04@!4I*umW>? z#5V^>JSQ`BCVH;zYWTT!D=I?y_PuWxwB7dO^5?fr*IGaFYi*MnYQJby4FB>S%HF}> zF1X$LcLlFl!*6v-^k?PsWy=}4=V2}(Du6eM>+o!y-zDI^+V@Z1`8^NQ zDz*P=t(2=DyZ%&t;EB)bk_IUJN@KJ*rj|SIt3`gdDv}g>mplI#MK(As4}IU(Y1-2K za-V&riYxYrvVQ)uEv`(sS|`4tjil6mSz~uN#s*Ojj3(~GYj!4N%YC09 zW7o%tVuibp@O&I6{&>l4(M!u;<$tOgU#)pk;MB+7`{S+Zt^3%&zv@KSh_+(A^d9|@ z1}0(VQtcO)t>LS6{8#k)93bqkH+#36{*Zc`8W*xRg|2MsR8og zU9*ds8Kx4x&k_{z6WPTDm}cO=FT>mYaV-CLKH}e3d1lF9wGE#qK3wDxS*Eb^0Un z=nH(wPwXco>hJI+A2F$4#hB!;h~)egZn6@R^A}*}Z*fMS;Y)r(pZtO_`T}3_6Z;F& z=nH(wPi%qvx2f3un?%U_2lNqS^b^!*#p4&~qA$@#4AjuKDWC8S`UyGlGqmI%0TBKv zhz7IJKZY0i8RhX`q~t#fNWz3+d<{hNC!%%!f0fL{k^B~^@i)^36EyL!LTUa&h>-8> z>~G>K{y0qITiAsaU?8926PQo=1JIrSfX4nj^u)Ju62E{dz6UV!uR|o5kNu}HK{x+o ze8!)OefS+1f+_f3JkP(Tv403}_&Xp;{=y)bKe6hI`4~EpKE-$kcHzBsN(4k@-=gn( z>n9O6Rn{#o;H;q${wkwnUz~MfgqKPUd12H#A;MWDmFzoe{V>8>C5XH*VjUM@s$xg> z9kIS2p{sI=yfADX9igtWkL){a9T}mfvW~nkWE~Nqs4}|XJ7gUeA+1ugurO#1i4a#w zUGN>W4vE;K611=|U>zL6r((C@J79e~VjIo+mP6s`6lLCtR)>gF{zrOKS_Q-AnIfhp zm^SfO_$sKL22+?{TITTecOSJW%cx{hUef=@6eZZ{Z$z*}1QUOgY5Nb)&mSSre|(wn z5yktPk?A91_?I?dj`Sv+@^5o+=bg|@K{yu5h0Uk7{ux3Fy{QesYZA5k$L$)9Wd}*m zpPR$Xmi~?C&sbzel+-CBMb|F}3llHq1NyjS04hI3hE{RKoXJ!WUszz7k%B>8|51 z>#pO=NI~DxmCOhsf8PKpe`-vi|6ea7%&Px+(VSN7If@a%8WBNM>11Y@O88Do5W-W9 z2(>`fPD(x|f*6=hvPLjPOilb5Ht~Htd`nRTI2o(ukcYUOEU~G3mq37gJrq zD<-wrX`dwl@QO(nmgqw@1Fx8%v9qKlW$+3~KBg8ZglD*EVrH01_+Crc+o_%%XC#$x zZiT1kQ50F(hJQ^Z%lxlyB7SGklYDdQzi&jBuQaX#$fW;O4g61g31IHQe`^n(8JdUb zMS{5QD=s07-I;C_5I?&O_z!Gk@5BGsq81KzGIyo_yj|(PSI8u$*94{)iLW`)U~Vyg zZHt-dR)V?3yz&+^)2#$vxzj(JWJJivSR-nI5kx_zfT@q4%z;#3zClZ@6Xx;X1@ri? z-PdPU>_6@%0Ydy&UvmF@!3mi9!2g1_VFsmO#^m@)ND^jH3T8}>uZ1LG2Bl!eqEoX;hQ?g_`npeE@H%BaD^eIcAf<|@cBFj;Sb@b!k=1EYsNt0p zN+S)98kbrrfh~yBLQ#V&CyQCMOw<@`#f0oki${&ZR!Y9=vtj^7!P8!#CRa?{7x-vF zsDYJ}#ROU^>Vx|t1>KCuYACWX=B7T`o~t|xykl3{85Ea$?pM>P`vh!>u5K zpn0HDDi#6Ag@9@D;A}PScLGHf>@+zX?Gu0;niYT&76NKyO?lhajb-UifUW|&I&UQ6f#n&G{3x}KXeGfX9XpCz#6$Q5E*fNFSVhN*<_ zw1jq`E?zT7?m32;VJhMKEa85qc#fe7tkB#M@do&ecn;4LF!k}t9O6353Ng=!k~uz( z2z9)f3E>TpDPn5k&#;NNCWKC4ac9|cOb*d4$Hy6Aif4+Ln)owpg5-cWjaN1yv;reK z{R=TEM8O=Uh^dJ`#U`?d<~bxMgdrYbLg)a(I{lwxm?EYo{tTN?#~;ioF2uY6GBZpi ze5WNm$8-Ws@vb?=%nVZr-)9M}KqmdKYTz_pAjhW=(+Xr}m`eCQOCUKSwD2GkLK_g; zNh!pn5mj=SqQ8SCkQR630I5N!5OE3EhJ}K&zw%Q#eS!6Ie{5 z`oX32%k&XiBQ=g9Mf#n9hxBcdBYjCE3JNhJnc&k?-Z*L|OMv^uO-T-zy(+uqvw^S` zv$tH`cUcfvA~YFH`Irbzew)Dk^b)!!eTY^|ji3mTR40EYU^Fq9qT0`BCkKhq;7Y2{ zu<0=J+wms(u36?~_qXJehZk=Y~I^(LNkS+F{ z0}?Ec009N8?%b_S!X?I4vXB-#&K?Pt1i-}tR%dRVCgF7Bsw0qlcATveEb#!P0#x6e0R|!M3Z8$?DSRw)Za8@;LojPGw=bQa50}% zoLi?_INhjfJLH}fr;Y?mC_pKnbw77&mGA?js;v-hE6%eLEFl2?eAYeOI#t4VjH1J%1Xe40B`c@c5;hV3R@eAY=#6_MoLP+Zv)_Y zbzIyTuY`4tL^eW{EF#?cj>wgP6mByVSqxROh#VD%2LPgAb<oZ*3K_(m>BK1fj)JcwR2z(xt< zoZyaW_*$-{JdmU=cmQ2!fDIAGIl?v4@Ks!pJdl7cxDV}RfW0e>bATU8!&h)6<$|{9 zf_u?W1FWAg&K|xs4PVLy$px*}1;0lxUc@4Wadz;jRD2OvQVxi$1MWr_Uc|Zz<80wg zsrWoDNDhdo1MWn7UBuc7<80u#RD3p9(sNL=4!9i+y@)jz##zGyQ}LNxkmsOs9dH|Z z@dDOJ7-t1{OvR^iC1r!Mbigg>!V6d(Vca#iMk+pu3z7|r(*eIhdtJbu6UJSIA4kf{#%HQGxbD=LgLgX2>0!CXl=kfsi}8V%LQ?ia>gfd{7Gv0M-w zNKprjM=$DO`Gj$%aK{w<9j>HIkfaXy6}nIlyImM}8Lp9nzr_W~1PSPXU!uMAu$zQ& zCh$Wkcwerh4A3?ma2Xn^hg~a-y9D2wg8z{Vk^x$+1Ac*C)Wy<;aK`Ydr+9C!q;wEj z8(fSo)WyyU;f&x-Pw@yYNIHn94K74`>0-x(aE5T)Q@jgTQW~gP8=Q}Z>S70ka0c+e zr+5c0NE)bI8=Q+?)WLQO;V!}*pW|G(8I{Z*F9?q5Y1hh>X9E*l(Vf}<~=iytE@d{j!C!p2Z;7901O)OFf zrv{%&!pm_bC4$IW;3#yVCe~F5cMjf^gg?p!NdytKz!7LKO{}dDP8E(z!XM^JN&q!$ zfy2;HO{}>PP6ZyAgqPrgB!J4bzz@)i8dxJC+*!C|68<1pQamV23mk$j)WGTp;m*J{ zlJF2NNIWP`3w$5#rGY&sggXsCl!V{MmGl@Cqy-K{Lp88ULby}#tx5R3T#(10>ssJ@ z=tXrbObDk8pL&Ad&6N}fa?%3dMHi}LrG;=x@TMpD9bAw&kf|0p0PUrY6&1q4;kYMw zF0Q0lkfs(GjfSdY_Y2`p!ULb+w{SsXL5f=7o9M;!SUw?~BHZx_ej`^>3`kN7?1wHq zkKHbWI|0{tg5SUei2(^{fp4I_&SN(T;S}J9p5R%yk{*G!X@Pyv(DT@}Lb&7btxxbP zxgd`~tF^#CpcmD!bO=r!K9z{4aVABB$eLgzx=;-}3&FwQO^Nt zG{LUu!gJUb2<{kMBN0Eu35f*7X@Z^6UgxlN5ZqDtp+tN?XHozZJD^r9-Z7=n|5PbJ{HIFrIbPMTm_bfGHtIRqySZ%V+w7v91uDD7+~ipT`La z1rasC257IdSX&70ARHHu&z`IHmX3y=#hOEKBJjX?dY>MhdiHQHFZ@B=O-D|JyP#L< zH;$m!D{px(*t-kv9(U80v-A$=x%v%hG+t>-n_%xwxL(}NYn%#qKndzMq|nYv*jmBB z9dOaOQ)ZkG13*~yJ_)px61GAxkO#gg?vx4VjsVb)>V0D9X*jk>FpwKQ7JJH&^Wklf zgL>aV^fNd%TQG1tye{^XKIe|xAQSaI;SXy0y#(6Kpge;{*f~vizgY28Y zVNx6Rt6n}GbiN53D768s+IuQUya|k!+5l3$d@5*j6WB*;!=6cDZRDeBJ55I6qA|lx zd-c1GDl!e7FC12vZXA)GdkmXsKYnd>+(sCd19qDO=F0&?aljB9FeeU}4F}AE17^Yj zyT}34=76bjz)o|(6ggmW955LUm;?t*m;<(t1IEt*+ra_b#sS;N0b9obTgmb4h<)$z zo2z$i(mFEn{6TifQ)oW=KP(%Cuww-6+w^ss$Hg~ zICJ(+wq1(1>37l})) zo!jpsiFjG`$g&ai=jzJbwijnjMEZ{ZPSV{Ef2iU=Eb-sI;PpuUk;O}-b+Ynp^QCnh zuE-4X*Cst$C~fCxL1xTe&*o2|T^e0yC9g;QU8L1-Iiyh;^Ve(nt?8G@>%!&R7E1^I zPe?EVhFP$CzMn23CWaxLOErhkj%?ZD5v5RlBxWJa`2y!O!Y_fdsQ4% z8*NR$LOu{K|8B8rKusEjoBvMw`kI)ndam}|8#fwc6Kzes(s7^z_Uz05(2Ve9vp|{N>Tx*jWB#JRdj()V8 zr~Sv6-O-4?niXfg8SCDPTE ze%miue$T$^;LoswCo7oy3W+Q{7k-FY&K^_@h)JU|=Wo{yxQh7$R?!nI{!!xS$h*{QCWd}U==#BHhXWieic0jtg4u~3%iEbnajVvR9|J25zkd*SoA>R{P)xUj0&0; zuhOMZVe;{jP3NvAKGST6jNW%WBm4dGTh#`pi7#I6jOYE?xqMi08YX?1IK1#jVBEYi z88g6ND<6iEVp!4}7rzt!R{E$X3{Xl?;rukY1R;+_2i`-8FT0)=2m_u{Pp@M zi5|0p)yjo=N>MDw8aw0il$|gGe7}i6r8IljTI5$cV}|&elt?k+=f!GK zS7F~x<$u&u=rm_zG9ir7;`Q}LEMUA0Q~S;F_YZ{Z-&{l>z0v@a$L9q6PWkJZ2g4Gr zrfJ^^-}Uj2Glyq0mcxxLajD7|G0*vI8%O}3i z+`dm-{{i!MSoMP7a9j!o_fG*R`7Xu~U5x_#^rJoStB)LC0>IB)*3gW{fZw_drIvI8 zKX@rd`2`Zd(3#Op16ck#Kou}LXoN9_DJmnV;61_8;>Vz7rr{&30{^bJ0R_RMDbLh#nH#j27%YB!v zl11Ah15*lHOXV3c{X2t&g~8#tbWAbqJNT}Izl9|*zYqDtF3WrY8y&`YvzR!rtzFrY zKgRpU?3V9fNePvg_^*4j&q}m?2fs-Pz>D={y2easa)}l#{)5=ZPdEOJbnLrIznjx< zGN*6P@0LE}qjS_UV&>(g82$r{hDH7`h9Vhd`7@$Z7xQDkE^lW_W0$fU|33*BP#n4{ zUHDtQzk`3c5>SQ|hL^lN1Nh-nV)P@*6$-XX*_HpD_ZPv^_)+i1Pw@7lOmHce3eIWl?q{w_`U*+Eg97XocNf0Xo_$86A zzk`305}wU9_A1}zKZyJe{y&vKFg{3^r;=w%7{r4`qzVkTywRfjBtea_rYP|hW~cnM z#V3<-|0uu!{sv{HM2m6uzh=>@^3t@nY13xR{|ca5H3R>{f*ON@LNaJ#FBtRG#pv-Z z3~D*0q!NXi3m+$-7{m!ebUL+cxOgI84ETD@yto^UXp>n8!lmXE~fXo_z=wT$H5lM`Js&bEA@78jk2=a!EtGEjkw@m#)Gp9RHt zjIE1%fHcNdAU0BLzX+xHIvF2fc=r@^ElD`eMnKI`$|(GFYFT+bP@RLZdnfluj{{{Q z*VDaI4oaudy2^=TON2QJnRy0ow0$Ifb=Y1G$bg_s6neVh<6c9jp0^|(+bei<%MC~h3-1k690Q!$7MzUu3KjGbEZd^7&0sP z60M>%$V#3Z0F6Avsi0`M1^)&UIvD>KC-9146s&^3tl zq`i2>d;WVkdX_GxHuvqli(Ja_ioB}J=U;y^0w}h`zx)}iI;$QFBFJIsD?^!B|2c9ZlDtQlJ;YVg(11w3* zYoOp4s`z(CN64F7*nTuxKYij6cdzTTJj7jN;_-XSVnKKHiP%eyPO+DklB-j~rKRNH z6mw}Qxj4mNT1rk%ahHI!r<0x4$LGc|8j>M{GT8F2GI%Q`-H$4~A5y{0C?Pg^$l2f(=S=iBcemhfi( zCj)boTJF*RBMm@MCE(PA3N4_nGocgG*lYFXEKP(0N zUlcSl1Kac8vMfZIk zo-gx$46Hm~edInh?0ZrS?6J3z4I#S_%?Sl&R`ycHoQ1&5N<8nVy7*EmII1ZQq>S9W z^BGGiKTjiLDdpvul?OGNEh4c4p_p_W<&^T|qodvF--e4=uqN<>|M+W|*;AWQc zT7}X_Kg3-vx;MC7T`Kq1W&C*NMC>vyWR5Pt1BFbZIW>La!m?nujD8aGiyWRA0fm^{ zTG)U>OSoyNWla3XnPHg2F(5C7X*GYfIG@`#gBwg0@N&xtKg67e{yEn13+?~CrWS!gF6*g|>HdJF{RvF}1ZU`T{Agf~ zN0-K9phy1%raw9SA7|G3$s;Y62D@)ZU#_3@CHrmZi^v>~Pl{auHkQ66hx`=44l&mg zz$o+!!@?i8lK*7J!a&o#Pp<^#N?)`>9Iy|^R8tFc{dH1o1a;z)-O|$aFAf=3J`Is0 z-_}T^!CdD6{pa#+4McZ8#R=k95BO$9=LdaHh;e$v*eWxEd=9XvvECiH?ZeF?#=2kT z0PB7rnafzPngeOO3^(g`AnC~Hvg!ayT1JLd8c1cekM;@_03M()jmFnVaClj; z2DYDsO!*Rr7B-;J61QrPn1*Gnztl1&{=>{ZI54AS4EG0zC946r{iWjk)HXTha11n9 zjv1DKhW-WG`AMA5s(zBSv@Ha5Wcs(&k=K~3Dxm)kGv|AN;y#P~*P0rcIp6a&dNR*+ z|3BG&f1!tdV{mxhJEz~r=<(*z)%e)#B*$q`I5`>P#bdAI6>m=<&6;oFH{ zpWcJ#HpTcsrC%-pT|b{z6k06WH97Jk*B{v{6^T%IO0vKfBz$NptA`->f* zi#gi^)I?>@_P&|;)xumA0^{O07Vkfq{WSCC_t$hspA_yJMw>kp23fl9(wBCsqBwbL zmv*X*I8|)VajF36wQU{eYaof(igJnoDaAI8GX+T2wzizsK)MkQyaC+I`8XVS19*?~ zemL+3@I2?UaNrGK11DY;__FF`slC&Aal!YN7sS>V?V0vzV8bm}8N+>L`TB&|AaZH_ z3EawO&Zz*2TG&o3KmJ)l{a-JRUur=N(3E>K01sxtVdnK8X4Js!6M)BZp8#V2HFA7z z&?6GlcmY*0Cs9Cqzp6ey%dzLPVl9@wIa^=!sC8+n325qbXa4!U@e8&8r+uh0qYfYt zbJp~WX7+U@8(?m~-Y{waz8GJ0c?A2|VKd)IhYfx7u*b}eq!VKQDe~9t!kwRjH1o~RSBi=3!@fTn@eD3yIdP2B zrAsg6l*#bYrI&I+;oDf2QdYPm%Tl@>&c?Eoo`wsvET!=9T`Wtf#~hgCtYH~42PQdS zmVR?!lEcX|Y7R_tq*xp^Id85~7w_cdWM@4R*21>BNW5VwOW|JYVe)7x(-UK#E<3)I ze&Mz~`_}H33Uz3kjHL#U`tm^-h;UqKU0AjsjzByeI z?HiElr)CnFYcW8q01|VH8|c%&Ojkci^^@{ny<7T_73h2;=9=tZ`u@-Dwg64B06x$Pc2%f zl0xi;^Q6@~6|?-DX?Nwa(%^Y8i*(eB#K z^RN~dD8jC{sLKWB+V0cp_H;nItFAtcI^K4a4_lagm7!CvPC@q@Tr?)ZBFDB}%*#v< zK}2|`(8&WuS^g{ZBsv0^i zN|IQgWrs9;SeWHT@}~>*o$^2oD#K+lN5H+?6{# zipdc3re`R-VXwPZuk-9kP9l^(FHh*Zf^_O0B8AKaU3c1RoJ8KO%2qEsjygr{?7qF_ zim8+*M$v7y=CuyrY3UGQF{<5}Bb1e=BWB0@BRm6(GL0-K`L&>W-Z&S=)dcc8TWhnh zqhiE5qYjof^&E!zOJY{eI+vW$V?og=he;o#)(Uizj}cmebuR>LAtO!XqeqcGI+ch`PU7^3ah+2a>x5D zawOTZyN!r2teGY3a2dj(x^kCF&3u>r9xo<;&> zU{UWwShoFa$~NNMFw!t6T!K2&)`pTXA}@j~YM!RYxfs#{)y=w4E9jl;TJyB~ka-Sp zFE#HY7sAuBEPAsjReDYM*RDQBNfx__}Y)K^1F>vylo$-9GrW1X+ZnAhs?knlKfqsWb4vZBv$tppI_nlP@ zIW^rWQb0nCx1QoqVr~YWFM74(nUPauF!n>ytD`x^Y1zUTJMMT{iz%h&22MHjccCU1 zGoLer3}5Yj4uVl(t@7rgw9sr4ac6XHi^#Cc#_pFlw-gRgoG~c_GwThVswW)|TH{>I z>D6b!r!-2{#T`7}iwI zGcH86dPVUTj!WoY+`(_%vSp@6qDN5{0xr5mOGVM6*Pxsnqj5ulRL>W$b;Hp1CnwQAp z7}#ae?bT)HwNp46V|5M@KUdZ1j#Rswe)t1Ssgqm^_nVjJT??^dJWhIS^R0cIP9lxF*EI^wdxix&QXJ<~16>P%6){tT{oN!PdT$11?3P?3VPaCtGB;m<%0PZo~KXSt`0q`wfRf=3tMBz6x=cE~1BBQ!d? zgyn7N$ddYTOmKFGW9m%tY_9kG$lP=r3fj$m-FSRt(TO3Hy{kagJ%!SmPANsWK?@@a zBZ@@C`up2L-E_x7yJlRWM^K~2IG9d?lNmCjH>`u$N}pkv#@7YYZjX)!9}pJP*{@@+ zvt6gfyWjnQf-z15rQfJLpQ|%YAvaS<%KY_+6kc!Wu(zo%$*Uxj7QT^+oO8cLr5w0R zA-~s#w)JJ2@^=R_+^xrRa#5j}U;@Ub#CXwlhrCGWc&O(@v0+)QR?0)NOn?a&xOOvbw;fUY+%8s1NR z>FFT+=mz8f>semk3( zNxfj~O){;VX>)jUbyp58XI(_-ODZXfM10QJOtCvClfI}pev3dCW*Mu2u_KG>=!mPd z)Qg?n+kOaVz;29X8vCH+W@RHN`_t+Rce!++JgX)}sI@n?cg+%rlN}uGc4(Ri)nKsb zR`?y-AvrsWF}=Nf__>@>PEU3Rxo8&IM^2)AAP>K#7Mi2n?$gKadGC5vN${Htq23-@ zP~$+<$?smo)PEII(U`PXu1^ZE4 zHh(2;$8a2pltnBczaQS^Ff5QmT`*o(gvoiS?;sFrT<@c1*F)4c8ZvKkFPxxRCBvrp zZWNz@BPTrZz7hu)+dVF9@SCvBOA z-(Ej&qwimF9G~ zAZURzC<&jewp%_ojJ(r(;+ALC!2dQA7Z=`^0= zZ?|O;-CEz;Tpt&KAy!t9JGYF>!6N4g2rCL<*%y_q%@+JhqO z78Z8sbg>x;)i+K&ZR}_pKetby)t*t?SMF)WsH>~AcN={!e?W|1H_-HU^TW-kya^;< zEXBysILDi3K?+0ns%Wnt#hA{I9<7q@7IXAkh;1V&jMXem!Mv!%kc*=X#xA$K=!6); zCO4G)E!wd?^vu*~|4P*1MQ>Vu@6n1yQ!h4{%SLDFF2=BwbK)&jO>8jQ|ot4&$$*;B@!4CghEN3irLJ?0ilV>n+{R4(IjE{XLUdJ&XVL_AI>|f zTPLVYdwA1|a~eT&*w_2AVC|`M-Iyv4w~E#c$jDaX{cMchGKR~}MTFy|gDQ0_fO;sz z+ncCc?}Fk=C-0scpiM%qN0;F2&uwSqn#<91*BDc0h!2dpN9%LsNIkSeCUa8!bHt}? zq@l(B;{MstlZILKVlv6Hcp9&td`a0%Gwt-ELpQHNky5}N#o{|9yFm=181F#ZDko)u zJPt@6%1V$j9?*X-$x((sua?^W8a|qxli)pRbWyq$#wfDrVBjd@v~)K{H{-+-um?cX zEC8+o;PMi<41lQ_09-^Nr{xI;I!Ku4sDA!&1YV5Rin5bIEgHH0kTqhw@$k+J@e4*q zh0(}&WZcLBBCaIMti)cFDq|FSaMqVV^eS#NV|YyAoRq_}%ow{RYmRvqP9HXZcNJTb zHAlM4*hIsv4lv`EuQ4Sw*lrG`z=^0?#_YPW){Q6`8${xy`y|zEO$e0Z`%#sNU)JO|OVzQE+P4y}nLY<=) z(@4*v_&esekd*oO$FFCjMo8_%+!V%#Q8D3~&|q)AS(pB>7v5o2v|4mxSaiq&5;2bH zC(f2TdX+%;&!P7QPF)(JaUS?Oqm(i`A7>s?|nCeOUZA2L~vE72co zzS)sZRGKqReM3ko%z1uyJT!ALBfu_%F_Ai-*&nTgbP}Gcb)@PU5jy5-chB+H*p&#* zIuPeGXNTC#P{^v0=Jvx9Gw#nvSV`HY+h2MQvE1&?_TQoq81 z$|-T4=MRrU?$b!JO_G{NM^SOs#;_tUM4o$|hrVtXeU2ukH(FB=iewBa4=8ns-pOW| z#d#0aj}qcq=rYLJe(lz7C8|@c)Py%qg!J;@tk`pr0>cZl{0heg9LBll_)g2DmUJh! zw-D#-Wa%};l3A2KJ$<&GM!P?YT7CZPy7l%$dxX!dTeTt%vu?%tv#Zyzu2{c*{R-X0 zV>%51q1Id0tynR*dBqC(6)RSl+S!O%nweNTTiTd7TA4XJnO-%sH8m5vW@2h(YUbo5 zcGbq-T1?x?#KKI~#2$PeI4in1I@vjjnp&GUIi>2T+Vj5MLN~W~CXlK$HF9Rlr445< znWVqM>6C4~(YE(hAeVH=9qV`db&xMD-9+vbh3T7H*sNYTwQimvxp~#;7d))SDqQE_ zPqm)(g`8y#T)j@HN}}_9NW|6?@zh#Nw`YM*3(ts>7iK(ZE+u2*GvV^c#SX%1o{Ii8 zO?s9w^_N$z&d`oHeY{k^g;Psp-=nqL{AKbta88P=$#Ly#nOlTp4H&FaJW}7x2KBxX zuRKrY5%cnmubd~ZQH#DZ^mK84ry|m@LdJbD0iC#Y(6=@d8u=n;!X^rtxP#C?=~+7m z!^oeXROFHmy!SwwJ7p55>nOM3LW_flUE!sWGdibFOg(U^n>y2SW3JYh%u|{TqTbkGes!EFZF<9IB@4E+8Qm7thsIM@TqXIO!za&kRNT zzP`1U#koynwsqBTde4ZNvz@ozs8b%Ua=UW%S#ruf+}7u>xvpAibx12)kU0wG9dbtI zw)WCv4L|UQh<9Fc=2Z5I9&Mu+ZJm9*>oDIDh{6sx^7e`&X0j&km9_J$u4-u7)`>(& z$%T_*GsO)d71xSGOfX3=(LP#MNXF^q#-A009HA}eNgPY zAm_1BEpFZVrat|BHQej9qFWHR^{iFfjcz#~9JorDN>B-TcTd_nW)wF1!1e5NyMj$( zR}e9&chRatDGfQTS(%bKmi={2w{G2=y((+5R(!Poa@5_N?V%u5PJ!pO#j-@t=UK2(VDUY+UV-s(TQ}K6Sq~rMRy2Ae9)M+Ra5D^jV}C$NN`?3YZ6O7{O_J*LRsV2E zW&r%6Ht%TY-g!4u>JD)y@)h;m zd-BfW7i?nh@7;KP2O&QYRX?zrB!+vZI@5XV{Bt%0tiOJejjQC%#MS|+!ho|{g)p>t zaISZXm#=8`C4S?In+r7$g6!)PBt;d{E2dv8?hsbaaqML@`r786JWM|$DD6=bYlv{- zwtXTY{K4G(?pvBbTzPr8N9Gg8;2Q;wWAlf1Tia+FYP(?sEu&C(`dhXi6O0m0~Zkqx%yt=LiOmcA%^^SB`#HBe=V-bN;Z)%U%AE{-vj6Hf! z+V!A-dCGC=ZXd_$dpZrz0*GhBMiLEz_^nLGSX&?<>SeE*2-v4T&pcL$kKtV%+ z@o}FOy}FN@8p@71t|8qyb;RQ^@9u3!P7McvpWe*A`*co3H7Uk-LjTaE)=1tCyM{B@ zT{k2na?ut*$<`xZQU} zTy~$Xy^~zoI?F)$&Wsh3yY}FB?OD9OnVz@Or%cV{=EP>RAClTEZVGAHvmJ0gT($RI zNX**|%_|)9#tnU_I8FNcqA3*U{@Bw2oY-r#0DfXa$Bzvc%LX@9=VXhT$8R`z2I>>~ zq<7!Mp2$$-!rc6(Nl`z}o!jg~8`{h7BX)FbjI&_r>RsP;qCvo-C;YTlzE1pWoiiKn zOAn&&Uc3*s)qUa%iR97F2TARC>UU_nUs)4G{l zxWY@$e}7H=mTSUv!@d%(-PO|5LPg^DJ3uU5TzQ>+_&tPaUCo)@Y(iyux3n9acGPBi z>)vB0br>lcFx-tPZ;$!YEVaiTX^kAu!ENif|KqS0gy=k{CXh~P`C zSeml4Tc#>b_4mHbAU@!>iH_%;0>MKvpsMfIu%A)b3Evs?#=n_|(^)Nu!RcrDT2LbY zP-jLsgKcT_{rMRCucNQOjN@!NFzV8Qe@A}v=&N)VZfkDmXlmvNmRKHkg9B7q%fvVP z8b1rWz~Z@vJM9iy=HB*`FMSW(bQm!XJEbWX!N&1E=H*Sc);d#jcT6S4tBZey4v(Y# z-Ia>Y8Ldj{7c4LHBzC-vNTt-K+?N>&dMRjQXoB0FohRpTlKrfi6*lONjZsZPY)g_) z07(6eHpKB9=M4Ndzjgj49-Dv|^VE2!KAv6(uYx2d>!#>Z`? z2YuZjb=SfjFMn@Vhq<*&O8I%y|5YiQHK6fIt5&SoxOT;gW4~D`igwm;R}*VRJ6mTn zcjslbL~3=|HXq+CcdnDC;M~OvY5if_Pl!Gf-OKWLg+}=H4UiX#PYYx3=Jey=l)DZtSQ%VL&JPow76rR5Q6Y?Jav} z45=jHs-nVJ9#OV8-ev+4LZU6h;F# zJ+A@5d^^Ij4Oj<~+C5b=HaVydKNg*rSJ9Ym9}#3>e?Q=|eZDSVu%Pg)1wa^WsJ&q@A8g*mF z4f=)O)SKoTSc|-p8Zh?1p%lDs@5+)>&O=*Ong-5o(>mZhXuA7YT8~GHDVNOFwj*O|7xx6>3TLMfVJAJS+PRy zH(Too6DPBund>J%j=XQEwoQT4j}UV!q!XPjh>f67n-rAUzG;Cl_>>L zJrA+pIB2-H)=+fz2Ky>azGq%eL9#YibqLvD#d@8ny|X8eJh$>!+X0eHQeAtH_;#rO zc&V#dQdi39l4`3>ci6W>OCwdsI?xRES?v?mYQt_1SS?Oz6+Rd1%TMj*NS!=BFq;=N z*?FGthEsu8wB~GtFt1ofS7a>vE+fOr-Mig3zAin~fLXbktS6pvlge%?^wKf6z%;n< z%tfgdo1r&O#pCm9NB5|n71r0lrS!!FD(hX+OHj=`_l|SeT~nmsons2>niQl7UywE} zxrt-d4sme7;rmtv8aLR7ocdcPADGZfVm@5dKTs0vdZp3(uo6qWg~mnStOWg-HNhWh zysln1nS?gxlUi*;Wh1jr_$$|>={P0ldSOM4+)v=Hs$rXqW7d>MKH3rxXvHHC86$nl zbnVeVi3QEqz1>-jge6=4xyAmgEniLaQr`{O@(Gp|E6)68TRv&xY;qZ}=hMK-5Uh07 z+DysL(Zq5;MAQE%R5pyMOsgfTR63*{&0I8g*So<?Q2fac ztF*UCg3s9`;u6<`Vdyhj!Fm%by*PXCM&3^z@;TY{y3^q ztPwxHFXmkXZRa}VDz_!SY=@&;E0@EFduh~oAr~(A`1(lie%~ms+JC2J4JnIK3ZPS3FwqZ=?rFAr}H8WP2=Kdkr(i`od4sc89L^i$60XQsP9rW#C70zM_L+*V<7W z>EA)-X*xA_iaMooaAS6gc;N%gEqYy@rMx5jgV|6YDe+7c%eIgU)@*A<_3(S`9=&RS zVxB%eOC|M~+X))vW8Wg~i(e6{4heo&YS~~_+a%j^US}snNciEiVJ!jLrQB2Rq|J7; zv(0XiDsa6SU?_hBdG9#)wg;OY>>eE0?l|Bn5@yKx1S#elbMx%+tq0Y2g`5mc!YWO2 z8SKt+u+$}NAK}M)$?vaEj?IhQd|~KEiM{2A4dhp+M6X3XSj(Lh5NtE|**kaMmav!I5*I6*d-lrC)4IHrs-0#SB^&;@lg-_yGUk|bq@SDK zzZ&JP_#goUV3ehRQNn+-Q98JoIU>Z=mvW|EYS_5}rs-_Tv8br?v z`v?l{xpo$-y^`y!xZlegk1u8TUC%MuJ@mpgXUtX|iMD(*So+jR%3VB)#%)B~@am>r z$cabxv7Qeo-Gz1pYInE{nM}wTU$OuEso3428o2?&kSo~cnwz&8!R_|iyG-|6Z)@eb zkQkg8yTk9Kbk55c7PJF_F;AaB^UgmNsd>%b9#4==&j>d3yQ1aaI8Dp`4{TP zg=GR)UO!Ai9D3ja=Q-opC43B7A$qn)sUN+*H203;KiS5$A4(AzjmlC zgL~$f^6Z4s%T?-L7cg@752m4AOj<$x?Qkw7UjyP=2Mvkmp3xR}T~GE6>vitqR9u)q z$*p7Sd@{EE;T}=;+sZG)VMqtx9cPXbUc|n0-yMI`o*cVVQpO?jZo*?7=$5yug!)`e zsbMv(ZB+AjBz&Rv$EO$j_sNs|58BXVVuC_>hmN`hf#vPVKUjxKyZEH@7))*Q0y;{p?xg!q!jC zyu2`6aTU~{oIgAIsIEo5Xz&2xXp^Qmy-do1&Y}9!H#co%-Hh|gg$noGuWmiKp-@{T zP9Keyim- z4tC-0`vX6K0o&YHtHB3)E`1V|nHh44B`&(tw(?L$S1NV$oc^5e$!)YT%%SdgHpwA8 z&v4&3aFez&U1 ziYH4O4wk2V*hk=UJ-jV-O>xGw&%~k%Og_72Gr?XcA|_{#wGsO@v$0~S>g}o1+`P>h zt{Zjn2vdo%@eP<5yGmF_q@t(n`!R=Nog5wwM909GpP4(~ftSrbLvN<#Z1w0ywe}pj z;}3m0_P0_tl3(;iC9gYr$1^QP;I`cT1Ww#-&oEE&p&8D=L9NWNvYmX@8+r~PlRGnu9vQhAMC(5P(0SOa zr%mlH@Tt$;8x}jH#S%Jp=(K}k3$7NJ@aS|D>TqFrnn-IITV?i_9A3BX`HTy&D6c#-#BeHqahevxTWBKl;I%nu6a&~ELi-drVQ@~wP@ zCni%|0S(=%ow2(go(a*p;x#4No37S4dH5w>Wa}ZF#&Y(x0xFW%!M4OrdePHk(hs`T zLpD7027lljdF*Qi?W=}A*&phc>9=zu5qOm}cKD6ugY|Du*=^Ny_VXFK=eD*;>TXk1 zVIQAC<>ot0cR?Nl_WT?z{z&H3`{zFKuijDih1x7v?1kt(tIs(->wCS%(Bs{k z1>?8Hun)r=^p&)@g=3!FaRX;1=E`g9Mrysx%`MC+(I^IT_51C}G3Bi?8x39@Tzx&a z3zI$(2_gtP9zY{jyJ0mL+d1uw2z6nX~E^EI(2OFmE=vMFAo|0DC^mo6Kp_u z(-&iCrdP*Vh;tFl72v4pl;4iKkl}lAbITSnUhb3G*WUG`O=%k2=x42h`2A;P&!{wG zIQQli^15HW6-1x!W}9-1IeOXeNvQl*mWQH;GNi6=LGRimCK2GpGf{J9P^)sSVtpFL zr&OgNZK9ReD3!I0U|nWBDD+h6kmilZ3y2ae5C;&GB>UceNNd!zvoG_OMIn$-r?(S!$vuMqcx&tyadGq zT;G@;QGOu%&_1*}c;*^X!MaVT>1?WvgW}4exzd-4&_i1kU%lM_T*cb-^-EK@tGJR4 z>|uAcjoOE{!&rFT%fl++b6Y(3n{#sOUg1|$S-i2@uR_gA$1mP?g$_dd25FA3Y*ceg zJEC>+z6t%cVJvY+d8e|+CXvLla}8w`Z3c*EbZ8DPOy43G;gKV~TAjuL`@nZ>^|NaV zirI1-O05ZTujHvviRomjWm@t64F-!klU1)~4L-cxwEJ8tUYM(=$;G)yH6b-_&i0Vs z;~Ycmjk?IlovJ1dk&6=0L8wdW+&Tv7#l7y1d2{!sjn;0r4A_;SgL668VcqY#eoXEh zA~vLHuxE8iu@*hLK8@43G2JrTxU<~i=B1mDcUBcW+w^i<7O}jY{}O8N6k6ItT;A4$vrBt@sNk2+qRRA({VbsZ5tii>^Lj7&6j=88Tajd-!JdnzhI3uM$K7s z&Z_!+;XMO|e+$X6YQULGyPA9P=4EZa@yyg3jicU0t9Sk#^Z#UnY$gcHNnjwLhc8C>|2N|QQzx?gt63H$NZWrUN5h{9 zoQ}vcIAaZTHFH36zZj7TRlv2vBc$SBkqb}qTBn@IwGn~ln4JCv80inS!&Pk;{ zN!w5T7wGn<$JL}0q118qx`!JEiw^$!cjdv(lU_Pi&ih4n|2ZhO+Rj&?U!Ck|x6^Mw?Yo~p z(%#>8Cn10+7_+`(4=*|p2PAOIks+y{qRQD1U}dWIw~^jZmiBm}QI=~84U6LK!e)R1 z*4ERIei(RPoZo0lNXd)?^w-l#G{c7?#q>)mvL;)Uo5` zYpM3s@>D63ogHD0L`&hIB|g=vQwth;=TSwz&b43hC%}YpteWO6zCTD%!Kd8FQ~Drq zJ4Q!i=fjr>bR4~!Y(ms-qvbnd>VwiG6Tw2rZ<|dOio(+aTRV7PH;kDqja${oeWV zb(#{G7)(?bTBnN+S!~APIjC_jrZQBI*DiPs$qScLl{svvRE%I8(s;`=k?&X9&nL=) zH{mm-qZPWbtZDQT$W)U^4_IqL^5-`e9K!(;hBrDe+tDkc+>+gY0$DCzecDvFOcpC% zlT+(ePv<7>aMj4ZH5+zdzhE`a0*B)H5n1O3Lm3$uAS-|S(n&G(0V0^p4rt4;xC6gp zp!)|tTd`ram$?x6BIVjjDcqdXHBYs~dC)x^ba~?e{TwkXQ`+0E~`$a-Tk!XlX*caE(I?k$vn z={A#EsA3_Hw47~7Q57wQ>{XX>md!q`R}WpEV}-iMSFaL%5w}j=D8QtjEA5SCWQMJY zRGbcnXp5fz8FOws^q_nu=n*Eg8hat_5)3noWW-l@I62v#B<;N@%)cbWkKW7T3P(~K z3r0Yq?GyR5q3WV4rZ&UJ_kzvcZw$L{k!+yR8;VAH`TZhC-#M{K(KGw)EnYj|nm6(L z^$`jA%qC%8m#CQcPoZ!(S=24A(4Fg5<{o@O#fyryz z&*DwXCIuGf4}uOF5ATcx_Uy-wkF#_a+6{a?;oUFR+hVu6oDbUxKAv|OfDjB_jnSQj zbOt*~Vd80};uUI92he!o`eM!FNsBzV{OtoJ3`Mhx$R2f^D&i|8u6_-saQuR#;%&#g#JN%<<>j6k|+nrCG)t)#qy2_>$*N z+<4P)Hw>=mQptK}GUj08>{MX-#;yb9;uXedoqU6|)7Lpd_&f3yGbzgQHKSF*md~Y< zw6*)HG1v@IMQsu=S|alGVl5Zb@xA8}m)8DxRSnqIkw244?(Pk3t5ST2k>GBau!WvI z3>imcgvrRappb^89Yu6$k>+M#fyEVjZ9X7YyP^{@A6hrlxaQ2{SA~B)`Z=oU6FTE! z-c;Xv9x5817{l=R+}Gy5sEzIxqYYoQS1OekH@n2WVTmNy(w9Ib;fJNjW?QGBvF{d= z=Qn+KmG!2sxRi_?OKMry)q5Wr6x942QTu%-C0(@5qPW+gm%YK7Y8k3OdI@`ygSCsa zvUNh1sud^HTKq(Dw&gL9@y^7yd>y)m&HjbQD!g)~Ho{0Up=xZ`!MDv&_s4XoM- z5gbi4s*KW5IHU*c{+?^q%R^_-`q&Q96b)@uRx)SoPo~ck!xh|XzjmfhaN_P+x z>p7296%$$pjcvV{;+O1RsEUkHRJO~9CUF9$QCJQrPXAt7dn};0&W#Qj3qPlA@p!|V zLBAI#^ctnlArr2H?!+16x3*ts_^y8LGL-9j1&Zt(OKY<1mdLc(D07AP^s|z)-;<(O zk)7V0TkisnD#08a zDbi2<_gveH{pqWnBTc6T+bq=O0_DdGr3MKp?l^g}`31|_c^6Rr0>4vrA?Mg`B2+C1 zM^L<0$&PQnlrFQYC^6sd^&YxF*F=XeNsk8T@`aQ@L-TfEd`^7)Ima-GQyf~S9W!vH z>{4C0aWCL6;=A_5!fp){JzVu^q_iHxE_;0AXA7-_!)N7ATteJupEC*wiJtHb|3F=G zA6tdGXGJAMh2kpKpyVu%YT!`Fzz?$z>FVv4H4i1cUL8^?dM-UuyfO3x@F+VV&vIXT zrbSCA=l6`^yBhJIPOV2R%IQjTF&5_8bA3F588U``D@kCva3QzXf+i{4pKTlQ3(GLP zzijR%`0U{cyP&u4QQt{}YcbS({}lV&1OUZRzTZG}IYw$hY2-D|Ehtogv5GWJD4pTP z|8e*Q*Eak3YKj$mouqsVJW%G$T?#7*bVd>nojvnoX!Ocjr{tbn^A+$BQWik6(9`gQ zFEFL|it!g${hg8i$yISi@D@8?8KxyX5Rl-1$W{MsQc0!kE$#j#Q8(1J)lt+@Kjfx< zK^eBPI#C5;qp^W85^f5KQd!Z0pa%;#xj=%#k|w0eAf6BEop(JLF{~~w^1CEq=&z~@ zo@m)_wX5mBy4$EfW}6U!w689d92idXzh?S!Z9PwAk6!w{-?8{_JoUi{yxdB{demXb zS76==_abU%8#=}edZJIG8(>cFiwuWhAVs0~^rS{OqM=aBFi0^T_o50E&>GRwGVDlr zC`Q5HlX8iSfo6afd3vn|McQN$Fjv5Ao zez}V)oyC!-APE)pHYdf0k;U{{$PPOmrRtXxmFbVb%#-9}Mq^Sc`-LU_T?AuJ>_+Jb zcAPQVIvLy&=~us-P7fdNiaV8%kSIgOj*5unVIhCj&$(tSIM;NljHQFe^xb5;l7z-u zvq2YYGu*|hYlWID)~f5|{=J=MiU}3jf}Za6@_odrV$;>1;2)5Cn7c?o_mIMO)ncX1 zIWe7l{8Aq1?&-<1NiorwlEacc%UH2Tvp@JOZ#b=0Fy#Z5a8C`rkP4+j<>~po2HoH{ z2hA_Eb@Tm)W6WIw-~v=(Vu~h@O5<#>cDh=XNI;ShbV#<@lzp$imoV9W54RRU^@*n* zQ>YRaL5U}F~DNv_IJqmC3^IkD2RqB6_QN_#B4OO0z& z*biu$AyB#wL|pxhQR5P`b$U8Vw=|g@Fv5s5L+{W$L*o49Y{UH~GdL{EpP66OmZkGM zRE*W~PdK7A19HDJg@-K4u!p1wqARvlt!_U&I`BeyYd*lVGk-@Ft_I+WW^d6N!J*n& z1hDNCv9%{l2g$fd>zgfQsumB=Xr5(4VM1;meJtJqjymMtEV6c8O0VC+pc6>gn0}nB z0q}KB?|{NpwHEJ)z!3n9-JXK&#Qdv%9yz^qbXc6b`ixl%6r^dv4Lb&MT(|Y~t_vY^ zzT~ut>+*@S&paP^oYvFw#aDF919J(_zF#tUogWtz(4pa*QmZ(q0k{Pg4yxKG19KO^lN3^5!rTfBmVf~Vrg(soA$WpVzj_<2RXgO%BEz_ zo#Wi{0l{PA*&`jIKTmaA4*c%kRfRo3_SwkQS`lSL4gbgkRtkrKC(_ac?N^UkL}PA z`>a>5XZAs{jb40BKHa4eRHc2#0%BGeADW{)={UmrEauwNIi+%|3%dQHfD&7yzBMMu_M*U3EOc0?&H~_?!aFEYi}Y>^T7;9~4o6aA z3s-y1@Nw+(M|wjRa5Pcv=+4I{xl9|Bl|6^-%ZjZs3*Y)iPeN%^Z127ar`C>`q!(iP z?4kJxt8V26&nDhS{vpj<*Ge83_WAV+h=YLYK~EeNkrVbC@})8i>Bj?DOyH#?XbC4K z`9E!@O=c8Yg;mZ3y57T?N||ng4Db>2@Ho96lX9kMwj>JnTeb`I&9>JsEY4&VRflHN zy0T?DmX5C~9~Gt2-*I708PV{y%uoxw_pZgSq~ET|?Ak_P$eV@!YjgMSUE`nqggSHd zq3&x=0EGOX&&B>)KTtNcF*SDi@6Ph?A%WVfH>x?>4+xJo;_jKoBZf`m!Zm0Aur^MJ;yxZe zKGSC=4I8A5U=8x!e|436KJzIbg15&ZZy@}kS87^>)nCgFLqQOZOF^KVC5B5uC0C6O z-L#oCkoQpv(_=R}blJl&YzkMZaQY}yo|<}z`(&DWOINO*KScV$J%1?ox9mMfgxZha z@?h&NUg@H3q4aMpUx5NXYQJ^0mIB24ItzE|8TD@sAb!w}qrnLv4GSds=OpZR(lU(Q z)JNom-<{%viE@Y0)W5fB_=te%DgL#Msd8Nm!j~zPwo)A&H=f1?ej#$Lvd*2qLkC_B zScxh#Xyjrx&itCqldz%T*4JMzsEEfUwCsc%kY+b<_W>kmvtZ>i#QM_Zuve6elOhKv zRVHAkFzb|=SW;lJr!rPnwsR|GjoOTtpF#u@ha(0l$)p`~ z;Xcfw)0^OcnM)o-HC6$eVRW$wdrP2d@rXE%94~i^l%@Y^b z!2RiZV{~SRWbP)wdJn?9S^3_~v2;7M>BG_rDKPg8Mi*F8hYd^(6!h~(w|G zYkVBe^4uA$w0P&76UErjqQbiUaNIwBZk=L^%K&R|;Q95e^<~Z*8Yz`laPOqfIzlH@ zR>inqiQ_HLROPl)tZqr8Kn=K%nm(gIb1NB5@Hz3K!d){)OS&!AS$yt-!evp6j*QfK zy&Z)+Y%$r%uN}V6UMMtr@W;Ay6}R_`^aHx94Z)jWOd9>Z_a2~8Q+n#Agl`E5;&3=9 z59g4HRT&bZom4K`ebECtA5tR2>KY1>my@$E6 z#{`nqvR@NhI~askTY8}xm#@hx&q#5s>j@8pCTc@qYk78t67Eyz8z)6fwiG8!DKy0)O^>WS(4%d7i}0Iy2;Q?P-1h`j}XShdZ*YE>Io z1&&+5b_Q`ndo4R!B4Hs_AAW*f_R)|pw{-YaKh`-4Ie=p1j9<6Zb*-wOr&3GWActG6 z=>uo1ZScFNy`Xcs@SJqJEtaU2`w5e(C}RB5Wgpr7)3-RS>o0gI#;3L2kKfmwvcO;c zs@VxjD~J=|1!jEI0|tMs?SUzu@X9;x7y#ZTEU{&Pv}puvhj^tO{01ugVSl$W=#9+Q z*ah5Ao0fR!Ruq*Ae&iO#dgOUsc6-DbSUYQ-D%}zNwG0$)*52_ z1man)yZUVmgKby>ZGV_jKM}iN0fTB0(tREeSLM2h=TCWGYtRsMdfpYj!r3=!^%Y;X zBZ{+}POujD)6Cy`&PTR|r?&0)4~H6zgzqYM_80al;9XRu?Fyw(|1@cvfA6T-L=I-8lCW6@{T ztWH0$NFsDzk`?XBRJv;`ovbwX`I5$J=qobm_ctw<(?wgub9L9ICkmSUyCU6A&M+>C zZ979J`BA8cIee*)&RUbrG7_D1YXaZkG+oqe|gToD}jG%1l=rWaOkgm%;sxC`oEuv{Cml`NX=IH z%W(dnhq6INkMb{ERWpus104|5wxUACVXUiz5(^{LlD210F)?hwVaF*DDAIo+IH5Gi zvU*=g`}p+{Ay;xpJ|Zp11D7_s;XK9n$i?uO+3g zAgxG>Hgma%F|7K0yKYPp7{`WE8V^oi)k$cr3zGKXODKK$Vya?wYTabI1OBkxIGgAj zs*ns$GGK~vePMM#qUQD|ubML)OEP=;g^EddFON9}SbtfgVfeEa7eglQbO!0M^cpuu zN~^KN5A~xYL&j37apbAY(F#?eGZXI**l1-Hy#!uE$mW+JMvp%M!%4*`(CC=FFm72y z^G+7+vI3AcR$^uzlWt>~otjJOBjha+b)w5ml{+?*j4*v^+yPK&c4S)vhMcczD5%*a z{XF4x-*Tj}G7k5LRGZWD-?K$i6qq~fGgPT7d-8RiUE)~xR39&Z_2Np(z9T2wt*UZo zA=!=6Q);Nh_2Z#k0xBb%HI!;Acj(nr*86?Iw<7wbB-*HC&_oLHO~^Gzk220KX_o)YcW7Wv3)IOizL+N# z_paCHu?v0s+#_?xsp7!V4>MPDrdb)VBF|28wjju{Qicy>OHdxn0N@F*g#1QEHjm?z*~UM-ycter2vbUx?U7s$m^C5`?v z5Y3@O!$+dm+a}5%8;ZZ`ScXHSpN|?#!Ah$IW)3#dq6Jb~Gqh!kcR7ycw5mhnsoAa9 zE1NYu6AHaeM0uU?u&Li6nw67jBH*s<+9h_9TQv(LQRiY5r9_n==ht|?GdQ8cj$wUQ zfq0(=`2KqRzCp1mGJOGvGdJijSUY+O(LmrYY>G_uH|LbMZY>LiX@z8^gc`@z;F0?QmOWc?#V>9S@k4ys=MKSwA-57%0NfjRYmEKPH-OGX zx`?h+`)Ebzl3lI~n@I8(dmw-=GX}Zt4a`@z>LbS?t`vA0e(Ei%H!fFBN>pLDPs_V_m}U!DL1@+y|C<-u9M$?kLb#lp$n)!gELIFlk(P33t7)DJstNID@z2ccfHq$1>T zVgq1J)KX@P;88FSXjy-aDm7>Ga zrn&^(ZLP6yQwZhLzi<`LyJytJY-$3gw!Rea|Y3{$*)%D0WQ^8aAMuL2dZl2}xnMRUh18f=nXxWbOLI@|m(!#SZ_0 zYNL_RAaSw^uV6~V&l^WLbu`)vjb7^hH%aj%!yOGE)+fNVwjkGomI5e#w=z2ze~e!pJ4+XGh+1?@b@jk z-;m^_qk$9FR{#7i+Z5R75fJjcUw)fs`_y=|%`!jWnEn%R2)AD8sU|^ld!9QoX?YmF8 zfZa=E0rPz#p;ZD6zZdr4!21WtUqJmk9RDv+)xK_j>&Y{UIlq*F0?PkV9sFz2`(MdH zmO6|Ft|}ToN5`lf`@Bg)W22>#gnlaOYGaW?Zjqatd%R3X+RDWQxvWq>DPFuSN5}D* zjf6_A!R!qpxR1XA0b%ZwAaW6KV*~@L{*zjzyiZ;!A^+8))6|)b7l-7z_^mIO`Nt>! zRNG^=^Th^5ET0z`5PNMyP%RohY#+sTo|Ffh%&%0^Yf0oB;%mn6e$rwIUf~@@{TN}d`9S*6eJKDa5PhWWiozV&zUA%HyZdM$9~Q{yE5ZPK zs4EpTADnKHTOW1*OQFtzYPjH@ynzt>!O-6V`#$G`Vm*wn^ZCJ}#MxD;>e*5DaQOwC{{N4G0fILsh;0tNL1SO!5h@~|0oJoALxiJmP zW*(0x32U9SXA4tBS-{hrm*|vx6>0kt7Hr2t82F<%w|=HyLIUJ%KB#+tfDH;Dl zyEWZ8d#rF=e{Wx!mt*yOToOPIt92Y-bDUsVVad%w-73Exzc+2f0O0Zw2>_5G+FIv4 z%65o5Gk?1+h_IuicIBK61ytRaHm1Zu;=fK9{(%16sSRFYY0%B(TihvPSsLC4 zt4ayoP@<*6=zDm&eoyVcxBF0d8A!6LYCUwH;xepPH$) zyll!`7aSXd)v)!R28Qe0$D32=>&5fB+fO){8o8fcT$5sdIP#tLql^8S-3nrszF zfO7{JChPC5Xm(JwzecPyvmh@6voKF7Vp``X-~dM7Jvi2pghX6{j74$){E6vNEKd*PBgo0wy7jg z%rrS1+SK*EYl(s21Ol-k3v#XDOs*@}@zTOl(Di!XF%qM)YNVY2OjB&UDz=!_K5Qk%$S&;J&7TQC;wVsWnW(O= z`aCq-?h_)I=2~Jzhoj|7wu??}J~pwvDRzCx{8LqU_w_?JM=v3r@4_xN>6499#STb@ zvXP_lQdDcw2v9ls*onpI5z8i?DaB)5rEA968+vGpbm16;U2WWKKF)0jdLqa1R7hy5 zQU>}tVt8+OlHfu0eS99Y6Bvz+*?P3#@h5I*Tj&?P$FNhrWC=Y=Z+W$7HA}a7z@(77 zEJVT#Bw@tKT0sNY=P9B%8eeFr%#gZ?u7=KRfEWAl&43 z^#(dPeW-TEIp44cYW5uncE-7Eh%XMWfOa-;n-TSU0YU>Fm)q{3x_s4}r^k&YKl!q* zU6$ravNsPc*XFt8d9tUxy-=$&n|VSp?a5R8_ztIW5cLP|JFu^D%%j$(0oI86;{XmK z{vJc_oKsGj!$&jt-{9B1SP#`XhL_)_e&Zl*4FFn*yVLsZMWP-nI;yq#d~rIB+uyUo zuJc_zJJsWU_wR27JxyUf(Zl~H_#8iK^Pk`wY0Mh}LD;W=2lXB~zY$7Aqi=(~5T!!r z?(uaH=xPLr4ECB%2O_+cR`@6@utV|xu6Wz8rmk^N741<4?o|~o?NJMbc41W6t`JVD zMi&uMpN3IwYj2-WWkW&q!nCr_2yB-ut;rAoham?%`vQ8dYW(R%Wc#>XFy#UGy=aIMW5)o1t1yENUpj z8{oBJrHN=Tkk_j52Dt7}E`?zb4pa9SM*lLmHcT9KAX@`BriIx(1L{`2^so5CSDPc- z!*=G_n#>2jTvq}h^oFlCXXt}7{lTe$KG1n9jQcElW#|Fa^$E$mxXm+^4b>x#W&Hyx z=r=6?0r~JSz_&Cg73e1;&1Odv!1OPP_IDxneaAf~1kb*czdMCh{Ju_9K)Z2!6OZ02^bw(GrIQtb+*LTRaY3TvmWx5#m^Gvd6N%80)n)|A zOFD!wN78|T0o~A((>=WYa^3i-e26Lf*+Q(zZCqMdmTvMIS$?YSjhh5Z>@c`As)o-_ z0=#Quxi)Vs2*rF8pH?cD)UynYFBQWF= zj|NDVX@oupy*ceDFvv9{cg4By#h;sUxs+&$RZ}G4KTUn2Q=NynpSuj``Bh=ur^V>eoVQV-<&CHA-S1 z8FU-`p~s}>KlA(c8hYZLIs}2a7;93QP4tq5LYK*n@g1u@c-*C1ur*Zbm5U5m{f0wl zu<5Ou9-T?LRGLBze<8P){@|!n>?X$W9sIAl`g`^Kv#!z*ryi%jYAOd72uS2V^ynf^ z_Rh{b~zgOI2G=tLKXdUBlDG-OB7Fy#b7YT}A!pS`XLEq6jje~~1^B`N519PeM z&>Eu~@ZJK_jdCT{^9T#K8&RB^02}*Kz%!SPszPQq z?rbT#H0R_XE_GSFuR~vIQg3x-tH%x>*btgMj?{xpAUYx-CYYXB7pO8*$R)B>LRhsQ zjHUDP5|hbJf&)w2w_W$sP<7Slh;#ap(ApWjc1j^<<`HWktax;&l6v2X88AF{HC#hh zPFb8%J4_y)$x~)y8K1@>u{^YRsISfWdZX2B!eT0+Wv9%+oIDi%5rFN0l@95srhbb> zXQ&&Paz$NjnAfLyy27UX$5Uk5Tm-^xUCfD=+PmGd?1XZW>yh3XF*n~D*0w}u%3PtV z*FAdC1-Z{lPm#kUD6fI|`$+cAVG=784Y}!b{G_*3SE5Bh8AVF|gI(xr(y=4-Xid{7 z?IJayY+3fgt(X=(%^RAVN@pnrZj9mGYG6O=cV_Sr;^tbq9eh+I`D>X0csJ3~k+7Lo zwh7-HLxtcN{7@c@t3yalB>P#CK0&$Q@Izv;r*1`F3cRub8D10S$2$Eb}R23>RQi!3|-%F8odJte4>@q!69>(O=dSYRN z2u21$PWKmMPf9o1%u!%Q_c9h_!T>9a^7*(w<9*J9K4k5HCKKKtiu2R!dvkn`2&*y5 zJ>~s8{Y{dDN#~Tki>QLF@pLPVlx+uVn-bF}$4OFnPadcb`^dPSbJhrx5S4Qj(Za_x zpLQVh!FS@lcJsNFD2d^e*lDcq)Sp_hqoo;xpVp;Vk#me&P1VV0uH_9o=!@Hj2I01j zzGHzbOCtEl=(A_tl{u&Im*^~P_4_X5MT&W{_DK3BOcNYgxp#_hFrE03iSywroaUMK zlpxOj{JGSU+Jw0Q1~vWy#s@Z1rKhECuIcOX{k3gTJ{c6$Byv#oYl#!3{uHs0i#m|C zzf5Kp-#U8Q9ag*_DpTBovk~2idJ=sPDB8zC`Lh;mp?G(@s5v+C(PwJ1E|UUr${)Kj zycZeB9k|Dg?Ym&VYJkq7zZg9@Hq{soAH}qg#NpnGL5_Odphp#h|Cp6JBuxi3@<;6a?Mj8wj#hk zgfq5I`TEX*-Zu-PSJ85|9E06{^WC^9h*)1IQ=cuAb2`-X2K*oFtomzB_k(|l}R{E&rNzp5RX@QUPC-dTs;4l9CJ4^nP zKcA_HR<*t|PESZ6Ao2f@KmVIINtOS4OzmH1X#eieYTC+QXYwCz+StgTs9={KqI6VR zSJk=ss;E_ra$QlK7L+`QvI*&hUtgEf&s21986w!$zmmjbKzP}XjEyl*kf~I_Ex9v% z)cNf^)zXpH{eFJ}=TDNll)D?I6GP2eOcb(4syuETk9) zo516KZU1|!74Gc7yqt@VY?iVI1I&~M9d}(nt zdUZu52ialNZNM_=@bFVMbJe$NJuw|Jh*A~*xuw4N>x-hiMOy{Sr9Ei@d z9qo-T##)|lEJ2?hs$FeKdd#u=OD>koW>w^wT%n+on|lJQqx9}^lDw4}%>rDPt=W|6 z-93!H$vj6qQ?n%YNRoUv#Ix1EqN_?JzmFVZ&BBhaCnG*@x zr)H90^Km~zxXf>k3*^V1;pCgFa)9PFhD6lX4lK0}z* z`JikcQve5h7wVfhe2}3rZYVAnOuieXSVr+tBd;=vz}>Q^=&ws*2@+^YLNwekO%(@5 z6GbWTcIGlfAJT8X5pUC&Zlnz+>k8ahpxnrNrw|_;M%MBeZyDKVyF+$dUoh(83VHMO zA&v(dA8`JH^WQ=HPdH;(qg`Nq!8z+|WFh$eouwT*U+xX-;H?1FFv{+Dfg<{_32 z*+pXD*_1RE^Xca)jzi|x``40bpyJwiVSJ8xi5)d;=tk6Uj@nZ0bE!RrfmGo0&}NwJ zhcat-7?@%W`?eqP*61y#T7&Mg9eQD`0rpy2D|c^!)rag@$f70Wm|o+>bW4F^s;xMk zR&nIYZm@Q?PRogh$;<%zH2i+26$k9q8J=d(Z4B`;es1*CQQCl<$&tV*l<^0s`}V1w`v!lf*C^ZtgI`CqI4^3+6Td& zmwYfXR;4?@Fc&IC!@~watRgv7nK9id2pXFwacC#Cij!4v23UuoDL*XC0KYp*gV3a^ ze%-O7S<;qz_=}=RkCc7^92&ozGJinf4dHx*(YI2GaRUFu-M}F9b+}q!Qq24Wjh1Dd zi#Q9>=6ehzoOa`Vax!~zc2ctX9hhNhx-7U*Fr+Bc<|;s*tun< zAS`tjh)Y4_Lw4Qu+ttbdPi<^i7nvw7Ut{qoY^zl9vIFy*W$@6EC;_5W$uA;7s;r^3 znRW>CS@{noY(lC4XVMDW3Zi;!a<0(G!UE3%Ri(6C-Br?Ie!>s@aE`oalUckIagnbn zMg1V5kb1}J&2-C~@%1uZ_Om{(vF)@qrlqG{oZ8KK!H90fGlN9*p+N5I=rpg%h#vD< zA1Rq*1f!Yo#CV)O4}ouM3{LQ>C=@W3u zbYzgge_eL}9d-Z2V{iFtS=E;{xy1cXcI8Fv|7xcmUoU~V+S)0*Iyl%n{reJcQp3|n zMI7%_&aJ^d76UQ>juuhF!ao=Y0BajB?EE#7Q(zYYQG2QPltgFmaD1fuWyE)E+SI%n ziQl|Hy{Z(V07~^BOs7@jrEOpJ=&M$R`Zl#jvJx!6a>sBRkhV3E@tn1_WpDNn;w;Y( zXHZn?+!w%;)e7UzBig)jW450yS7aEEYEal0^3dc-aGV`mtSAHGC*M-61>(a7m zu^R{?;zM4bakuQ<5aPqHu~~qvYWgi-qbZ`+ysAKIQkh3EHr4{Ifs>!rj*1m=l!4rZ z=T)hC3VAx$W1#?Jzi33nB`2#Dovol#?x2{Kx7qDrDw0^$T|qh$;; z$ri4$R9{r6c!a$qfA)SAYxz|<77(7)7Y9F&r8HPhbd;J8RIkI zQM`yiZgfq#sPA+=*)2g)ly=&t45f6He54ofjIlN5>J(`xRlHGMU2E1@{MBhMERO$> zS+a?i2p*2m7T$tea?RN>!XsjqO!BCL2EWB$niq^8Yu zF=+EtzvbkSl7 z2d50ZtS0kDoMZ#~qk5ZwH^f4lvRgHFFg)X)l@R+>p2n~SE>xLp9xII8NT`d8)YF4r zWg}Tap!83uo00OMz;#5RU)d8!JERKQXKw}dM`S~AWyG>WOslG6_6uZQ1g~M<^3PZ_ zGk<(mR6|wK?pma+a)n@BgZj4Mr@JPTT6#v;J5QAy$PgY=RjqkM*TjD!;EA1o6XX z!&r&(LK2E{<0NGS!bi(fiPy_1hqzQ_(Ch+MCT9mU&Wm=;B(I@muH8ssYr1PIw<6W3bsJ|YCn z>0ID;lA7koc2@4W;oc}Iw7Hu8UI-ohcpBr@Kh6hpQqTxI-<4Uptd3O}Kw8eBC zoY**sOPL_PS;K{K^!!Zn`i(VoUCYc-s6p4}A)Em;itBH6(-jvw*^GN3Qrwx2+c@5h zD38q_jD^yrEv5c2lD;`NNb`+kkD%e}C7SWwVlLE3955SSKjne2`|*9Aw*q_61K+9Q z`}<=gy+n+ZXy1_J*fgo<04Fmu8pfA~jWF^OkHq9?-4V2qn=tE$!F;CdwNTHF^qd6v z_0#Sd;ch{gFj#KJA>}cd<$4{#I3F3>*03`}gf)>gajvZ{85EwP;`;S0a-93>NiFvi ztO*WHP5PD(CW+tl5FSMr72P@v2V7eZ$MPWuHgjHWsg=_d_-}L)3zkG>Q{b+f z^q^T(3xk^2B`~cjrQ(tH%)QdI4zX-T@byeNihib+{-p>HSF=I897hJp(67ms_Bl^t93nnCJ_aw9ZwZ8972Chhiw zUDL*R0`GZLaQ#5BDSW0h?#NXtVg7{U$_7VOZ1bX|q(aZPAjLxp(Ra zSo_$lIi0ng(5XCThav@F_oQ}nFn zG|UZ=G4KYCVh6}KkFDPnNBOCYA*F8}pvReUk_k-xX}`8D;TYDw#b5(W1b`<~16@-C z?kNLe^_PwD*>kbgkzweL2+dx?(pr#Y9}$^iYyF~9v51YP90ns{pQMBP$6M18X62Y* z+BJgh>X7;J@jC{-ouRKGqIJwr?3_LBR*`$alLCW8BO53Qxkw4d&CQCo%5JaPDWS_C zLERC8(U^rnx*2i^amc$6N9lo7iO>pyDN-hG=T+<0DtBN|l|<(92F-`XIXRg@I$pRc zZWS;}lu0NwB8+)r=TrVEFsMOLyf{IS z%rirZY|&N4|8iYL$~UZduTH<&0g?vn4NXL2ROuS=0M0vHoP;#j8GV2VDcAx|E|4{Z$;gyBS8*g_ywrxAv zv2EM-j&0lS*tX4%)v;}-JGM?{=6}x2+|9Yz*X!B!tg2OCeQ&)#V8CP191EwDe{$1G zL|0>(ial?xJp8kz95rPQ5r(F6tT~m2g%{>?3Q5WYofWOBnzU+9@%S*q;@AZaNaenu zMPdb-(x>I?H~PmFS!VEhv#~;^p0XrCwh1tMsL#|)R&b4>PgV%pJw`L!x_TFax`u3b z!rbb80etO=-P^~>Cj9=Y#5duqwp&R#SM62wHXCd4P7CgABEy=QC*lkqFm}fya+!fi z&}T2rCtJg7Z~mn{wZKERjtkcdL+cj@hfmRl;OBHiJ~6&q8IcJl5O<}3EE{loloAvs8Ot%SKqa!qY!_i{4yZU| zCgzzKW1gR}DBtC0>VtUr^Nx3|9cK8x1f-PF!}{8Q5Xp9i4-J{nE@rdYp6`m&6s`1Xzce;!r* zlha^fXlrX~{eP22O(<`jLx zCVzEK5o@JxeHa5|@PGj%(acQA20J|J+_w~K;Ydm8{sb1AwcubDib=u5l7JEn1EJ5` z?$%UVq}BWB*UfEj=FfDyp7c`okEecVk*4I!I|YLGQQ5;gV}$2Zin*8aCSUUE*3G-x zJ15|~P@%r(s14owPt?n{)8#uH;JaF+-|fuh$@cB(3CITzhX@1;E+}txJSW*<$9p^|;tT1#bYP-?_u&RCKFyE;P$S!9V7j~m&p-qt_#6W?+NmvKzB z!Mg?3$s}QU!2DJRn7zN}5g#`FGZvduzTcP%-V`lGiWo~O>ZfKCNB+wHaCR<+16ByEDV|KDuRd7{8DYH+q5nPh4j9tS@s%BG3#S*qq;7toUcNy(ywxCC1L; z+}hIm*6uV#&THQcqAFUjv9i&vpEL02J%ES=mrYp4X-<6Ixc=q7FTQ>1%s&dmj}SG2 z41#bCo_vxYF#;aPp+wXsVATFS9vHkQJRo)l&5RENI%)r_YTE|11~RQ)!^hs8RHb(| zx>j9VVD=9?^0ma{hrNIKC;{vxK&bCyayXBwe}qt-4OccI;CqO8PZqXB4fP#DD6x;2 zg)}VpdJIwtOv$LUV;GrE(Kc)o_U=bKmZ=t(DDpXzYk*9;A0ZNS1SFjykwyK+_yVJ- zC-2c!xV2qOFzqJB>prF>TVoj=Kb+-532C?Y=avPdZA}zu|BTjFg#ibq{=h-i7Ql~& zIsqcS1#G}f+^B6j`T^62C>pg@6hzFT5(!Rf8{c_2EKuQBB9$YFWAUV9(9K@o6=B_F ztTEnQ-e^BRz^42I<*04HX;OBqwD>}In(e5o2(NRF{1mu~!6?2bxS{6^T1E(a_YD+5 zRsQDHqu_P_ z#@u$BV^9SMsVns4%L<2>0$^XFHL3{5w~4@g|5PAG-!NWtdj6pxWIg(~JqQ5QBsUo} zDiIaFkNRaE*3LjrZoXQd_B8x_H9VE3L}WKeUzLcK{o@CKnV!b`7I1+zwxU{maXw{4^Iar<2Dc_}qak7&hkPbamBQcnh z%?VH?I#YXG(>lUcT&$9GSCDB-%o#`LfC`kJ1u_r(T33;ikt_nd$>k$K_aO{U!aLPA zqn&}2cp!P*Hu*BQ4`$@z`+rj_9YcSiU6~y8)on$mz?cFZPpYb_e2~3Uk!#^$M^-h< z<^JY{EmTe4yF+k}izEpXlaMg;p=MwswjQ&BhGW0A`uh02G=BjkW-u%` z#)74ReeAMdayKWK4r2;3((k)?{*JwZjJ=(TUfs=^bS61k$+YLAP9+G@x|df62^3HG z9^rS7&|F>a|>UO|ksBcl+-rQasOdM5JN}*f> z@S$Cf%-x2EdR<%y4hoT*T>5yG|>_K0rY`G88=nccPo~pJS;)RQaRu>u`xNJ#a8kCc9jd0Y=vmfphI_n&R z##z8F`lUXeb;ap{>l9$u9`1tvbtm0}!GFZ9UwGSmhnKxI z==(yigvE%P@D zMPEznWN4GfL@j-WnyJWnsX0>5(4D#}MfEvl=IS&f1w(jD0S8@ITE&^J;%=YL-sbwk zc3GuFs>n6~o@85Vi?hos7x$ZVI(n|J_m9u8FkDEHMI4p%=N{0iSx+zYzfNOVB7DdK z<)u_V2{srPAsLxZva_no#gz8vVyFU}>1mqNkr?~cTpO?e=t5fO#d;oQR6{%f$bA?`vOX#H+d&XY9{94jJfGbTk!bvD{-vP_g63ST$w>zVP@=Satc_V1B& zVvfU-BT4OC9@mdr!)bla#PPz;#O_Q2*NVWsN1?UgLX5Z=lM;oA{`mOqYjY1I6FU>e zX5u~fD(LO5_d>awZTIzYla4!UW-0Aq^Lzx zD-m8%G`kjTovFTA#HgMYkSkfAp(p&n#BpMIp{-=-XP^6{K@Y+c!G&?3D36*~qLx;J zT7m&NQ7o5vj6386=b@m-_I;WgNUtk*oRoWJfnmaoS}R>mqTEHFKP{+UrRC;-j=R`_ z^5R&wlxz4~GFN;(LKW_5Q`FBE=8DB)tYRsnBV!Ga;hPnSOG-;XwCq4_W?~8!UvPjq ziCL^<)g&WUIz~CjW0h>Sw7`XDF@;?m*+hOMgdRAF*1tz>;N)cBaNIwR-q)cP&t;lPsnG5q!;=>7Mm4n?Sg;$^r zb0M7exA2u>Md#_RZE{(z4ePnK`BWbsTfvJLA#AC~EviV2~3 z)Vh8wIUsDjvs@s$yX}l}l24IY%xNwjUD!P^l9jR*Qi?VNdp>!>dc3v#UMILO%pth= zIQs#q(XdP}YW)MxKphpT?~)u&Je&8U3__6)bo-jo`g%6H(9E1b^=EOKxY7#S>dM#k z7()CJkBr>Nd^t1imwq`laKW_sFSP(du>q6{jqk8mw$@@Pk?O)$6(?P&1+iA#VZ&li79H9 zBPd&4!I4Ia;cbT7Tq`H^hw3oQ=^!bjx>MVF3w`<9$Qz)7PwG}({eTC2TH~2<5*<~q z2)k`$ZLb5Mr2r6v87bq#L6^?8i!VAcy_4POw=>=787jZUSU)~@#^()6ad~AK=Zvu8 zr;(o4FX(N-Jg7#bLSTOE%i7ozqjzs9xuK-1_*164`}+!O$YL6lFp7IT+qkL7EdZ5x zK5(2x&bX86HX z`cqCycvg^%vKNB0N8Z*G=J7VnUWq@n`7PLNM*jQQ^v<8{Q1d4+oj(k_RlcH=Wj=TC z4<8w>Gj=<3zESKukM@F>)R@Z}H>Cz$aXr!I399E*V8%%J$>xBgUsz!VnXe8#2yxT5 zX_Kmi@)TjmBpIc%NN@aKH{s}Og)C;71JW&=)|ImT>gV7Vl~!Pl`<(y^#I991KE)P&6HZ;6Y@FF>n$uKGpoMKiL z!zX)sEK6)0(%Gt=*75j-jYCiryH=%2mLFzXa-?aIoCQZs=NXisa!Tf zafB=!fng|w>|ai{(*q9Yo)fqB2F%o1aSfC8!+M+F5XvWBr0@&TiL|seU9y(Afugn^ zH5b*?E`d>SQhyHHiAAjz1&yPr6Yq2RnTW9rPNS4*ua;bg?weC9C%SiQzU@NAd9R^r7*2@$1-uefsXnRgObLRQBCjX@ zrV6T-+`V$Pk<)+EG{W#^CG7-X^58|ghGR)ZP=eb|zII(IxhPrk?PSt?=GN%BC)c-t zAbDon897OGs3+cL6U;hFVc24e7}BVI`(1Thc8=*1x9z7s?2AE&Tld&&d8uzx%KcYU z0$~Tla~P$KU6{UARzRzkC>H&%zWk8Wgtgj6Aj!8)rY(Iw5qe*aAc9spd2$ZBqEVOC zon#4A$i$b;gZi3*B?kZ~KfDi*;IlX8*4|k352=QM%Dt+jo`gs!jCG23+MDkEKw4jN z7~v?B*f+YNtxx3es7?*~z7M3erN0`=s*iIdD$1sC*4v9aRdis)+-DUjI)3KD^jyfO zI%{5qK?r_tW?EURj<*SN9XnQ~w~7@z#wwE|K%|$6r=f*Tz3(A!-Hx1#0@vDi2yL|Z zS^}SC^xzzlSUZldn@+nSZ){5V=bQ6X5WW(HetQVHfGR|2Re^V=H`jjB ztCIcd75_~8YfRk_rssJ!BfuN<;mxxD=W=av?O2qZuzD6-7(c()@2g;nM z^0QLwn+LNCh2sTxKo=StZ-AsP_qkkGF&}>02?Q2Z{5;nQ=h{7w3sB#|#*M?Xh5LG9 zh+%p*h*V$TIJ(FJKb$1L#7{BoJ-)RZ>uX~2y3yJ4_W93C_y3$9{r6<)hiV6Q$(IPG z^h?wBzwhn-7yad*j_Q(Mlnq1Ye-lBq?QldbWK+hQ>0mT8?+UTov&SivY=)E`px3KV~HcE6hETzio>CrHm)mx9^ z&5gajP}pw+8tsQ%F|rg_2Yl!yq<*1>&AeAX`)Hr}5Y%wwFdeyi<{(f?%4?0eL>b(T6bz0B(N%y=zKrNa0v^Jq=yyY25Z$q?Ezw6sndGD^uQHz;GBULLW ze6KZ4t+4W<-$v7$C~e%$AiTGls!=K#FG?i2D7CK8=H5UPgk^<$!{nbW%x`LfQxIkKGl7h8yMLp>2(4h=5gqep*ZB0D+ZXC2}Yq&)6V;1>lpaF z9E-v&Q#&X?`%uHfUN&WnaxlFK6mUwtVzBw@A zj;2uQlt#SDQZ3tnBRQ2sYIKD?R^|@msq6`tImKLRmEiyNZGc@$S_>zh;)P*cD%fOD zEv>2nbC+h16zjLA2clQ>?U{Hfa1>p;QMrJ-24n6Y8frAJ#7T{-AclQjb&wZqG8!c$@M}lL&EeD z_xfZ+7{8dLh?Wuq=K7EtASG#HW#&q7VLN@URx}6I85)(WR-zU~{oU~%oE}Mo3I09P zU3$`PA1!0Vf>ny2jvSbyj`{RYdE9^I|9@A85-sWe_Ae}n=$AZB_J5u^OWK;4y8oj* zkcykys(eBD{w2&2F?BL_1ls?*K&bqadB&?O6_!GmOC4E;rYJ3#0urb}DkP{hj|vwK z#bDMM%~-cd)~Q{E@KW?a;Nw3C;pfjk?ry%`L>eO*bup05?RJ^v)NA%HHWY4(arllQ zEbbWPaDvi$1ST*ER~eT}*hF$|PTwp|{z$g8hyZfn20<#+i`08Mxt}e>*CI{4kJ)(9+^+a8%~Go5$@N7VkCP{g~yHd%NK@b@bTl_l)+7%o|^b+YH+x4KDwO6ZDaA zd_AEnvQb;fjwfOi5okSXKO8MgGaAt*>YOE{WECr6hzno?dYA1lWG>F$jxA)afpT9d zbPFkq9Wd0em{pzKiAF0{I-ZTRO#2I8TA^JX#l4n;&d$P&A)iJ+?eN+V?4P-u-9rQ= z90jaIoOYVDzXhAzI;=@rYEM79#-r=bJc>nGh7l#naU5MwR6}jq?208|o&a{?m28f+hM{pXUTKGv>7i(|d zB|$zz@0BEoqOXZ^>z*=#4xj3{SvO$@v}y4T`0fircWCsHrE~rtrGC^VAH)4Bdu~;Z z4w1>Tmi@WSLsJqY-!4nUYl`2n+v;`IgmeMR+1U4d8+EBNJzrmCSm`#6-XETAMXJOt z$IgBB^Ru&Hf{BrC_EMqosrJ4&?sih3t%>p zU5yz~&c=iH)9Av*9I)jxIsbjU9$n$l@Xr>UYgH6bXplZVD^X zta##Ail+cAE%(1T`1l3Hx#ge6%00~@?t97|nFoP|R})@$da;=AiUANvFAJ_Ssabs# zQfZ{QWk!U!=-vKHzH9kQ0>;5=#yRA9!{9mn7&!6pefVB#r9jvSX0{kHwjj3~ z$K!k#s#oAI1dh#H5>H7LU*UNt(90$kT;e<8rFYmE`Fx&Om&ZGNCEOgLxHR|~7U~gF zuIT15nUaCrkLTNZ@mPPS1d#8Yl6}?rA;mKTw)t_J!&t5uLDncFv{4Qz1Eli_2HYAZ zo%naspq99@;V1^0zbe7&RiK09n zDBi&%P@&tUcFF0zQ1$K{gaHu4sm?KTm<3e63JFf9y&X#chT1&V5>Ej8J*j#xb`x4 zzzrS3>r?~_V~V2~zmRWHR7vHony5%hU2ZA571hvgaUCRk1Upa-I8f8_l+Pi0y$VSo z$9Pp1rDpv7F=S@U*mRZcin_Nb$ZiJFeImKR;<{2APiFUzkKup*^MA+WA5JsM$KQL~ zzG4FRwaWazuQ4k)+S!{rI(x_(+W(W`_3XU(Xb$_#<0*^mW zUkIgpASRCaunZIFbzf)3zlhh|sjjoCEU5b11j{)7SiZ=lP@cigb_{aY6*DrJf3vDr zLq21wmQ|}MirJ)StzM>*ysDkBmO9Mr>X#tS9Mqw4>T5R!>>XMf0GCnn1`G zzE73k%l2gqB7M1v(y)w@U(6ZtC*K4O^0szm6*3rnYd%`2OUq)!Iv=GQhdl<*6RqJd z%0EK%pMm&yi2kwZZLLSGUH#Q#XMOby{Qq;m@b4a5)YjS2L&d}1^#Al7Dt_``c3Hfp zn`XsKxP!!_T6oQrKkYDjd&51gJvO-PMcusFf@jgu?k^x z$1shbbi@L83!+H}Fp!x*7P>yj_1Rj~qi}{x{4M%m-UcQId@_|pryJi*m#x|Ul=w&6 zvYu&8vTf$6vA^?jTWH1p(QIk4((+2#w6>J<*G|0fuq#>2#xC8dATsj}K!?f={A(?a}L|Iq3!cXIpLP}>DbQf6TkR;f1r7+IQ^zkO4X zF`kSwP zdO1I_Csg9f%XSsNumVrD5UPzs4{T;p*M3}~yB|fT>DMSCu^$b(LXn!{8Ot)l!>~il zBm`wh4=TMvv?IkcPzS~$oQfiHMp@5|1R{AtO*@9HmS`=5RL9@B(%Baa*6H5>s`x9g>{2tQr(l}Cb|r`J zRavpe>1eCl%dIiM|MK&i#HlnV{c9NYo6~%}=k6S(^SeQ`yR5~rr$6vG!HJI(Bi@OK z6T>5!YHi5&53)@VPEcy16BcJ`k`oxGx@S{mQ{`BDXy9_~%(Zx~)UL6`J)}Lf&9rnj8Q{V$yFylP{wQq9UieKB+!CdARWy@x^UM8@ zQEE$OkJsp`^wd1)K9*xnaYjzm`~z^hv;M@_Q4jHJ`-O5r?}pN0?Q&Bp-~j^UwP8l@ z`WNjVvi$C|fYm#n$sfgOa44UYP0PFrw%}WcYq>lM0pCICv-y*5qs3pNT-iDOigoOi zHnb03aX9UiukakVig&z{FMS;O2#)>R5cDuVLU@EcB2b^P_&Eaq6Q)_Ba5yI}E!Y@QALx^51v*e))ww6hg=?`xdyq zUChBN#p(w?R6Qh1#U9pkSEB;Ys`Ij2I6e7vsmtE}-n)iSR}wb{dL$Hq0pV*cawd-?1Px&9-(z*e)sSdLrO5^Xi^p z;kW5tM*LawQbfp!nP=-M~uz0NGovzKWe5_dhe4rAqELpB9q%UBAk=Pi%LrZQBg{CFdW14&qnxA_zC+~PV z!FxsIieIRv0ADMuFPUCZ?hMy=+kU4Sycowb4%?l=RI}9grY=nRs9cgX);c_JbI0X) zHU@+=y;ZnIC^PZT5_6PGheuInKS7SlXRsr>E9`TcwucPY#}EQ0jxbkWznniHEKS6J3I(vH*S zi^t0|KATdne;|g^6)^%%p}xS>kjfr1NK-(TG?hCR(%E5fGpEBi3kqE-)H^f+PQJb* zfB^ZbGT66yruMqd`(4Yi2Ib{mh~lAaTcUZB)Y&NZMXch;EY3H|*9v!H$&O_EE5C^D zxOuG;dm^N^MF}s_UwG-?^-N}8%q$0RrP||^{vsG^<;U_1RC~vJBP2Kh7~G#tb0eyJ zUIuL6k=_+=Y>3P&XIed0yx#%&{C^F9zPRQ?@LC7-3>3DWbB}ohhPq*J%FkJQjTc{* z{K8M|SuW<6)ph@J_*g3BFTK1ynLdBjeSx|2NoeR6X?%Zlcp7y$xA`f>KNZM%D}D|Z`<>14JDuh#+kMC?>MK-kQEaCk zyoGswp#6MVEA%V$!x`C{?R7fc?z|(=dGVtfiEG0>KM}$ai*c?iaqQS3mgu8XzEC$uhBB|W%AJV1&>*|0|Jlwuur!yXPz^XHDMK4FW z0|iPxJCvl;g%grACh2gk@>0_7?pE=sj&ZrWcdEiNP+Vhwor7RezD&!!NViZZ}MKKz()p8 z@_i3jK)X7%I|_J)>=W;JrsP`-n!_ps(s_gXMyA+ihakU0zsijWR|=d z!+)rEt>00Ok9|_8~4knoP-n8TE4NrjOhRu5Wi_;w4j-^J=wqY87K#zYy#jHUcAch_OJa|oD zQdd0d3}hgDf}yfO`mS>^dvrr~KwIp}K(Y-bf%_&BHmZm@ad*_mooSnG{4FXE1s zt~Xxw({#s^CkB5EVfNo$^DAcEH`?*oXnRs|F^^mU1XjzC4#gw`ST!`jEm zE45Ywkf6V!J*rzv9Vum?eGJC&D@1Nung->yR0?NSl&}kQcT3M!U1`4voBQ3QDtpJ& zQLz3{1Fu?9Sr(17H+}|`q}-HX6No)(&dhAAt%M`>WCSjy2xwZpOX<(c5VUO4E|)SY zY-=2At2g(|VlWHGrB{EfhcnN*3D5ii^p94uu?=ivFB9MpLI{pw{U3pB%mkfovRFp9!?Ex+j|A zK9HiOp`>9SFw4-9CAaa0Vszt>RqM;k)KqnL{dR8*-d#O-I;0vE88wlDF-^me)!da= zW$`=;MIvVKS&E^8&cHBaTa>R`f@)8fb>GHmxG(n)GD}!bE);Wm zN~*};0J!{Vt-yUXEhV;*gy6SVTp2A!giK||?k0|g z2p;ht^Dr<{Fc`^3ev!mUkS}@}%P>di>dC0|3^X~K3aSlt?$38*szVyKW)7MTqJpP4 z<2R8NhlgmQD>orv#W0K!sz&!2I1`~aGMJa6!v;6AaY2(L_Ml8c2GaGCA=1PG6mHh& zN--j*U4a!9dy!0;Pg>eH_F|zzL5fKI_nMCR@#xw7VXPNHmLopYMWYN&bk8v`dfj~$%20q ztVsAIbW%CiZ!9^P^0kC^74SmQ=z(R|)rAMxeon^OZrx~0sqF6OJ_)e(4aHEL6#a5q zJ>$Jo>cY)GYqb_Hgk#rZIzn91by*FIxVR1tR+wXR8jKh}Bw65NWCO%>vP1E(_IQ4u zT3KrHFRzD_@vdljrQ`S=%q7B3KCGOR1UfL(URO78d@5eiSxu&ZMb#6vg*H7c%}OQJ zb;-f2OAdhOc-EVHHxTeDV>Ou&uvWX^wigoSB_<~3!(ds21Ym0|Nxj4IIen{;nUhbU z=@*Jhu9m1aZ6?6sJu8F2{Ia%ll43rUV+L-;h*(ZTOF9z;Mlqk#iS+!WgT#hVZxOLL zE;x{6!}J9UD%Taw4%!h)oF#+ zSW2+Df(Vt@-`>mI>aaw+*+`7t*JZlX5bF=&$TgRvl~1b0qH30k6-_JzsYpF({=sm& zS^(BkQ?&6HAiMmtA>TE$?7=wHpS4Adl;MV>J^$B|qE~Q>^c4+&Aw^^6qWXS!$2h98 zGjdWPy(YFIJD0K_MPq%ZGCd5SaTs10;_t4#cyOy)U~txGP-IklrmzNkSYN>0fHz@~ zpB@q~X)}W}$+Kx}o*x`e70J<90<`^&oP;-^9SB>FK4L-*XqWP{32H5FhLVPl+RRxI zEl=3Q{xM0{ur}x48d~xL`gk>Yuj0O0;}2H$rYe^UeUSsC|H`729dkeCvMFXh6FMe) zU^n>tC zB_HO;5lGlue7HX}iJI4MHDF8AO#7KJ3pF0pq~h$CnbZdqTu(F7HE1iPk)=|S?BDq9 zpK{y>C5vRs(Zp4R zf-GYKX5rA`%4(VQ1Jx>4An|LBEHnO-7x?3}$7YmU#P&BS3mhj>)GlThq>WT~(O{w@ zf#b0im~T85Sbu&zV$lni+j*#$^b*Zo>YDM7BDZ7B&Kd_ZEMv z66?hoVA8xV7A)+;{rqlei?&L*{01^+HWFRq5<^u3=o5{aHI3R-nON4j;!06@Di3Ib+B z?z>9RZVc116JiSjoZxtj=AFu-M)2CB(iqI(A_g5D7HO@jD>Kh+#Pl-TTE^neWe9PT zQt%3uDKW`*f0w1tqL#Ut>0*27yiX=W6%$Yk!<;po0+=mX=hm`rV{R#1P;wdRFY_~C_QWN`8M^ngZC7>Q_=fPB9bWyIqL%k1<;vr z^!6)<9b2Sk>tlOj1Hm*l&ph>`de_Pip!|ZWGT7MfG?%^+j$E-mS5~uKgX>o>cza0u zkjSYXIMM~!vywj2X7)4{&Bvb6)RQ#rv1A$%K1$zug_(u1AmuX%z)D|;uohvlW-fVZ zjgf&0DKyT+iBHWUYs?V#Eh`ia$$OfP*eZ=FFj;t=b)Qo<>cs7puam-Rs>1PJ7>GGA zYUP(m3o^z0LantDydFCSrKnpK-MUW`W_hjn0mm8~3vMU;tv5mqYh13A%8-s}&4AIF zEv2GwF*upYPnfmh(zgoVP?`vZM4qgvpaHv#!a_7aZC)fvS4313Dd_H^KtqXwjE1gs z^=?a$yc))alduM=tGgGmLYUYvrxhymh}dxP&XTS{;;|zm1*=}l)Mx^}(JDc~_*r<* zwSvS+k%r!!9O<#5{tZBLiliA)sDs;A3kxUF#mbWJ=#acAcduYk(_0fuYX+6bh4*7a zS8Nc|1FbW*t-6vZ3>6%g4yI(LcK8{c8WaB`r#{TDW#L+-%D+S@YLG!r#9ZFkagmr9 zy>|NOtz=1GJuIoCCa)abTtiqojRbOYAddw+tH$cwaZqS4ud1h{xI@}(Jq^)5vHu_YLoxt3}a9^kLmV}cfwhHAu5LT)6N|327EqzbOMSy;J;_GPW6 zD%)IA0TYr5TB*x|qd6}#A>`RNp3*iMJ2e47uB>;n#1qM-dMhO zH@TQ!mr{kHaByKHTcf1Tb~Y=>!`aa-m_m@M}ED!CK<7DOy+p6&;Kz zZNo*BE+sK*$vQAbCVt=Hw?gqx%>r&(39e2RCYB=&l2(hGMXz@_4%;%fU~6+_V?hpB zU1xu6hD@bhW>x)*23Tp749yYP-ngY9s`|8N#f3uFSNYo^+sobHHh6nv#Vq)Rr_l^d zt;IqW=*%;)T^txZa0o-H`LjyTv)C-+2T} z8cGdK^Uor}wDxjon8mA#N{v${yjwHBT-bN%Ey`NXD-)Yo)j%#cL{0@@MOVWeCblw! zx}>G>Z^NIjxhjPe@f!Rec(UG^k;*xcdp+tUs!V|9KTOiTS<(WRRpT_^J-W|n?OIg> zO3uQkk}#=XbN`mbII6$M0gKaFC9e`Baz4x^67jGpdQHx|8@Or2plK@EWHU-w!JGZD zT=dqwaW_Letbanm{c&zcOn<QnptK5 z!K1f7v(EM=a{JC>^qp>6qAgrak4ERDBj5dJYAK`Krksd_=_~-bxP-u$K}|j zgUsi;-*cZfVZ;FxO`uuM_?ho;f2`FA`>Kk-#5L6`VB;aYPes8mo%ABC-nn=9v#QD*4&k!qU>R!}8#94vf*IqPeg-ZXgmjLG^C9IKC6e zn>x0sTk#&J{^XiSG_F13$hUA9I;8!IcssQLmOr9Ul81=1Vp41U>;`#J$Wz&$y(LLy zBa;ua*Ol^e$HEnfO!A}M*ttTkc}0n%SAnK@+(dedkQz28=WJJ!odN;1Fi*kokzkR> z!7^87j{UL>-qz+J>jy=q(5X&MJ0YHN+t4*Z2vHChv&#&yUcH>D;>hZG5M#7 z2CJ%>T`4U0y_JydW+2c2#1M9++7iO_tME4su> zHrAP05-cY{jJ8N0#sj}zJ+YO=YP|XP&(F-e^=YNSi)0NnuG-m+WZeY}CV z1|<(>GGKMDsf&ZMa^6^rvPif}N)v(dcZKsztKtXk<_wmd8iMmY6p#UFO9i@z+MD^v zptY0{yRc4YYgTOVphlyn*R;`nd+wgFfiP`3zS|YdQ-~6VR+=SJ6tAS%y$f_!H)N=L z-BE_+63J{#62owcFf7I$**vSM2FPE?Q-o~3V#*; zvLwfhPX_86QhuuSFg}SL;t02kaxU+%CtZRciV3$zWU)E^Rr?D_)&{7y#~5tLO_4kP zB|dflY0nnuk?PBHY|MWX^jKBU%irlujsxo*+G(X$hw?4%v)gaQd4rdk4W6^U;+|C$ zFC&4I`^E`k!nm6@?P1*dycv2U9_WqGt9~^J-W#_aSFt*q71rlaQ8%rN^Y;CNF!G1t zUjESb4gys#=9}RMWQ8yh-nhfYnnFb~7_6tW(W7zv-?qV2(+sqRQH~_jEAeM^#}@1i z>E{iFkNmfJ1wP^27A+Xap2?lK>k+sfyxHGK%dq4w*H2DuLJ;4HvM&J1$4~GSK8`jI5q2ZHAsH?!zuB?8C7N z%CaojS~3L@8FD$mDti=8Z?r=vq4|NES7wwR;aHa3B;Z^jE4DbB-w;BK3j`KfNREaY3p=JVw3+}VfN)?vSS^yD7>hJo$iJcnc*PJEPF@?==-P!!O_sDLy&wX&w7L4(5OZ{i8}pkyT4hh-VqDfyrmilEQ*PtmJh>^Iqul)lf< zgDC<9Uqi<8vXRE5*WW6W>mwkw^O^I9;&5jYtxb*SELA|rDx3JS@yK7Q?&a>s8kT%- zV2=HhouQ7k4VqVvupfM%-!K&K;e!0arDNH|vJJ1|p1${e+FtpU9fopgXIowQ-S}Zy zVtryhJE$fREVEcMQC(_jC;Y$^IGWt&jc2uT$b{Q)Z=t8o;N7fO*E_(vwe5W}f@fmCsh@ zqKm(&QCg?C{qSssbM&;NT}e7(<~`9{=-HcE>6TQ>rLexk57$ZkDni196m|fiC7>*Y zeE{yF=9Aag$~VDvoq;_;Eobm4ZQxg=<;%2y(!2%!nNGm+#BlH?^%EcYSKg!H^aoKT zLN+hU`2=<#tfhonUUSIRM(~igMv#FJW$6FI*f~U3!bI)5W1ZNxZQHhO+v%XA6Wg{r zwrx9KPHfvrC+WZM9sPH92eoQYHLF^ycD;M==T!*@4pVxNc925G7jtRhThsI(z*bWK zrp6tj+*(9zo9TpwZ2mnRHm(xT(#MGIDnmKM;={)n3}agy&rf#22*~oE8sw_++_(ts z$AzO!Y+W*@&ysQ{Cv(J1(l$RclEb3Ij`a3yy`a|n!rs0}_`y1Ya=)&{XiTBfj)n`U z5lejD0Y2+KP?Ur@AZd4GjN_BZda$ohVTMonl?|dyl8vfdVDyu!NoJFFr7S}bdiMGO zi}FkItt#iM=HZi^A_1Q5q<(5MqZgFGrfm3Uv@GC9YM1_9HR7WF6RU)A((3al&l0ps zEWq$dd`e|=B@XCU(KU(kHTpvwfDsBfwiu-zF%bu#f`B-5E^$UBD|loXA-)`D|5bD2 z=t0fayh;iUcFdv-r_SR7&W3P_#R1sl09LhF_{nxc0Cp@^2k^+iZF)3BlBrub#9Pa{jrhbc^B5sy=_?Ai< zFD*q?9tG}J&VF{x!(!38Dw-~sci;!hiG__7UJT30tm+VnyUxw3KEt*$zDfwB8AKhT z<<^hn1=3(nPz7OG0%=Z-!~WIT^iuz!Ik~^Zp@=Y7{eIwF@j=~vq@|?QYvq!F`l}Qv zC$13%1=NWgrp1aH1?q$#ML+8R^PD2f%y9i4?r#;uJ9Roo7NG4y!*~W!sH0OL`^QkG zWq--Rx-#%3hd#>IpcskQV5|ZwRsYhwX#zjG?r<;kFZh96s(G#qsrJ|FD6g_CE8eKd zANe1Bq*_sW;k0`;G<&3~F^vV!RC>gYgl(8j+PKKXDF}CGg@!75d5#br(}qMR3j~dX~tPl2H|7 zi2}bey-5=lSjh(>53D>j(Xo|1M?GfkUjpnvu=(lVl7ld(zfx>+W5SxiaHBt}KC+4e zt8mXM{|>eqv>dR}DPfw`lCDKP13wTMDRC)u(TEK;!-^Fue~vWajOZVT5VF1)N8e| zI?@`4lEzq8CgSHK9kQ;Om)=LYF>=6pEjGaO{Dm>#9|R40d+gFDj-_wZS9(DOsmi2M z;XOLOo6qS3*A5M}c>J=X_QZ&=Z|}HGSBP`pp8~|^+Jnd#WDL%};=u_a z&qsvMVn=p#k&0stu)$#B7?C_M6Ms*5NJGiMF$?X`xVr?n;*!3&{kC3TyzW};iWm3` z?+NA_M(;X^t&Mt$nA~ag1FGI?LGZ<>5#>LePb$}1(mL{d&t5K?5uqzZv(%vx#IOwf z1SvSU9OWV8e*z}e?@~p4tYjX(O+qg{A-pRcaR&_Uh;pZyYiqFqMqo#puv6rwO4Mrd z=yh0D+EMo~ig&bP>M;}P4cCYuhxRRI4x(#8$L>P8?L<8>3NrkCA}JA80t-%HDt89r zedW5D4Ydt5UUqR6aGkD=C3D>~Z@ov;&deE%%}(5*Bz?9hU686lc~dnKP4s|)U!t4T zOYIKs7+n0SxKBXVs_xH-I?xSuj21iDz0XbbzZ?{E@~DMyv~eOx#uv0tmWQi{PGQMMGS- zlA##$cyOj9Bg3q3L6YWYmgxQ>eJZOFn0|nm`&`qqvVhP}5L{dZGgC1>p>@8` zhpRQxY(gxqy8eLB)4eAc*&}o+!FE$#1drn;6x+mZaMV0=!#VnS| zU<>Ej5mR~l2@4%s)qm|Gq0O+?w3r4t_8*vQU2@aZ?~tY;GlPpRpH^(k zWdd#r?SD`G{>utxVq$0>p4To-pU+g)RbVzxW6IRG>bIgaR$%dE7Q9jck(Ru7U|Iq4 zL`@ci*~W)LqS4-cvfjgVWp!2W&7cFw*|VIh7ul_+XCa}fzm5AGn-~0R2OC|N{5fua z@k}dTun|YqDAtyh;JKUK7!q^#EbxlI>xHEvCA82w6@a^>kPgxvk?~{i$LHIhPtkIZ zmk?}W1P*5?Blmw12(9O28M<{G2}J}&qQbe?wpSIU^qLWz9zj{AA)L(8*B4^nCE_HR zB}L~YCBIpC>~RaedN3%f#Nz67t|d62jk}Ow5#yA+4XE4)n|jqT(2FKfENFqeAR%q5+4(g(3u9n?zD>uEv>9?;ynbG~0*x`HJIz`3GBYZ}_*Kf(c- z^11E_iGrm0e>J`q%~X36u5FK*2g)2~62Ixc=~q6wAb&0906e|;0Z%Wk8!|qmHx+G9@Urb!>-+q-@NE^&G=kqAc*2UtO zqm|044OQui;X`keQx+_gpr3T>7ghJzac!?CHWMSctaCC%&9nV98N<~$1z1aEQ6-#v z`ufS9XA)VnGeeTEgH{GXN)4$J*DcKU*&l-rL+4kO^c?$6;YR3@N=cku+1KMBwkH0d zLGmIYm*37t1G@`q`}Ky35qXzlWJ>pZ%vtD=OQoPEk8VanXol0f7}eE_@&P?x*raSP zi=S1c{J($I*qv1BWMJ_7iVpg}mi&BK(9ytB3nDjD705PXbN6-CRfSW;HX^h!P;MS& zbXLp0TWaPX4TvaZky~!&&x5+JT~nt@KPtAI_A_WwI_DH)_Rp-3pAmrWN{SVF&-c8%E@jdja0GT;b>vsIJ*#Mc_YS z@YN~3)Ac?J;kyLB>=#Km@GqCJHNtQ_Edkp{3Fc7=jnY|+(|G)KQ>^&X(Obi&UONNk{J0q;rzY z5e!!k@d-UoCU|vEXxO+%6 zXoOCA+JLdnJx;=$b;jkTgf?D?;N6fX0`K?x5x>c?A+~V0of<3W#DbEPQKUxKo6`7i zzMvBGwn;$I$OCxKWso+Fyk9cBj>niE1V;`k8TIyOCnoM9c5Z~fzUj1#jbyO`- z=9FHqBxJa-+P&nJB>P~yEcT9XcCRJ_R|8;#@OxKLcAif_YtBtR4%IAURMLqp z&ls_r{#P11kc<}>a>g4VG?x{_TztPV(yBIb=C1GGpQc2nEXJzzDagW-eYUQEc?!*J z=V0^}+&SV**~S{{czx~JxdN4jIW!K!fVloISD1_M5I^S<|F=G}2CgR-MQ-~1nu?eC zlf5H6MRI1!45e!pgjqrvI>D03Q}-XOwe@oamk(jP_Z7yy!<>R-Sz6EMBgS;}aBc|` zDd1{Inc6JP9pZ9l|IN}JZ}RqE6Fu)!%zu`2wJJFWWME^Z0)y0)Zl;$IJuayg;{DyZ zE-pGIV`g76(K#nIeB_Vh3}}K@EEe|LXRq^cm}FbI^f*$Zt*^rQ52R6k%FQiBwbGLF zOJ$cm#tQ>$Uy{~hxPm{Vd=cJRs1eOR-|(Lx$`!ygvwjoQD}=Hp%3>RWk%S^$?6+4m z>F9>|rY|L24l?5yVIi?&1C?PT($HCZwvzHA_c7x=m`mwb<2^7ht5p4)`ZVQgKkKwU zn|4wx{gt~eN%}pt{Ck&3`oY2jLwNI$=%(Bh<(dtZ5rF!H`|7McYp6yM`_J)rwm3rD zs6r6;E>@5|`BS>_cOacxUex2Cbp7gct;46JJ&$P!7TruOeb|%E%6!et{rEM32EPrk zROgT5?=W@d)21}N-s}Lzhp@&7lO7NRUy~l=`hm_(eU5A1C2q~1J0aI4x3v$|3#-n! zecK;9MO(D}IW>2NYp+hnL|K20EdOn(*kbNSra_*%6=&P!ETl!IPcWG#f-+f7(t;do zJGuIi)Qo7bIha@C0HdsQ;}tnQxrrpD^z9Wn?@MqHn%IDf=Kc_x=yUBQv!Ng>dL|Y4 zK;aN^MBvF^nwXM3+$OH4lnt!fzHcV+ zwXhhHPBhQN({84YA0bDzEd}_REVUD=R>~NoB-P37B1t4r1zevkmpM~^bUZ*NLD57s zz=_dxhdq?4>=|XJi4Aq(u_J&nPT?$!V$k+i^GmN z8-Gqu!HM;c7yXSU0g$XnN+ENUq|o>!daX&JY*;O53<-=2l)? zs+o|kd9R)DTKz`Vg@hE_xV#{#m^@24!L(bU-^r6?_8zui< z#}S-pW+qh#3oUF4>Jr9hPWCR+_-&Xiyb32$HqNOO&kdK0b7;uyK>MHvWCsF#hQ%3w zp$$SSw0n#AeYzt{=+q0`(2OavjE7QUKN!55+1}m|$tQjvg4JXbrM}mo!+B0aLkCV^osb&yF5Nx2#R zUq{j4e-5Ms7BRTi)J;xT6l*cHCZSmwaVDI}xk?ZaW8~67M7$LU(Ss^4^vu8THwxPy z$lR>CYkq}K$xq$dwlzm3C;fW*67|hu3GdTR)TCLGo{>!J)25V{wdhP1@Vrt7KHv=< zt~XowF+hY-fbeAaU;`3Q${USMD9p>me7{IyrM>a^A>sCt7)}u>jw|kNae;G}u{3}F zxf)tuX_W3*CrW?80<9Z1uay)%7g%Y6)V>|+ez^2-&N);YP}tyfT&d+rD5huG%yX1~ zO1@W9AeHw-30*Zkq(5-Gs|_g7`s)m6z%)pWw|Ij8)c1wi_Fi<>5!uz+5q}K;Wg;)U ztGrKu=i94VMx@uJ4T%4saFMpg?Zq z`>X#>CbfSQQ4GI}(R`P|iclee-N$LQ|Ch#ilx9n?>u#GBA*j|Ly~HKqZ0L2I;j{K3 zPt%7knI4s4elV?DI`cxnt=JhX&(SlHvwbp~emCVTLM1Q~l=yT8y!to&r4t^bAs`lX zQyydZL|?0sNQz6{PnXdC_K#anddj5@xn)<>(`-_)~DQ->5>m^ zxVo_U(eM4kW%EbiHDCaa@JA08`T*&{aFV0-;@cnlnzm)mN%EJE-b~80%+wqV!#9yw z(@QCBm)fDnq)M7veykT7@4}-rK(gy3^@NHA0Ct+`%^#VzTlTk4rENwDfo{%{)e58CgHB zE}5XhFaxq=x6arg3|RRflD2Y~I?HsF(Pn2j+3@X8 zxYSunwOh%LJT_|6XkO1gwLOtpYYT4UjDRxb1b&&a{^qwW{l`jz8W+!1i*$E|E~R59 zOe>XCdgZC;k#^S7^_$j!9BD+C;H93P3TY@ulxkLM2J>`WKn{3ze@w<__@Yp0L%_=@ zyzqhl&7@ey+bL?ZBX?C9-lDJZq+cas>-12yX>zTkQ(>0%EXw03XN7N9)V=pa$s@V# z0F&D&I5IHr5K7cJY3&%=O!zKNx4g20Z`tWCb{uG(Ub5ymf^|w&u!(f4xWd+QYO3P& z@Q$&-`WI?t41v7`AP9@Cx=3OWOX;4%oW#rabgBxm1BFksS(bXv=HQ1jieD&vV2w0& zly*V99z!D{m6w2Ckr)K722SIHTdO@_S{-|Jb5Z>!MJrDxy^m zy+*wAo2(4VNlPhwGp+Tm(v9y*W9}DqYV$tsvB7$ME=)Zg_7MM+q>D%=j*u!-czx>h z{?B9hu~d3h|9(BBOF9gWCKB=MIK8NlRIK`!q-;rUobngVW)}y4VVK4VFYK^NI}dE{VqD>Ghn#5f%Vwfty@=9M1DI^L0BPV=D%+VPyFD9Ivxwyq ztPo;G`#xTHU$2WVO5Hjj1YDmiL)elWX=Rp21)s3hg6|~sN11eyihE$| zb8;8e^U9)nC(ww3!2xG{CAWmRU+Wv6uFFg!Vv5XuN3HC7$@OpC++7q zG}uY!FsE6BG`uu(k(dvLE67n}-N8s9DeK2A!QNb9#sClB3uc@47Xb~eEKlCdFDI4b z;I$-G9t#}3)RqJXxqM27tI3~a6KCL;q03=1L2fm7QOGV;0REzP^V4<+%GhyO4Ng0> zX))YQ8;OFHhWz6wQ%8U-lBD@J|Ip#4G2VJU5b_xJ$oCJ5>K{~9<{<`+F?yAEC(%*< zt!j5d^BeH|0qDYum{6gqn-X<9nV1uh^B4xNRJAz+ov|65VnD-P-k&7PoNN=JKrSfL zb+3*Tw125TLxQhYmm^^pkz4f18g{aiAsx@$)eYWEnM|+KNSU7EmWR?y-kU}4b>^=7?O%WX~CT6|2nXM z@fzBO57T!B|FZ+eFb9og-L|!86rJVN-i}gD+e8^^X78D9_Q2I~x}zjfR=^iMNvcXK z8Z9?^0YEr{oRLsma^KwFIivlkE}8AxwlX}(9GKCt)-{o1WV!DZ!|L~1CkO}Ebo~|6 zJ~&!U*@4rb0%6z`+`P(*WmmDMqYmb+bi>ub4Erh_mu$yV$Ol)aVJlLpB$r=1*9tgH zFj2$kJc4%+@FAy^a@V?t!+-{^bH7Zg6qd1T`h<0Gy%3OJeOWC50Tyz!a%fr(P`n(={r z9{jMLV!q@?3&MHlZ_4ICm)K%4|!0#WE?d(c%ow@_^CEpnoW)hV5V6@q?ATwnihGVxLR>p1w%ap4DGoj8ssz^ z2WnUbm0jo3;~$bwQdnl@4L~P)Yb2{L<~StGW!uqz3+C~1z&42Q4(sY%Pbd4lishE60hywZwy3v7oCsStOoCle*wfepg$1mmfU2l=NtoF%4)T4?cfU zdKPV|(KUDLU^U^0XN{(*maAYFDI+?wBZ4bBiT04LjJ^?Ps$>A7M8?zzP*V`Meq|QZ zJ-A2^!ND!4yQL&=>_4-`{xrw`JSRetX%3p3A9;Vt@OrcN(J0j_f=(pQ4KcI_3fUoe zIXh;itBAmH3eMa42|Q_L$TqTJF0G;`_RW;z7mg);@x{S;{aCF*Urnju+6hU7U7H~A z^x)<>t>KcHP<8jRr*HA{ai3%IiGh6RPkXk5z($(gw3Lv1&q6&~PVvBc2HaJiie&pS zaXkq*oW1ivWAowhs~IxA+D%F;S}e7T>HRCJiN4U(){FBjiS|e7f+J80Hxg;?SP@1g zZn5uRPHqXgo8u8w?1vIK3c{|61+Oc0hXslHII9AsD$^ikcz4DZV7!;pCnHR!{zP_w zjlr0zJ%Bx_OXVKVmQ@Q=TGr%Q~X66os*)L$F`7r(m*5i^0iY!(R zA&MNOZ=BX9moyH=2p}{&_9Ga>cTwRUa(U*;tlWv>8=}&w*+I0w+brFIsrdamtotUY z9Pooln^@xIiKV4Ve;?}^wL>ug!>dYvkJmGYAPOF%XUipx6G3fHY?ZoQeLnQ&f{uU? zsh!tqOem-41;lP%AjqEYIRjXk4h-*elUiv4@$lMI$Mc`UQ7*KN<6l{4x}rQbK}0mr zMij`4+-mE^T4r=g4Jt@{jf=jOG6Z>_Y_o6}6ZWDmJ$T!5W_gSF_oai73ep{c-)+6wI(7>8PX& zdK$By3zR3cYbpx8;o`%UYH28)LFZ7!Zz4#gM@um!H93BNRz!Lq3st4$;O}!@S#aU1 zyRx93{AR?yjcp`j<_+lGC&Z!luaG-KZ;gR|WbTopwjfEXXK@~-fcfBRxF+9HwXoLVa3=wlZS zlK%_~Ls0 zwru2fBkF0fa;QRS_9I2|_Qm@J6cWgg^XJ4<=O)fP0voF2yB>Hk?;J3E`qo^m23G&c)O{TTMe$9YM*5kv_4W|w zw5GQFVEju61|#}X>kf;~baE{YIgDlOHpt&I^vVJLNu7C=2hp-k;h7Bt8k|sIk}g0v zzlYZs=vpaGGWGp4IH3sJWMRMoeIz)mQl>mNyE&~E!mx@Ff8g$#W^S8G>CluYUkjA^ z&yCb*I#k1v2e2R|*2lFW3ODR;iW13E9bAf5+pS^h^M{M>F){+4V&U7%Dva^7( z^qmjoXRgTeWS&NDB0}f%Sf;+%)R6+V#XSR>j1J$q(zAkDdqZpHeYAl@76HsSx0Ba% zN#_kUTcuGgkf&bWq`QMA-pCKReilM!(rw}~Rvqr>swhy7XkJci9WAtpg^I&SSR%@` zC_o!Y7Abn*PwxfUE_b^g(45M@eqPiy;?{+7Dvt_6$0s?5W+CXBBRrRya*zsHkmtT ziR?GV8MvmvkpaK@1-nS^xIbSEQz*TBYy{_vlPf}>o!3D`>e*>0=4ynu7~`&PpE%ERN-*fE zQlFM0xy>bA(I;ow0X8K@-P@)MIULtG!LSVu+&K z3iIq-%fNV*o3FT7++La-R-)1vd9Mpwn~U5Z8?p(YuNk-*L}b<~^Mbee(34x5lgx7u zGh-mG;9+zy%MUL+u;E?gd>-e;`*qCQL}kTXI80y~5PfMp&G8@F3WfU-)fNk!+ejvW zP#~9RVo|HPUI&See~w1UBX7M9M_gQV6H@9kIP6w4TwGieW2O=7Z+TZdP%O(^0!=wh zjqa>&A(*`748~INbi)%-T1Tacy2e_PQa@8uKu2+j$r%N~Z&_l?#%=V>{dtPpY#9t~ z=`^`R<+8Xrn3>6($&*il>#OLhO+SHz&Vi~pfoS>Zrthuchs`P`Kd#9}?ng~*@DtgM zfj)f%euvx${SbHjD&1&qt<@$WUWf|Ib&^=Ff*Lm515q1{E=^wa95(HuDPAzSagTQb zj_3xZ+lc?!zYa)U3$2%KBO}`)i*&z`Tn*i(Y?R!^wqfS4whb1vj$d`Q!cj)g(l}hY zu)*(-h+y#GpQ5s&VAhRe?guKY?&(g5v3`Kby1}^g`lzT4fTe&Rbs$5vFOs+7|9-0y z3y|qTCJtj23phdn1%!Q6iVPA`9@NSZZmx_ozzfc^)gI?yph{z)T7+YfqBO>?CyP_y zlckW!#ib^xG^1}Qtyp^Pt9?^EEqM*AI3gd}b`zpg?mBt)m8{~?M7i=<5^1;VBl4l+ z=C4TpDIC}n>`VC5rBBkVOfQF3fnm{E-;IdIT7(m-E7X7cCWNmt2P=TrnlBjRU`iZ+ z8A$LSc4B`oY-mfybz5m6g?F8Sbt4w7+cAQwlwqC0i@dX%+i(@7yS%C zA?qNaqos$4RNa6wL84nFn=C5?vhiQ#5!OWLdb`i(ob(W5)*AM4w0ik#2~*dfqFw%8 zXJKOa1yQa)A@{}I19P)*g<*@%;+)Zh!EiS>AJFGfmv!o-+Fd`dA-(+!y1MydLrf>E zME@W}^96R!Rd}ch%~y z&{;H*5nvCd7P!2PQt`7v4FdY|o<+l(pKhL&x2d(Gay!||M&}QE0yCedL!xNr0Uv1mPqd3M>UzJ^ZIUpz^qGl+q=Qi{ZVZlZo&uq__z|>J)h=_ z_KJdC>5T9&*{4QEyGBMC&3l9?!}#dhG?4td(vJ%^fQI7iJ+T-$)FXiBX;FIMM*zps zrMXFs5ElH?y-G1L48l{COR*4~(prT`iV)`9TE(EGADBTs_E4=~Qmq?>NOp@8>KYd) zd6vpn+AcErjJHC#VQZAQ zd~wD=CYXgSa&P!C1X9FC9&Fn#K=QNO#OmoFO)na&Hq`hzf=O2&Z1nRei?lKL<1@NR zRUnzhCADZ?AA#O8rb~Js+T3!0OH?0x-HQD_bO7g8d4YrdNF==eQ`a_DwsU*f2~USo zfb5sxfh$99joNSManafjwu9YNboI#qu zC#4%i*Ct01Q+(yV=QCn8wEjrx>=A*1wstj2>t%7lH3qAr0p3MHDGolcda)jSoqvdm zijSA9#2Ck1TBoD{=W+uTKR}#X+F|ApP9Z=ZF%OV$8TFW-yQQ$9Y4&~gy?vHwBgn;& z01`g`nWJEWisLUIFdnk&fZOGF5aQ1^Rn++tluc4SjN2TJbPA&zuwoPz$n6&UGimMC z8wb~K@sJsDsx~T~Q`x?mQJ7QPW!yg(j4uovu@#>TL zybtM2WAcVNa#>K5nT5oO3X=V2PcV^d0g6<8am=kewy^2sU#F!Y2E;&Hep8d>KXK~E#u=Z~#`&-E;H4tDy`I;z|(g2!C| z51`KdWnBLaS+nLX8v0FXqHHniJV9s|p|Uu}h*F1cROIt=i@T<9B{5D*P1?;cHd6 zy%+2i3ZsYb5Y^$}PolYFwAY4502)HBP0@LkFCl5Lcm1KneBD4erPO2t4g>b0yFVX% zzntFX&kN0Z=`G%oeirsW@c({n7t-f=qx3!W~ZXN zEKx5kpe~KSS7KhX4ZD9@N<`R8_w1c3&Hfe%KQj6bAbg(tT7h{Y7TWVJ`|9Semq`h%^ zWzoBErZ`DkyuOt*3j}R6^luRZXPU*4JF9N&e1j*y{PboAvczrqX%XL4Mad~Uy}aW7 zlkn<+!^zm{;PjF~k+|YtbPD~!MeGO4VlFruG@$69-vFiU7v(7BrX+-a`-1~c)J-7^ zpbve(6pofn;hDq8j3C|L6m&-93$y2en*K;Dk8Cz4m@ZuPA&>87`sjFInispOTk5LA zwCaCJ5p~f`+dh;#+c}}Ee*aB=^+Unsa_-pL@V`a2UrDeX)+pvfHqiB`uE_AsNF)^b z`h)J9((so9tAcn)s#;4dOr&^W9!uBk17l-(AoabM{TAHO{&ogWqGS!aWT~SMPg1fd z-rX|Zy`uA{b4Uvy>10aLJ)w?ow=DNoYAYa}RBn~Fv-{ji&&djp8`WDg(eOGE##)41 z!ie*13nuqJF@jfdG-2GOcf7OFOB0g%2>+d5GPUBRTtkq7@CpZtE&|TsxStv{gSP^G zYeEz2FiT~53y9||5?=(UCEW7Jr?o zk$w&JRe*>&0%2Db6T-!*7U=_i2#Y2VH*z6=-K0?NYv{R&8w4uV&vXX5P6I zfQ#jRq_qwcR0J||J59XCtp%1RF6{q&hOtk+YPo8KNeuRtjoh8AGhg__B-7B7 zm1E*X!QL%2lcKhch;zmW+K4-w2C(xqdqAehFLQ|@{L4c)syLTTSLf7RGx30}!n8Fa z{Wqs$s|}^oCYO*RX*yRM?ty(%QTD{RoA&)ZN1R zHINS2E-lnA_F(G+qdirdILa7w3FMtQ)gFR7t3UZ~i1w3@C&L&S`w@R1J`jCtWOGj` z_Zs;w$CSDsD*v>7X!`uwJN} z$k$|X9>m3bvKLQJHIYV8BS(zK08Kg^fZdfd1BsG`Ndb)N5pgwJSK^2L*>8wzm=HjW`SP zYbkm4212Vl<2=Q+$7vh3&S-;IJLPZNKdnkB2^_Y28%qRQ^exp|`Lez~vk7-gc6mYb zr)758tqp{CeW^Rk0{s?y=loCsc=#)H4ya@6)gTl%Dutpy-{9DXKsBuWv zCnn8ty(r~ha)y29b-^1jb52Fu^w184WllUhsPu*T4E^#Uiai)oF#gXp`mnFjnOkW+AN zj%a)-Mj_I=O%%!sq_nJK1y}4Gp@7`bIi)5473bI1Xxipm9736~;b>B}a*^dbJ%~W%GJLvlv&=DE*3$Q2N zkw*Nh`%Stb>n}vxL(q`XH}m?uFi`uAnuaR?n)qRUfA=Kwltt;PXCj?shJjV$1CRi3GfmtSkhvFjj z_MdQt)zXs~{c*J^5{Vs3Vh8}{5BX{fFP|)C>CqVuz=1vv5wBdc*S6I*Z_@QETgX{( zJI|*9Cnwhy^(+HXq50alSC;ijl-Fxq2gdYuc|Gq75y`hUreBJ&LrM5?0s#O3iaDu% zY=nVpZd{TA3k5gn){(tni^rt4TPxt5N#@OfrR=%I$6kO0ep|t&_v15+F-p|C zFA|IVf|m&_((lQjG96q%6SsFm`_K^=CZ-`uSz%PC%&Mh~*Ks1V#$)EQIo1OpiieNSfsAQT zEIU>-thKp)7&hf6OED^{7d%-F9O_yuti5PhUi5?>g83+4Cf%BNBM;(96gz>Sjsv+( zBZMdsN?pf_t%Bke5Z$JbYa*Fjow0j7n!wi+LjNTmRN&p_V18@Xykj5Av-~Pl7PtUi z(rsDPr)O8|>CSo!V|l^DNNoKPAwOn2b)A@*QC(g{^mma=gZRPXv{-gu>)zOL%+>kbL;|b`p9@h=2d!c{on?e4(Csbm!;qcwhLVT9KyWzGMghWWe(>Axd?5;2ff< z?7I!RW#DUd32>QDiY}Ev8H*FL?L3&Kw9;;9OlnnMv)57Ej!z3h4|(C)^ow}wJp_g9 zzzi}9LJ&Mkf78@iP7}sJ$h2)Gt?w95J-C&Tc|zc7KX%N;g)i?B)2{;h8^kNbDvb2rz9r|}y`#Q*KI_O|_Maq)&_bdiCgbEZ)d zL2yH9g#~W~(~wBzj?6A(rGtjBktjLfZ;cp7<*53;Lyeoaxx-DP85@Z#rl1qDdfEJQ z)4ha3`qc;8zV-5Y-2~-<-mRcfWo5I{>q1?ko_|riwT4RpMRSvIZqQ|Fb_Qac#Wz}!jY!z>u6e7 z(;bCc`L(TD&@ZHP*=H>BKE3HZt{Dr8G@k+KZVyu{JuTN@iKC{Az{;EcNFPLDh~NR5 zFlY^8)Bt(ciR*?COc9Yn^c#@84oud=#rKS2hlqM$i3Xv4(C~{8qz6n1-~@3Z{&NNN zgqrV@6Tldou=^*V?^7s+Ec4)gk5z%YX44FvlSMKXV_C=eIbfpZk^>I~a-x;QLoYF+ zB5Po6RM1x`s0ig)1sjE02bU_)$fK$z?XusMG)9X84t(vpy8jji-l!Du?+HSn?oa+f z@3|o`%Qj{HO*mFjl20WhtPRUF4L_Jv#8HJ!@cAlH|Al}phJ((6f-OqO)P^gWY4MFk z{=BwXYJBU`c|OjcrJUkxsWfk8H9>I0z2s{iX4O-;O0~Yekjk(UCJq;*hsDa^g=G;C z(i(um1cuBME@prvY5>-{F8cV7hIgP8(Nvq%Rkh1jrdQJrh&y5YNm6b_`;B+{23>PP zcZf}13d-aoOQXe^AUek3r;zV@OHVG7h>Wlxx&6`jDyRHYE*;hOpRda64*+tdldh65 zuLyV%!|j0sOA6wJ15FB|Ct(_9l#2*qdKzff;8Mti5z&S4%|#H=4Fb`P9MKKkQB&VG zaWgWreYngAe`X|4V!?ZWtS2SXFXIqjJ;Kp#p@@myze3!wZsTb$W65a70xitfP6)ls z^!bKeg=S799QGf0|Bp^#9ZUBop13*~p7-k8T_BN9CPdebu=+L1wX=-mx7OIX~rswzzd2x{nJk# ze8*`wO_%EHlG`txq-ve4;=6YA{OGkeW4ZA|LyzVH0{GJppbJm)G3!z&;T;bkAtJzROf@6M9IQLst*a(R*wm^W&e1vA8O^p=Qh$`c z0k9kX{3f&&N<0+!2}?x%1{nz0euE>Rd}AXD{qK(52U$4698!3cIVvFlN*G-r%6QoM z4e>L0-1ayR<^3&`h7xChSd#LaV(5+izB$DVaxm$w+Gc2%@Vj^x*9_&~C}dLv@Unryo%UrV>0V1^E! zw=}~MkuRVo#zKy9eq+smg;lwhb%nAyq`mjwvO7TF@TVesJe9#J@hou6}w^C2m_KPOnVM zdb@Gh@2_6fS|(RJPJT`lIIbyX3xS-s-2+)3t`%;Qs$Iu1VBMm+>}TBSrRP!*I})HatyOvXz8M2<{#@VOy?jUAduxUg#7iBC`aXKb?u7r^aYJW!?2rn&RR>&T{utnA5v zSl4H74U^KqFB&G}!)4tA>jpzUZ_hHK+cx1$xS{mzU7FdUWsY`0=_a8T&oFiQbo!@y z*-b+&)~$1iEgvIp+f1X~Mj_^2c3)NQJc9JG;kSNRmn5cen0kH&>;40GD`jeshyKvD zo%|59^zm634+FE(6shzvjuIE;u?g0`%tH#rFs0?hkpUkz2XC4CG37&Y3qcOXwUZPR z?yIn)tSdLNhDCq&utBl}TFaBRC}j;WnBp4{ioa*pfyxx#`jnGnf>jghrGuQ;0Oka= zHlgm3Y4XHF8lb=9cv?6ywask*?TF`;fgW6TYi2e)uuRKSd`y8Xw+5jAgy+rP!w6w8 zP*8s#u?x>d!fZ&|1B4J5RzQU`h3qN74w6G!qYm#VyKoKmLh8f08AS4~%o&6{rlRCQ zGL!oz_ahh}VcJDkXex#mW-B&ehW!1%r0@UD^ZtKH-${;F`ThSG__hBS_(K0bq;E?f z7dK`lPe)5PTXS(|Co5ZPivNYcS9knB2>d{%%QdIe_IomXl4x~q6)Ny(hdnFY$z;n+ zPZVt7G(D-JOPO}mIN_0gQgYOi#XAtOrNIAC_Eu4GW?R=V?(XjH?h@SH-QAr4Apr_^ zcL?t8?(P;`3x}XVgM8%lc}Ms8ulgUOM%7bwQ5SQ`UbfeKsK>zyC;qN5y-;$L<(uPn zDcIxx^*5|BEWU`eH(ozkin;uRH&#DPj$1>%F^BjxCegL12mn|fNHdZY2peMqnMkmV zA@8D}dYvFL*DP10uTsGxeSj>56_nv+FtI>TFjtF*PyyLsD0iV3vanHuyOp(IBwCczTgz5Pe;witsk|lDPq;F*mWgC)q5Xdvm5QGl4mQ zK>G2IQvU2UD%TsHHj$lm+4d&Rb7TgaGYti2wwG0YYZ>%YeviOr4PuvsWQ(fI0D^j}skHKI)QB>4KfN0hGJ*~HwR`YiAn9J5$<*}7FgDFQr3$H@_-gl6iGgu zpF85v)cia{E-d2@32c-QF3?_mEMoQ{al(0AE7-rX1|HIJ1_bLtcRQ4>S?jT{<&IIm zikaBl)4v?WdHX48y@1!BN?;I?F}*Zinm@t8U(Lx8PEIT2kUHPwy5qX(^AUxojVQAy z-HVP=+X94%d&J5C7TdTaAxUU@x^>3Y|ONUA^D#=)JPMc(F`e`x??sda$xj(8Bs;RLoA{! z$iDuEtnr@)@xP|B-`_Ks^ueZq`$5>q_pePw*~Hb&Lc-h3!r9%%$?>nn_}K|#vfv^p zA@Ac{cn_2p{4{t&yflUw%uL@QP!~7Wn`QVEEen0~yHlw9f{=GQ9^p*yD8l3*u2nS* z)ZVqevcKNk|3WZEZ}@D^y4zm}uSQfXIEnIH#ik2kVNn^=A6f7Pj1b-DK7V$YENX(x zx|D(Z6MhY+sH0RU-xeiCiX5j4{mUl+<~suyU!{Fb5^Q?cu`KGdET1}c^m7s0(H`5* zv@vZ$r}Pz{P)kO7YdNDy1w}!-kp@{5gG} zX4uf@eDhz;kr=%jtx1N zX-q9*5#%v><`KX7Y|CMU=TdOK5CwJ;MjUMSG+9{K-Jh5S>(f6HFugDOO7Q%Sn- zL-wl>ARyfTM)o#;nD~s$oXjoUjI1mi?JcaB+`ZkW5_lblSP;a0a}KbL^07J^P2rs# zP)1*s5~S!popOl4MO(+SAKudzn@UthaMm$3hyOGU6_?A)9dmcyDMtR>F3=(H0`WTmI(+0LbNdD56#VB{Yj(4RV@tS( zUxbngz0u~7AI$;3ZepY9<2F7%AasJkM%WppR!@xhvMhFFF*iR{kIC#{ z9#iy$Rr`A1U!6}hFlRs6=BOdLsAdLeeII#h60ou85=uMRv=pBS%c1uPv2|kJE?H=) zjOKdt5E;3IbImMa{-Y)T)P=vbutBo5tXzn;AovXlRU-A>bm3N%+&(9cQF>kgs5&3%(=h#OrA} zVn2y5thygI+uXJ%JIH%(E*h9YcwlN!g*I>pt*%x<9N~8^Ncvktr@BP^kRGmqzrnpp z!XyQ7D6T&feG%CqJ;a9W9(-XFev0^%^rVqP>zQ|GM+f@lf$rvP^zT=rSNRvuw3*J(-m z1KlQ7Hsj$5ixxaa-rL)Y&VWp`;Both##j7`_RpE)jYC2$unehW%6sX zI55Kr1L^ugcye*pVblXb>W*S$1mW@blzX%ZWT-9dajxTVYjwFtN2TWLu28z9G7dx4f?%tArzLN|HTi5v;$Wq2<`5W3 zKWNkpG}VUj3VRQJWlEZ%j+;fV)ir{CeQQ+HbuN1zjB;B$Vd6a~bM$Rjt(yA*c^?Bn zUrlrP?&KF#vZFOI>vHeq?#PkKteajKp@%k9pQ)Rc?OcItl`o$2O?QybW=DHue7Q^` z@}Mkd8j%TKRvk{!X0QzFGiO&}&G}^e%emmh9RCB>#E@=oK-HN#daPH>?5{QVc&;K5 z;y~1rhNeh0i;`yLgL6VUS^2i6_@b~W!t67Gq*9HPc|2=9Lg%)WB}Y5cVaC$%uHXcw z`tG1q+o)u0%R61<>a@O#Ekp-nqZ#PTZhe?!+(vr%r9gh=ea2M!d?e>!svEzqEz7-S zZI~TyC?786QdsQR%(OlyaPPpOL@LL=D)$Jx7S4KQ28DKw7(p{rJWlkU0X6pspa73l zke>Wx!*4ZZl@fVzR%6q+DO4^OE~!<7I+x3o0N;f663uTIz2v~0ec2ni`UR9C7pe7HD_RWOnoEQ9u<2fqZx84 zi-Hn&Ss4HZE9vh!p2={h?LJ4JA3LhOe}R2C>MXtK1$oq7yQE8x#vNfgBYwdGUYatm zy*Y+)3@w$0Lu<}c2?I0kcrB;nXTia>n=Ne>!yrW~XYR8xnb$yHrhHZ}qP}VgdtCso zE!n^m)*OLt-Zk0&+M-jHbe9!v6M~un{AHOPYSp{lI@2Ljk0}A(XFC=CQ4z3S6A5+ z`sBATgi)CyFZwPixX-F5U9w-*xyI|~wP;^iaqWk?#^>?~vR&2aFH~Zf4qA0H>dSu# z6+KK)>8L+{o8PS0$x(gAE2>FmyiqKwQF{iejxWd(t3Ug!(is#X)4syuCb-FNfB!{& zVEH+8o||rye0{%izDstSqGfbF`9kg4a&;@BBe}rH@k@^W9nso(){I!k$NIS1sp%)d z^IA7hWd5I7SG5z%(BXq{(iX0HvM@xe}TR*#WI6{YRq0rxZUu-tp@H|#%$+CPu3|2@>gvZkZk zKSJJ^4~}xt|G$TtjDm!kg`@i)K2?Q}Sm>|tw@7{44NVK<&GFl2up{gc1}9Y&tV)3R z99LblGJ?#U>QlTWVsh~hM=m>203~=Nd-q*k&8w1amL;>>C#F3Ae+gwV!i zj<5fh4-VCbyUd<9(Auz2fA2cHUhdJcRtlge$k7!e^Akw-0HG$fIR^Mu0R~ClWCthY zHhVSwEY4-UrC1+_S@0KsWtIVNr9K8T4Uwd4Dcnppf3!H+gFDk^5|$k1Lym=dg$b6( zDF?MrQ+g?@x>aXCn^&<$F`_KV+w4v~d|UEbh|)dROY1ybmAHv;EjzOH?KmH%QeF#7 zFJ}+|aw_Fb{lk1Es*O$h_ln)_dcAsdsjh`SSK2jRG*+dxCBi=vJvP^5ss-v+6>=)f z;qiGDb`B3mF|s(^)9o3ATv;y5)rzl0r`Y7Ohf`MDE1Z^2#69bih~X+nd@c%Bt1_Ar zBVseS^{w%Iy7F6Wv!7p)C0nWURjghfHyyKY*x_S+}oqkd#29#e`}ui_Yxa_21IE#w6bEO|`#EkFd$o zVjY-ZU{=Y(hPlVq-Fbemz(7n;anc3AU!s39F7x`7Ev#g_0)AQ|^CcVs-#(|n_&o|P zE20VkS6^m(o13wGY*ig zo=gs1g2i*+2nzKjgiHhA1rmv*9mf}PhL{3&a)GW+aWd`|jH%;3630yd%*1IA(ywO6 z;&+|UZ~8{9*h=fLYCfL~OkG}4P$E+$0NSluXdK!?PtUg(g7F0)k4XJD=}&04IN0!t zoiHb((7F6xdz)k_qZjSKJ4lOycz0eZSl$liYw9EsA1&YGrw!-8&@~UHcI>EbK#xz@ zr~F9dcI5%$`Tpy6n2-^hZ3%0ZU#d#S$12ClDc(g&SEqXg?{w>WhH%}I!ya$RiccL6 zXj`^C=hp;#i|q0|^CN~evt-0kG2GJ7u;Wm$U4kCF81LEYk*asptU8BFvs({~J%CZenV0A?jgcZ~lKg%R^c=?&!;yZ^P@O^EgqU znO|fO>L{ZKMfaME3ytNSO%eJ)gT&6F4#EXV$^K zU94#~MKhQReKV^IZ5G`5ON7@G7k>G&7hFr@AthwEJ&P$hZFPEGdiD5jcX=5;&$v;8 zR1OQj!0ZwUYwmuJ@_nTl?sKj76Gh0y7$_NZ&Rw~)5iSNQgFFD)LH)3hA+AV>&;|zB zh*0r~?#z&cs6$2uuUc20zeUabX4PYu88*Q+Lc#Aj9!MRGVUFB|Wfh=J$we#ANpTsB zrQ|stQSqbvW_48=#fl5`Uo;g(7#ZA>!JTvwO2WG>9sUD*l*M3m7B%3 zD>+1!&IJrJk>^Q4>$aA0VkzL+F0SNBhH%0bb*XrSy?1XpGq4F@6lKYtZPeyT;qm_} z>{B~1c>bi$8G^piCIr08h;g`@*MR$*`i8&vZc(~uzJTzl=sG&>S6ycPk?0O??5}Y>Qd-leo2|9uZ)GP!# z64L;xXpTY+i(F$k5^W!J>X;*Vs=VSMqQSMPXWO*{qKRo6rEF%svA(vdlGexo?FC75 z%~vOz9<3E`u0Mn*5(}g(Ep9Z)t){#SG7{THktn)^VDsX$sRGSAJ~faZ4aqxGGpzGv z^@`n;AfZB41gOY{UylVZ$M~x_orFR^QEaE^AOUQT33ESnzoh&JpA>9SJ0w@=SysqW z#QMEDUUD{|z08=|>R@TEpPWie(*zW#y{s#3Z=TLhUxw?jO6#a$p!9Y{M`LFvdUQBP z->8DxrRBSS-#PT0#w2b^S(#1*CKH)lP_^%CV(^9sc5Px-e&O&?3R#`Z_{I152JD<> z)9MaeW3``RW2fj>79Lk=2wehjJ+Nl*I|VE+M14Vu5b><);z_?4`+UA?BPN=sRdrWa z6~KP*e6Ln8bf2pBp-hI+DzG%Kw1?TsP<}OPtU10G3p<^Z7D(wCm;MC+I55nP)$)_u z_sGa`Z)vwW$z0ib>M$J$uWeSr22474co(hI?6BFR4tRaeEY7NDidUABKvKD4YNXMo z5An*^>GQ2s?ECqc7KrMnH2i@10#ahpS-BJXh-z0FNDQQg^N+}t(M~sVP^|^ugsblN zgcKfO#sG*6Xkx2!0KOT?T92Mp2HLTH8ZLHCA+byhBdApC#n{$O?D(!wqkn}^zErUPP4pSte^htF#fp%<_qMy#fwJYwsS^aR=OQU<9<;Q*Util z;cZHN{n#^6nc5pBY5jAJlKgpt+TDKwGLES+3@#mPKr|NS3a9> zjH^=}R%IP5vDY@9AoZU%QlyJA&98sYIA4@RKzEFNG-V~87iP=iBcoO`BCX0fq1+BR zy?HknS2xnK7K;m@EkVn5IotrX++x#83XhZ&4jmF)Dki)5@h+Bk&+w(P%SIykx{ph; zpI<<9R<1(oisMLGxFI3_sfno8y8g`B2+4ImBbI$Y=VyNt`}ffjp~bYMR5gwEfXfFFqE%4uiB3OgZFp zE5y|IKfLg%R{HCvnSOz?X)T0io}L)RE?~L5EuKS@gkZ-IudBH zA@sPIj7+AZWA0QSgfn6FPUXkI3X(n4q)rK}9L%~YJiolj(2En2;&&Qvf}sv7Wwwy{ zG8-Kh4oL&};T?D(_-#^o_!}dk5LYJngQ4P^+Z|wrbS3}qnMk3mjIt9s8Z~E|*eyB}rJkWfKIP#cRi^Dx=dCrKY zK27rQ;z6N}k=lV?tqf|)ybfqBx5Qa5;rHOQ=UI4dMZyTc6g`y+=`Kn+Z#va3MX?tY{;3lDF7@kB4R2IIyp5w+Ndy)S^-vsn_&>bh%4Qvbx(SWH_zf{l4WCi9a3pU078q>;SnHb|StPzhIk<;PxXOhw%ovYG6_jT#q$j2w zCa$#lIGaRT=2-i%pc5y8)*eP9|$dG-BF!aqZe`>e4a9AHP{c$+vpY zjjGwpF{GdPwYH+u}V*4b1xscLSO^0e2i6kf+RaVy}r$B-_eZvabtSVY_$?2!mk zR}oTXK3!e@89hyDh8br>R3u%l)nmf&dvpm)(0jvmzMM>SL19R`d5_H@j9Yk7VX!Yi z1G73SDfl;sM9+Et_x>?B=qrD{S;;^Yq-#jA`3M(=@xm)it(zO(I6uXk8`ev|S%`re zc)i2ueYv#es1>YS(=CA*%E>ahky`CafG9m)%xG-d)Xd>2no`qiSX*_anm!Fn?gIM)_)k4W5DjoFhXcJVm z0z5JBYzMCk2LrOuZj=qjjDBA$KI>6zgt%T4+M+iv+#mMuur3mcPPr^5))-?F&VM58 zw4;x04a>0f0UA-rZbWb!KrsvwTJ|;Bq$x4J6xKI!l$ajk;L$eOpkZTOw-@)N$}a33 zAa(d{Tf}$)P2Xk;_9L~|G~Jp5Ox86ur)RSV=C$68{^WN5jLZHvViSqHIJ@}h{=t3Z zcC!B_V)Jmfv1eAWaFDdIw-B>7adfn>r%<&pF;{Z5|NP(Yf8}^pN&o2tj=ZaLr@q@}}sc}HknE@7LWDWs&(IFUq0n>@nkP*CLh(*}-x*(Z= z>tBHfDXy9G3{aVr3+pKXPOI~E=8hAM&5S3%+gy#Y-Iy(GrkTdm6&75~>*91!!)98~ zRKI`j^V~OWYozMpKGcn8D1%&Pi=;2Mx)ov@4pLaBzFeNyH*$^mjVkjgw`4VwHlv$) z%%qx0q|2%n5_F1hnnib;ACBYP$Me$~|NMu3L9-vNI6C?W$1NO0Bc5PJO?G~od|(K; z&p-kTCtPj;U-y6jkjDAdS&b2H{l*(U;kg=*v2cyaX^iI$5MO)MZz7qB@0hrka$N?W zarcvDJ8Gj02ylV&E>Mk+ixY|IG3At8I7}5&D=gx6LV=OwNMnrWTN%GHku;3gV zK^K>>5b%0B>tfb1B3ZC1S#n+LuXi+HRjp`fYSz^FU-+F7mrtVF+&D&T6#aspFHqKE z;S?x^X%tAy@LBK6;?RyiQRgm{!*kQ@QB?uW+a!?hjKy=m^Lio)ddMExJQJEm&{-m1 zNKR^TR2;GJi&@?uSCwNtXwn`o3F;0h-89O_-~U$05ummDX?uTfMIn?uhKAg5OsW!H zpLOWIv`3JCz0Kp75W{E8RbG_wQy9QgI>y^h-E{MVj%_#kmM+piLYW0c5-FeY9ES3| z1!L2a7`l9_c3pmdSSdKp?L)LPg)VI=9VFD zFDEjnF%*3M`TB(U2~!TVc!FdSA^+Pdh#Tl3Y;wqttaGV;94^4lPHnf>mH@eWCfh|Y zHv^gP0=i6UReVI#el5AVRz0hUtp)~H@N@BGNZ4T0UQXopOvK$VX27e zDOLyn>MwuwZEx4;Ba@_qQ6{s0Q2K=PJK1C7w6+m2hM?-Q!t-_=p}aAsPsbO;vLh6& zw;b!CQ~e1C@Gc})P1maJ^2j2o&vhDyxxX_r3x9kA+a|S7%ofP%g>3-wBW|R%q#uH+ zXSR%ijJ!DN`>+4ZQ2bMhf6Mnz-fJPl+Oe#0r_c@#)%c;%8@# z5qU2kA)`5l{#B6A4pg4ar@(&bym*=h0z1EUc7m=9NaDdV6xRX*!w)#3dRc2tO_v7o z#BM6*I(cl>m2eeL`T2cOL0MY>iJ-IR?#L@bOfGZJ*e+U^|fwm zd>O~e+3$WSpPt=%^KIXiK9~>leOdV?$qlH$mF$(u4(pDXoohcS=Dz||mKn!RZA-8H zyw=MR!Aqoyz@@00b`}tN5GleKkXPQE-)O6!UKgbG3mNZ$2_I)m8cB=ZHO^|FGfVv7 zx`s+LBwdM{UL(m|Ax|frA)As`Iu{5Mj6)s37!$|QAL1CBWC2}8Q6wf(bz+HWB5M)Zpjkd;&>f&9MJHJh0kQ5v$X*2|wlQAyE>*_`M zH2X?N+lGef6w=yEts5#>V3o?+^ZY(EQg9cAG_0M4ol5OuUTIN>>s7gVfwsD;0j4i= zhO+H-B99)5q_5eEL*uU;LDJtzFpLUJF-H*^B$aRD)5Ro>R~r0Cd&?r+TSz{0zg-qa zXQ4-*o&Lb{PZ0hW9thSXY^D$M@P-Bfk^I;2s5*Il1R8&U`RhaGKpk($G^v z7r}T7X=Y%AuhLdg#%MCtODc-+-X&8OnNvk$p|~+ku@J>>?kMcg$ax`tfhJ7LCseNfdH2OPmA}5A?a_ z=+qjNk_Q02Qsesa0*gUTH2MLB_{ut)#(4!iu!6H6Vz5%QQb3l?Sq&aBVoG!rxbS_7 zNjW2Uruhgtj*Rc`6{SuwEj2+y+$xx{H33~gR`7-)9*v^~e&*kU z?_+0{uA?q2ffxCl1~|oCW#>N^00i~>FF@f#pd=Kq$1s;&da<`Zy`G9pmK!*C0EV>8 zud$S9Vmw=VM1)`)!z+c93ZVKkj@#^ug^q`@|C(<4ryc%np?`uD?72+Lo{!muE;JAj z{(o(uN+0!GdnZ$h|0&`YY5sIWmmu)xQt*}W`SKOqwg@wc3zF=Ei;~8M_KTCO9vr4z zXYn%EPfFS`NZLReW;$j%y3(B}|Er+fES2HF0(L)-Vd3%X7O%{SJ0D0rA@c{%Ti+b3 zht+MT%eSY88EO#2kq6L4@)Ide)lQxLDV?pt&xyOy)0qrT)7OVxAe=|9*8Qp=*jGYM zOtP&QpKyhCf+hrZ#E7oq0Tn{}x4V4AUnY-)VRzIvcBT|yTX4!l2C`pwFRG8Pq?cD# z#NBBGR)&;$S!%M>L^Bey5Tj2|yj`24^9tFMCy!-SiM1Ll$CcxQViXX(V2w@Gjday1 zH!Nw2oedN|_4lZ0 z%hObhG%1U#@i#6mdg5bO71HoE6}sWgoFGBbX3Q;=Cm5HqrV=Uz`y@twT+&Y~e%!O3 z97Haaf%-AB6FWV~=^-ss+ID0jkqeBEw9!~jo%{+mhhyZ-6y-GK$Ha%DG<-LJ06E17 zMA({M9!a8AI1nl}jo_*-1o|;hbv^56{b2Gi2d*_{Sg!OavO~mi zQB9TiZ;MSa&RCSirt!(EbxkpDXbMk7!I_IFW=QQ#q)N82N96H4L4^v8*sdUm zoyfA$^;F-odZ@%MG?DMJmow{vCa;KYC780BRnJz;kz)p@ak%Z5@)ekK8dR<_q$H?h zhXjr1myV)EL>{}@>h3GsIA-yC7&!*Ar4vD>3u=Mi`xejBX}LZ=dQcHJl1F@d#(qZg>vuL{HCj z-4q4dIB_hI8`VMJ6Kk>^aubCn>Yd|Akgdj9#mdH6Gwd#l%gPMI@6M)qrtqT&@^7SG zgCxluJdID7AlW~*TUggRdD9dT%UL8YXf~^X>3GQxa_RA{@$(3LsTPv^@IWcfB46R7A;9rIoZQhfiob zMBKtWQ!;taPngC5e}*Y9o?-lslX(q>*Ly`4El`?wt-0Qs@{wo^vmZ3isrldwTC~oo zCE&j*&RwFtI?jbgb?5MuDVBbTmLxsJy$kLeG48=N@hxNAORP*2f(Bhh=oLCI$=OvP z(1v^?%rPR|@zJ`{vvvc;=IPTj=!13{g*^8NY-tNbF4zR~WpLhQM!G`MxS8 zS5@yIY1QsSgzEQYR~zS|cP?Z4En=Q~1wM2HcIfwkQnyZF`XOwB350`1KyyLsrK+IV ze{ub?I@-LqBQ`p51#t^#5(oJaBkBpk24C>8Z6d}453GqQ9A!sIP#wz8z#SQ$Ho35K zaoL5|srDLIbdzE+2;@wH_Dl@%j2nO#4xnMkEx4R<7ej Y@dl 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 z%0D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 @jZU(4oD{S&ec2~2y=1vz~ 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

7fGX2vrhuMkh`WIVIMu5t~s@pp}3LB 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 literal 0 HcmV?d00001 From 47888cd1b25152ef2cfce1d72e054b904ea8ced6 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:09:12 -0400 Subject: [PATCH 004/121] Fix: Correct `Ready` msgs for docker_util This fixes all the 'problem connecting' errors for me --- pysimplesql/docker_utils.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pysimplesql/docker_utils.py b/pysimplesql/docker_utils.py index 4191aad5..61feb4b9 100644 --- a/pysimplesql/docker_utils.py +++ b/pysimplesql/docker_utils.py @@ -10,7 +10,7 @@ import docker -from pysimplesql import ProgressBar +from pysimplesql import Popup, ProgressBar # Set the logging level here (NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL) logger = logging.getLogger(__name__) @@ -61,7 +61,11 @@ def docker_image_pull(image: str, latest: bool = True) -> None: :param latest: Ensure that the latest docker image is used (updates the local image) :return: """ - client = docker.from_env() + try: + client = docker.from_env() + except docker.errors.DockerException as e: + popup = Popup() + popup.ok("Error", f"Error opening docker. Is Docker Desktop open?/n{e}"), # Check if the installed image is installed, and if it is the latest. # Also check to see if the latest was requested in the function call if docker_image_installed(image): @@ -138,7 +142,7 @@ def docker_container_start( container.start() # Wait for the container to be fully initialized - retries = 3 + retries = 25 progress_bar = ProgressBar( title="Waiting for container to start", max_value=retries, hide_delay=1000 ) @@ -148,8 +152,13 @@ def docker_container_start( logs = container.logs().decode("utf-8") # TODO: Refactor to include callback or other mechanism to determine if # a container is fully initialized, since this needs to be more general - # purpose. For now, this should work in both Postgres and MySQL - if "ready" in logs and "connect" in logs: + # purpose. For now, this should work in both MySQL/Postgres/SqlServer + ready_msg = [ + "MySQL init process done. Ready for start up", + "PostgreSQL Database directory appears to contain a database", + "Recovery is complete. This is an informational message only.", + ] + if any(msg in logs for msg in ready_msg): progress_bar.close() return container progress_bar.update("Container initializing...", progress) From c040cf2f125eae16f8c5154a562774f2482eff6b Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:18:54 -0400 Subject: [PATCH 005/121] Feat: New multi-db orders example Sqlite, Mysql, Postgres, SqlServer, and Msaccess! Sporting alot of new features: - basic validators for all columns - updating a sg.StatusBar and/or sg.Text with info-msgs - Custom validator for email field in `customer` quick-editor --- examples/orders_multiple_databases.py | 618 ++++++++++++++++++++++++++ 1 file changed, 618 insertions(+) create mode 100644 examples/orders_multiple_databases.py diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py new file mode 100644 index 00000000..44997091 --- /dev/null +++ b/examples/orders_multiple_databases.py @@ -0,0 +1,618 @@ +import logging +import platform +import re +import time + +import numpy as np +import pandas as pd +import PySimpleGUI as sg + +import pysimplesql as ss +from pysimplesql.docker_utils import * + +# PySimpleGUI options +# ----------------------------- +sg.change_look_and_feel("SystemDefaultForReal") +sg.set_options(font=("Arial", 11), dpi_awareness=True) + +# Setup Logger +# ----------------------------- +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) +# Set up the appropriate theme depending on the OS +# ----------------------------- +if platform.system() == "Windows": + # Use the xpnative theme, and the `crystal_remix` iconset + os_ttktheme = "xpnative" + os_tp = ss.tp_crystal_remix +else: + # Use the defaults for the OS + os_ttktheme = "default" + os_tp = ss.ThemePack.default + +# Generate the custom themepack +# ----------------------------- +custom = { + "ttk_theme": os_ttktheme, + "marker_sort_asc": " ⬇", + "marker_sort_desc": " ⬆", + "shake_gui_widget_on_invalid_input": True, + # Recommended to set a lower delay if 'shake' is disabled + # "live_update_typing_delay_seconds": 0.25, +} +custom = custom | os_tp +ss.themepack(custom) + +# ---------------------------------- +# CREATE A DATABASE SELECTION WINDOW +# ---------------------------------- +# fmt: off +icons = { 'msaccess': b'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', + 'mysql': b'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', + 'postgres': b'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', + 'sqlite': b'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', + 'sqlserver': b'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'} +# fmt: on +layout_selection = [ + [ + [sg.Text("Pick a database to use:", font="_16")], + [sg.Image(icons["sqlite"]), sg.B("SQLite", key="sqlite")], + [sg.Image(icons["mysql"]), sg.B("MySQL", key="mysql")], + [sg.Image(icons["postgres"]), sg.B("PostgreSQL", key="postgres")], + [sg.Image(icons["sqlserver"]), sg.B("SQLServer", key="sqlserver")], + [sg.Image(icons["msaccess"]), sg.B("MsAccess", key="msaccess")], + ] +] +win = sg.Window("Databases", layout=layout_selection, finalize=True) +selected_driver = None +while True: + event, values = win.read() + # Set SQLite as default if popup closed without selection + selected_driver = "sqlite" if (event == sg.WIN_CLOSED or event == "Exit") else event + break +win.close() + +database = selected_driver + +port = { + "mysql": 3306, + "postgres": 5432, + "sqlserver": 1433, +} + +if database not in ["sqlite", "msaccess"]: + docker_image = f"pysimplesql/examples:{database}" + docker_image_pull(docker_image) + docker_container = docker_container_start( + image=docker_image, + container_name=f"pysimplesql-examples-{database}", + ports={f"{port[database]}/tcp": ("127.0.0.1", port[database])}, + ) + + +class SqlFormat(dict): + def __missing__(self, key): + return "" + + +class Template: + def __init__(self, template_string): + self.template_string = template_string + + def render(self, context): + lang_format = SqlFormat(context) + return self.template_string.format_map(lang_format) + + +# SQL Statement +# ====================================================================================== +sql = """ +{disable_constraints} +DROP TABLE IF EXISTS customers {cascade}; +CREATE TABLE customers ( + customer_id {pk_type} NOT NULL PRIMARY KEY {autoincrement}, + name {text_type} NOT NULL, + email {text_type} +); + +DROP TABLE IF EXISTS orders {cascade}; +CREATE TABLE orders ( + order_id {pk_type} NOT NULL PRIMARY KEY {autoincrement}, + customer_id {integer_type} NOT NULL, + date DATE NOT NULL DEFAULT {date_default}, + total {numeric_type}, + completed {boolean_type} NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) +); + +DROP TABLE IF EXISTS products {cascade}; +CREATE TABLE products ( + product_id {pk_type} NOT NULL PRIMARY KEY {autoincrement}, + name {text_type} NOT NULL DEFAULT {default_string}, + price {numeric_type} NOT NULL, + inventory {integer_type} DEFAULT 0 +); + +DROP TABLE IF EXISTS order_details {cascade}; +CREATE TABLE order_details ( + order_detail_id {pk_type} NOT NULL PRIMARY KEY {autoincrement}, + order_id {integer_type}, + product_id {integer_type} NOT NULL, + quantity {integer_type} NOT NULL, + price {numeric_type}, + subtotal {generated_column}, + FOREIGN KEY (order_id) REFERENCES orders(order_id) ON UPDATE CASCADE ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(product_id) +); + +INSERT INTO customers (name, email) VALUES + ('Alice Rodriguez', 'rodriguez.alice@example.com'), + ('Bryan Patel', 'patel.bryan@example.com'), + ('Cassandra Kim', 'kim.cassandra@example.com'), + ('David Nguyen', 'nguyen.david@example.com'), + ('Ella Singh', 'singh.ella@example.com'), + ('Franklin Gomez', 'gomez.franklin@example.com'), + ('Gabriela Ortiz', 'ortiz.gabriela@example.com'), + ('Henry Chen', 'chen.henry@example.com'), + ('Isabella Kumar', 'kumar.isabella@example.com'), + ('Jonathan Lee', 'lee.jonathan@example.com'), + ('Katherine Wright', 'wright.katherine@example.com'), + ('Liam Davis', 'davis.liam@example.com'), + ('Mia Ali', 'ali.mia@example.com'), + ('Nathan Kim', 'kim.nathan@example.com'), + ('Oliver Brown', 'brown.oliver@example.com'), + ('Penelope Martinez', 'martinez.penelope@example.com'), + ('Quentin Carter', 'carter.quentin@example.com'), + ('Rosa Hernandez', 'hernandez.rosa@example.com'), + ('Samantha Jones', 'jones.samantha@example.com'), + ('Thomas Smith', 'smith.thomas@example.com'), + ('Uma Garcia', 'garcia.uma@example.com'), + ('Valentina Lopez', 'lopez.valentina@example.com'), + ('William Park', 'park.william@example.com'), + ('Xander Williams', 'williams.xander@example.com'), + ('Yara Hassan', 'hassan.yara@example.com'), + ('Zoe Perez', 'perez.zoe@example.com'); + +INSERT INTO products (name, price, inventory) VALUES + ('Thingamabob', 5.00, 200), + ('Doohickey', 15.00, 75), + ('Whatchamacallit', 25.00, 50), + ('Gizmo', 10.00, 100), + ('Widget', 20.00, 60), + ('Doodad', 30.00, 40), + ('Sprocket', 7.50, 150), + ('Flibbertigibbet', 12.50, 90), + ('Thingamajig', 22.50, 30), + ('Dooberry', 17.50, 50), + ('Whirligig', 27.50, 25), + ('Gadget', 8.00, 120), + ('Contraption', 18.00, 65), + ('Thingummy', 28.00, 35), + ('Dinglehopper', 9.50, 100), + ('Doodlywhatsit', 19.50, 55), + ('Whatnot', 29.50, 20), + ('Squiggly', 6.50, 175), + ('Fluffernutter', 11.50, 80), + ('Goober', 21.50, 40), + ('Doozie', 16.50, 60), + ('Whammy', 26.50, 30), + ('Thingy', 7.00, 130), + ('Doodadery', 17.00, 70); +""" + +# Generate random orders using pandas DataFrame +num_orders = 100 +orders_df = pd.DataFrame( + { + "order_id": np.arange(1, num_orders + 1), + "customer_id": np.random.randint(1, 25, num_orders), + "date": pd.date_range(start=time.strftime("%Y-%m-%d"), periods=num_orders).date, + "completed": np.random.choice(["{true_bool}", "{false_bool}"], num_orders), + } +) + +# Generate random order details using pandas DataFrame +num_order_details = num_orders * 5 +order_details_df = pd.DataFrame( + { + "order_id": np.random.choice(orders_df["order_id"], num_order_details), + "product_id": np.random.randint(1, 25, num_order_details), + "quantity": np.random.randint(1, 10, num_order_details), + } +) + +# Generate the insert statements +sql += "INSERT INTO orders (customer_id, date, completed) VALUES\n" +sql_values = [ + f"({row['customer_id']}, '{row['date']}', {row['completed']})" + for _, row in orders_df.iterrows() +] +sql_values_str = ", ".join(sql_values) +sql += sql_values_str + ";\n" +sql += "INSERT INTO order_details (order_id, product_id, quantity) VALUES\n" +sql_values = [ + f"({row['order_id']}, {row['product_id']}, {row['quantity']})" + for _, row in order_details_df.iterrows() +] +sql_values_str = ", ".join(sql_values) +sql += sql_values_str + ";\n" + +sql += """ +UPDATE order_details + SET price = ( + SELECT products.price FROM products WHERE products.product_id = order_details.product_id +); + +{msaccess_update_subtotal} + +UPDATE orders + SET total = ( + SELECT SUM(subtotal) FROM order_details WHERE order_details.order_id = orders.order_id +); +{enable_constraints} +""" # noqa E501 + +sqlserver_disable_constraints = """ +DECLARE @sql nvarchar(MAX) +SET @sql = N'' + +SELECT @sql = @sql + N'ALTER TABLE ' + QUOTENAME(KCU1.TABLE_SCHEMA) + + N'.' + QUOTENAME(KCU1.TABLE_NAME) + + N' DROP CONSTRAINT ' -- + QUOTENAME(rc.CONSTRAINT_SCHEMA) + N'.' -- not in MS-SQL + + QUOTENAME(rc.CONSTRAINT_NAME) + N'; ' + CHAR(13) + CHAR(10) +FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS RC + +INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU1 + ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG + AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA + AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME + +EXECUTE(@sql) +""" + +compatibility = { + "sqlite": { + "pk_type": "INTEGER", + "text_type": "TEXT", + "integer_type": "INTEGER", + "date_type": "DATE", + "numeric_type": "NUMERIC(10,2)", + "date_default": "(date('now'))", + "boolean_type": "BOOLEAN", + "default_string": "'New Product'", + "default_boolean": "0", + "generated_column": "NUMERIC(10,2) GENERATED ALWAYS AS (price * quantity) STORED", + "autoincrement": "AUTOINCREMENT", + "false_bool": 0, + "true_bool": 1, + }, + "mysql": { + "pk_type": "INTEGER", + "text_type": "VARCHAR(255)", + "integer_type": "INTEGER", + "numeric_type": "DECIMAL(10,2)", + "date_type": "DATE", + "date_default": "(CURRENT_DATE())", + "boolean_type": "BIT", + "default_string": "'New Product'", + "default_boolean": "FALSE", + "generated_column": "DECIMAL(10,2) GENERATED ALWAYS AS (`price` * `quantity`) STORED", # noqa E501 + "autoincrement": "AUTO_INCREMENT", + "false_bool": 0, + "true_bool": 1, + "disable_constraints": "SET FOREIGN_KEY_CHECKS=0;", + "enable_constraints": "SET FOREIGN_KEY_CHECKS=1;", + }, + "postgres": { + "pk_type": "SERIAL", + "text_type": "VARCHAR(255)", + "integer_type": "INTEGER", + "numeric_type": "NUMERIC(10,2)", + "date_type": "DATE", + "date_default": "(CURRENT_DATE)", + "boolean_type": "BOOLEAN", + "default_string": "'New Product'", + "default_boolean": "FALSE", + "generated_column": "NUMERIC(10,2) GENERATED ALWAYS AS (price * quantity) STORED", # noqa E501 + "autoincrement": "", + "false_bool": False, + "true_bool": True, + "cascade": "CASCADE", + }, + "sqlserver": { + "pk_type": "INT", + "text_type": "VARCHAR(255)", + "integer_type": "INT", + "numeric_type": "DECIMAL(10,2)", + "date_type": "DATE", + "date_default": "(CAST(GETDATE() as DATE))", + "boolean_type": "BIT", + "default_string": "'New Product'", + "default_boolean": "0", + "generated_column": "AS ([price] * [quantity]) PERSISTED", + "autoincrement": "IDENTITY(1,1)", + "false_bool": 0, + "true_bool": 1, + "disable_constraints": sqlserver_disable_constraints, + }, + "msaccess": { + "pk_type": "COUNTER", + "text_type": "TEXT(255)", + "integer_type": "LONG", + "numeric_type": "NUMERIC(10,2)", + "date_type": "DATETIME", + "date_default": "'=DATE()'", + "boolean_type": "BOOLEAN", + "default_string": "'New Product'", + "default_boolean": "0", + "generated_column": "NUMERIC(10,2)", + "autoincrement": "", + "false_bool": 0, + "true_bool": 1, + "msaccess_update_subtotal": "UPDATE order_details SET subtotal = price * quantity;", + }, +} +# Perform the template replacement based on the target database +template = Template(sql) +sql = template.render(compatibility[database]) +print(sql) +# ------------------------- +# CREATE PYSIMPLEGUI LAYOUT +# ------------------------- + +# fmt: off +# Create a basic menu +menu_def = [ + ["&File",["&Save","&Requery All",],], + ["&Edit", ["&Edit Products", "&Edit Customers"]], +] +# fmt: on +layout = [[sg.Menu(menu_def, key="-MENUBAR-", font="_ 12")]] + +# Define the columns for the table selector using the TableHeading class. +order_heading = ss.TableHeadings( + sort_enable=True, # Click a heading to sort + allow_cell_edits=True, # Double-click a cell to make edits. + # Exempted: Primary Key columns, Generated columns, and columns set as readonly + add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() + apply_search_filter=True, # Filter rows as you type in the search input +) + +# Add columns +order_heading.add_column(column="order_id", heading_column="ID", width=5) +order_heading.add_column("customer_id", "Customer", 30) +order_heading.add_column("date", "Date", 20) +order_heading.add_column( + "total", "total", width=10, readonly=True +) # set to True to disable editing for individual columns!) +order_heading.add_column("completed", "✔", 8) + +# Layout +layout.append( + [ + [sg.Text("Orders", font="_16")], + [ + ss.selector( + "orders", + sg.Table, + num_rows=5, + headings=order_heading, + row_height=25, + ) + ], + [ss.actions("orders")], + [sg.Sizer(h_pixels=0, v_pixels=20)], + ] +) + +# order_details TableHeadings: +details_heading = ss.TableHeadings( + sort_enable=True, allow_cell_edits=True, add_save_heading_button=True +) +details_heading.add_column("product_id", "Product", 30) +details_heading.add_column("quantity", "quantity", 10) +details_heading.add_column("price", "price/Ea", 10, readonly=True) +details_heading.add_column("subtotal", "subtotal", 10, readonly=True) + +orderdetails_layout = [ + [sg.Sizer(h_pixels=0, v_pixels=10)], + [ss.field("orders.customer_id", sg.Combo, label="Customer")], + [ + ss.field("orders.date", label="Date"), + ], + [ss.field("orders.completed", sg.Checkbox, default=False)], + [ + ss.selector( + "order_details", + sg.Table, + num_rows=10, + headings=details_heading, + row_height=25, + ) + ], + [ss.actions("order_details", default=False, save=True, insert=True, delete=True)], + [ss.field("order_details.product_id", sg.Combo)], + [ss.field("order_details.quantity")], + [ss.field("order_details.price", sg.Text)], + [ss.field("order_details.subtotal", sg.Text)], + [sg.Sizer(h_pixels=0, v_pixels=10)], + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})], +] + +layout.append([sg.Frame("Order Details", orderdetails_layout, expand_x=True)]) + +win = sg.Window( + "Order Example", + layout, + finalize=True, + # Below is Important! pysimplesql progressbars/popups/quick_editors use + # ttk_theme and icon as defined in themepack. + ttk_theme=os_ttktheme, + icon=ss.themepack.icon, +) + +# Expand our sg.Tables so they fill the screen +win["orders:selector"].expand(True, True) +win["orders:selector"].table_frame.pack(expand=True, fill="both") +win["order_details:selector"].expand(True, True) +win["order_details:selector"].table_frame.pack(expand=True, fill="both") + +# Init pysimplesql Driver and Form +# -------------------------------- +if database == "sqlite": + # Create sqlite driver, keeping the database in memory + driver = ss.Driver.sqlite(":memory:", sql_commands=sql) +elif database == "mysql": + mysql_docker = { + "user": "pysimplesql_user", + "password": "pysimplesql", + "host": "127.0.0.1", + "database": "pysimplesql_examples", + } + driver = ss.Driver.mysql(**mysql_docker, sql_commands=sql) +elif database == "postgres": + postgres_docker = { + "host": "localhost", + "user": "pysimplesql_user", + "password": "pysimplesql", + "database": "pysimplesql_examples", + } + driver = ss.Driver.postgres(**postgres_docker, sql_commands=sql) +elif database == "sqlserver": + sqlserver_docker = { + "host": "127.0.0.1", + "user": "pysimplesql_user", + "password": "Pysimplesql!", + "database": "pysimplesql_examples", + } + driver = ss.Driver.sqlserver(**sqlserver_docker, sql_commands=sql) +elif database == "msaccess": + # Import java_helper for msaccess + import os + import pathlib + import sys + + current_dir = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) + java_install_dir = str(pathlib.Path(current_dir / "MSAccess_examples")) + sys.path.append(str(java_install_dir)) + from install_java import java_check_install + + # Ensure that Java is installed + if not java_check_install(): + exit(0) + driver = ss.Driver.msaccess("orders.accdb", sql_commands=sql, overwrite_file=True) +frm = ss.Form( + driver, + bind_window=win, + live_update=True, # this updates the `Selector`, sg.Table as we type in fields. +) +# Few more settings +# ----------------- + +frm.edit_protect() # Comment this out to edit protect when the window is created. +# Reverse the default sort order so orders are sorted by date +frm["orders"].set_order_clause("ORDER BY date ASC") +# Requery the data since we made changes to the sort order +frm["orders"].requery() +# Set the column order for search operations. +frm["orders"].set_search_order(["customer_id", "order_id"]) + + +# Application-side code to update orders `total` +# when saving/deleting order_details line item +# ---------------------------------------------- +def update_orders(frm_reference, window, data_key): + if data_key == "order_details": + order_id = frm["order_details"]["order_id"] + driver.execute( + f"UPDATE orders " + f"SET total = (" + f" SELECT SUM(subtotal)" + f" FROM order_details" + f" WHERE order_details.order_id = {order_id}) " + f"WHERE orders.order_id = {order_id};" + ) + # do our own subtotal/total summing to avoid requerying + frm["order_details"]["subtotal"] = ( + frm["order_details"]["price"] * frm["order_details"]["quantity"] + ) + frm["orders"]["total"] = frm["order_details"].rows["subtotal"].sum() + frm["orders"].save_record(display_message=False) + frm.update_selectors("orders") + frm.update_selectors("ordersDetails") + return True + + +# set this to be called after a save or delete of order_details +frm["order_details"].set_callback("after_save", update_orders) +frm["order_details"].set_callback("after_delete", update_orders) + + +# create your own validator to be passed to a +# frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn +# used below in the quick_editor arguments +def is_valid_email(email): + valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None + if not valid_email: + return ss.ValidateResponse( + ss.ValidateRule.CUSTOM, email, " is not a valid email" + ) + return ss.ValidateResponse() + + +# --------- +# MAIN LOOP +# --------- +while True: + event, values = win.read() + if event == sg.WIN_CLOSED or event == "Exit": + frm.close() # <= ensures proper closing of the sqlite database + win.close() + break + # <=== let PySimpleSQL process its own events! Simple! + elif ss.process_events(event, values): + logger.info(f"PySimpleDB event handler handled the event {event}!") + # Code to automatically save and refresh order_details: + # ---------------------------------------------------- + elif ( + "after_record_edit" in event + and values["after_record_edit"]["data_key"] == "order_details" + ): + dataset = frm["order_details"] + current_row = dataset.get_current_row() + # after a product and quantity is entered, grab price & save + if ( + dataset.row_count + and current_row["product_id"] not in [None, ss.PK_PLACEHOLDER] + and current_row["quantity"] not in ss.EMPTY + ): + # get product_id + product_id = current_row["product_id"] + # get products rows df reference + product_df = frm["products"].rows + # set current rows 'price' to match price as matching product_id + dataset["price"] = product_df.loc[ + product_df["product_id"] == product_id, "price" + ].values[0] + # save the record + dataset.save_record(display_message=False) + + # ---------------------------------------------------- + + # Display the quick_editor for products and customers + elif "Edit Products" in event: + frm["products"].quick_editor() + elif "Edit Customers" in event: + frm["customers"].quick_editor( + column_info_settings={ + "email": {"custom_validate_fn": lambda value: is_valid_email(value)} + } + ) + # call a Form-level save + elif "Save" in event: + frm.save_records() + # call a Form-level requery + elif "Requery All" in event: + frm.requery_all() + else: + logger.info(f"This event ({event}) is not yet handled.") From 7196c3d7461c837278e0aa173e38323720a90cd7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:19:27 -0400 Subject: [PATCH 006/121] Feat: install_java, save previous jre install --- examples/MSAccess_examples/install_java.py | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/examples/MSAccess_examples/install_java.py b/examples/MSAccess_examples/install_java.py index 5a7d44a7..bf0dd2ae 100644 --- a/examples/MSAccess_examples/install_java.py +++ b/examples/MSAccess_examples/install_java.py @@ -6,7 +6,9 @@ run. This also serves as an example to automatically download a local Java installation for your own projects. """ +import configparser import os +import pathlib import pysimplesql as ss import PySimpleGUI as sg import subprocess @@ -17,11 +19,19 @@ sg.popup_error("You must `pip install install-jdk` to use this example") exit(0) +SETTINGS_FILE = pathlib.Path.cwd() / "settings.ini" + # ------------------------------------------------- # ROUTINES TO INSTALL JAVA IF USER DOES NOT HAVE IT # ------------------------------------------------- def _is_java_installed(): + if "JAVA_HOME" in os.environ: + return True + previous_jre = load_setting("General", "java_home") + if previous_jre: + os.environ["JAVA_HOME"] = previous_jre + return True # Returns True if Java is installed, False otherwise try: subprocess.check_output(["which", "java"]) @@ -64,8 +74,9 @@ def java_check_install() -> bool: pa.close() return False pa.close() - # set JAVA_HOME + # Set JAVA_HOME and save it to settings os.environ["JAVA_HOME"] = java_home + save_setting("General", "java_home", java_home) else: url = jdk.get_download_url(11, jre=True) sg.popup( @@ -80,6 +91,33 @@ def java_check_install() -> bool: return True +def save_setting(section: str, key: str, value: str): + config = configparser.ConfigParser() + config.read(SETTINGS_FILE) + + # Create the section if it doesn't exist + if section not in config: + config[section] = {} + + # Set the value in the section + config[section][key] = value + + # Save the settings to the file + with open(SETTINGS_FILE, "w") as config_file: + config.write(config_file) + + +def load_setting(section: str, key: str, default=None) -> str: + config = configparser.ConfigParser() + config.read(SETTINGS_FILE) + + # Check if the section and key exist + if section in config and key in config[section]: + return config[section][key] + + return default + + if __name__ == "__main__": if java_check_install(): print("Java is installed.") From a95f600e110263a9f8c8cd100709ba0bc5b69811 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:20:29 -0400 Subject: [PATCH 007/121] Refactor: remove triggers from orders.py, align closer to multidb orders --- examples/SQLite_examples/orders.py | 299 +++++++++++++---------------- 1 file changed, 130 insertions(+), 169 deletions(-) diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index c3d9ad14..9c631696 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -37,120 +37,42 @@ # SQL Statement # ====================================================================================== -# While this example uses triggers to calculate prices and sub/totals, they are not -# required for pysimplesql to operate. See simpler examples, like journal. sql = """ -CREATE TABLE IF NOT EXISTS Customers ( - "CustomerID" INTEGER NOT NULL, - "Name" TEXT NOT NULL, - "Email" TEXT, - PRIMARY KEY("CustomerID" AUTOINCREMENT) +CREATE TABLE customers ( + customer_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT ); -CREATE TABLE IF NOT EXISTS Orders ( - "OrderID" INTEGER NOT NULL, - "CustomerID" INTEGER NOT NULL, - "OrderDate" DATE NOT NULL DEFAULT (date('now')), - "Total" REAL, - "Completed" BOOLEAN NOT NULL, - FOREIGN KEY ("CustomerID") REFERENCES Customers(CustomerID), - PRIMARY KEY("OrderID" AUTOINCREMENT) +CREATE TABLE orders ( + order_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + customer_id INTEGER NOT NULL, + date DATE NOT NULL DEFAULT (date('now')), + total REAL, + completed BOOLEAN NOT NULL, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); -CREATE TABLE IF NOT EXISTS Products ( - "ProductID" INTEGER NOT NULL, - "Name" TEXT NOT NULL DEFAULT "New Product", - "Price" REAL NOT NULL, - "Inventory" INTEGER DEFAULT 0, - PRIMARY KEY("ProductID" AUTOINCREMENT) +CREATE TABLE products ( + product_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL DEFAULT 'New Product', + price REAL NOT NULL, + inventory INTEGER DEFAULT 0 ); -CREATE TABLE IF NOT EXISTS OrderDetails ( - "OrderDetailID" INTEGER NOT NULL, - "OrderID" INTEGER, - "ProductID" INTEGER NOT NULL, - "Quantity" INTEGER, - "Price" REAL, - "SubTotal" REAL GENERATED ALWAYS AS ("Price" * "Quantity") STORED, - FOREIGN KEY ("OrderID") REFERENCES "Orders"("OrderID") ON UPDATE CASCADE ON DELETE CASCADE, - FOREIGN KEY ("ProductID") REFERENCES "Products"("ProductID"), - PRIMARY KEY("OrderDetailID" AUTOINCREMENT) +CREATE TABLE order_details ( + order_detail_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + order_id INTEGER, + product_id INTEGER NOT NULL, + quantity INTEGER, + price REAL, + subtotal REAL GENERATED ALWAYS AS (price * quantity) STORED, + FOREIGN KEY (order_id) REFERENCES orders(order_id) ON UPDATE CASCADE ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(product_id) ); --- Create a compound index on OrderID and ProductID columns in OrderDetails table -CREATE INDEX idx_orderdetails_orderid_productid ON OrderDetails (OrderID, ProductID); - --- Trigger to set the price value for a new OrderDetail -CREATE TRIGGER IF NOT EXISTS set_price -AFTER INSERT ON OrderDetails -FOR EACH ROW -BEGIN - UPDATE OrderDetails - SET Price = Products.Price - FROM Products - WHERE Products.ProductID = NEW.ProductID - AND OrderDetails.OrderDetailID = NEW.OrderDetailID; -END; - --- Trigger to update the price value for an existing OrderDetail -CREATE TRIGGER IF NOT EXISTS set_price_update -AFTER UPDATE ON OrderDetails -FOR EACH ROW -BEGIN - UPDATE OrderDetails - SET Price = Products.Price - FROM Products - WHERE Products.ProductID = NEW.ProductID - AND OrderDetails.OrderDetailID = NEW.OrderDetailID; -END; - --- Trigger to set the total value for a new OrderDetail -CREATE TRIGGER IF NOT EXISTS set_total -AFTER INSERT ON OrderDetails -FOR EACH ROW -BEGIN - UPDATE Orders - SET Total = ( - SELECT SUM(SubTotal) FROM OrderDetails WHERE OrderID = NEW.OrderID - ) - WHERE OrderID = NEW.OrderID; -END; - --- Trigger to update the total value for an existing OrderDetail -CREATE TRIGGER IF NOT EXISTS update_total -AFTER UPDATE ON OrderDetails -FOR EACH ROW -BEGIN - UPDATE Orders - SET Total = ( - SELECT SUM(SubTotal) FROM OrderDetails WHERE OrderID = NEW.OrderID - ) - WHERE OrderID = NEW.OrderID; -END; - --- Trigger to update the total value for an existing OrderDetail -CREATE TRIGGER IF NOT EXISTS delete_order_detail -AFTER DELETE ON OrderDetails -FOR EACH ROW -BEGIN - UPDATE Orders - SET Total = ( - SELECT SUM(SubTotal) FROM OrderDetails WHERE OrderID = OLD.OrderID - ) - WHERE OrderID = OLD.OrderID; -END; - -CREATE TRIGGER IF NOT EXISTS update_product_price -AFTER UPDATE ON Products -FOR EACH ROW -BEGIN - UPDATE OrderDetails - SET Price = NEW.Price - WHERE ProductID = NEW.ProductID; -END; - -INSERT INTO Customers (Name, Email) VALUES +INSERT INTO customers (name, email) VALUES ('Alice Rodriguez', 'rodriguez.alice@example.com'), ('Bryan Patel', 'patel.bryan@example.com'), ('Cassandra Kim', 'kim.cassandra@example.com'), @@ -178,7 +100,7 @@ ('Yara Hassan', 'hassan.yara@example.com'), ('Zoe Perez', 'perez.zoe@example.com'); -INSERT INTO Products (Name, Price, Inventory) VALUES +INSERT INTO products (name, price, inventory) VALUES ('Thingamabob', 5.00, 200), ('Doohickey', 15.00, 75), ('Whatchamacallit', 25.00, 50), @@ -204,17 +126,27 @@ ('Thingy', 7.00, 130), ('Doodadery', 17.00, 70); -INSERT INTO Orders (CustomerID, OrderDate, Completed) -SELECT CustomerID, DATE('now', '-' || (ABS(RANDOM()) % 30) || ' days'), False -FROM Customers +INSERT INTO orders (customer_id, date, completed) +SELECT customer_id, DATE('now', '-' || (ABS(RANDOM()) % 30) || ' days'), 0 +FROM customers ORDER BY RANDOM() LIMIT 100; -INSERT INTO OrderDetails (OrderID, ProductID, Quantity) -SELECT O.OrderID, P.ProductID, (ABS(RANDOM()) % 10) + 1 -FROM Orders O -JOIN (SELECT ProductID FROM Products ORDER BY RANDOM() LIMIT 25) P +INSERT INTO order_details (order_id, product_id, quantity) +SELECT O.order_id, P.product_id, (ABS(RANDOM()) % 10) + 1 +FROM orders O +JOIN (SELECT product_id FROM products ORDER BY RANDOM() LIMIT 25) P ON 1=1 ORDER BY 1; + +UPDATE order_details + SET price = ( + SELECT products.price FROM products WHERE products.product_id = order_details.product_id +); + +UPDATE orders + SET total = ( + SELECT SUM(subtotal) FROM order_details WHERE order_details.order_id = orders.order_id +); """ # ------------------------- @@ -232,25 +164,21 @@ # Define the columns for the table selector using the TableHeading class. order_heading = ss.TableHeadings( - # Click a heading to sort - sort_enable=True, - # Double-click a cell to make edits. + sort_enable=True, # Click a heading to sort + allow_cell_edits=True, # Double-click a cell to make edits. # Exempted: Primary Key columns, Generated columns, and columns set as readonly - edit_enable=True, - # Click 💾 in sg.Table Heading to trigger DataSet.save_record() - save_enable=True, - # Filter rows as you type in the search input - apply_search_filter=True, + add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() + apply_search_filter=True, # Filter rows as you type in the search input ) # Add columns -order_heading.add_column(column="OrderID", heading_column="ID", width=5) -order_heading.add_column("CustomerID", "Customer", 30) -order_heading.add_column("OrderDate", "Date", 20) +order_heading.add_column(column="order_id", heading_column="ID", width=5) +order_heading.add_column("customer_id", "Customer", 30) +order_heading.add_column("date", "Date", 20) order_heading.add_column( - "Total", "Total", width=10, readonly=True + "total", "total", width=10, readonly=True ) # set to True to disable editing for individual columns!) -order_heading.add_column("Completed", "✔", 8) +order_heading.add_column("completed", "✔", 8) # Layout layout.append( @@ -258,46 +186,48 @@ [sg.Text("Orders", font="_16")], [ ss.selector( - "Orders", + "orders", sg.Table, num_rows=5, headings=order_heading, row_height=25, ) ], - [ss.actions("Orders")], + [ss.actions("orders")], [sg.Sizer(h_pixels=0, v_pixels=20)], ] ) -# OrderDetails TableHeadings: -details_heading = ss.TableHeadings(sort_enable=True, edit_enable=True, save_enable=True) -details_heading.add_column("ProductID", "Product", 30) -details_heading.add_column("Quantity", "Quantity", 10) -details_heading.add_column("Price", "Price/Ea", 10, readonly=True) -details_heading.add_column("SubTotal", "SubTotal", 10) +# order_details TableHeadings: +details_heading = ss.TableHeadings( + sort_enable=True, allow_cell_edits=True, add_save_heading_button=True +) +details_heading.add_column("product_id", "Product", 30) +details_heading.add_column("quantity", "quantity", 10) +details_heading.add_column("price", "price/Ea", 10, readonly=True) +details_heading.add_column("subtotal", "subtotal", 10) orderdetails_layout = [ [sg.Sizer(h_pixels=0, v_pixels=10)], - [ss.field("Orders.CustomerID", sg.Combo, label="Customer")], + [ss.field("orders.customer_id", sg.Combo, label="Customer")], [ - ss.field("Orders.OrderDate", label="Date"), + ss.field("orders.date", label="Date"), ], - [ss.field("Orders.Completed", sg.Checkbox, default=False)], + [ss.field("orders.completed", sg.Checkbox, default=False)], [ ss.selector( - "OrderDetails", + "order_details", sg.Table, num_rows=10, headings=details_heading, row_height=25, ) ], - [ss.actions("OrderDetails", default=False, save=True, insert=True, delete=True)], - [ss.field("OrderDetails.ProductID", sg.Combo)], - [ss.field("OrderDetails.Quantity")], - [ss.field("OrderDetails.Price", sg.Text)], - [ss.field("OrderDetails.SubTotal", sg.Text)], + [ss.actions("order_details", default=False, save=True, insert=True, delete=True)], + [ss.field("order_details.product_id", sg.Combo)], + [ss.field("order_details.quantity")], + [ss.field("order_details.price", sg.Text)], + [ss.field("order_details.subtotal", sg.Text)], [sg.Sizer(h_pixels=0, v_pixels=10)], ] @@ -314,10 +244,10 @@ ) # Expand our sg.Tables so they fill the screen -win["Orders:selector"].expand(True, True) -win["Orders:selector"].table_frame.pack(expand=True, fill="both") -win["OrderDetails:selector"].expand(True, True) -win["OrderDetails:selector"].table_frame.pack(expand=True, fill="both") +win["orders:selector"].expand(True, True) +win["orders:selector"].table_frame.pack(expand=True, fill="both") +win["order_details:selector"].expand(True, True) +win["order_details:selector"].table_frame.pack(expand=True, fill="both") # Init pysimplesql Driver and Form # -------------------------------- @@ -329,17 +259,46 @@ bind_window=win, live_update=True, # this updates the `Selector`, sg.Table as we type in fields. ) - # Few more settings # ----------------- frm.edit_protect() # Comment this out to edit protect when the window is created. -# Reverse the default sort order so Orders are sorted by date -frm["Orders"].set_order_clause("ORDER BY OrderDate ASC") +# Reverse the default sort order so orders are sorted by date +frm["orders"].set_order_clause("ORDER BY date ASC") # Requery the data since we made changes to the sort order -frm["Orders"].requery() +frm["orders"].requery() # Set the column order for search operations. -frm["Orders"].set_search_order(["CustomerID", "OrderID"]) +frm["orders"].set_search_order(["customer_id", "order_id"]) + + +# Application-side code to update orders `total` +# when saving/deleting order_details line item +# ---------------------------------------------- +def update_orders(frm_reference, window, data_key): + if data_key == "order_details": + order_id = frm["order_details"]["order_id"] + driver.execute( + f"UPDATE orders " + f"SET total = (" + f" SELECT SUM(subtotal)" + f" FROM order_details" + f" WHERE order_details.order_id = {order_id}) " + f"WHERE orders.order_id = {order_id};" + ) + # do our own subtotal/total summing to avoid requerying + frm["order_details"]["subtotal"] = ( + frm["order_details"]["price"] * frm["order_details"]["quantity"] + ) + frm["orders"]["total"] = frm["order_details"].rows["subtotal"].sum() + frm["orders"].save_record(display_message=False) + frm.update_selectors("orders") + frm.update_selectors("ordersDetails") + return True + + +# set this to be called after a save or delete of order_details +frm["order_details"].set_callback("after_save", update_orders) +frm["order_details"].set_callback("after_delete", update_orders) # --------- # MAIN LOOP @@ -347,43 +306,45 @@ while True: event, values = win.read() if event == sg.WIN_CLOSED or event == "Exit": - frm.close() # <= ensures proper closing of the sqlite database and runs a database optimization + frm.close() # <= ensures proper closing of the sqlite database win.close() break # <=== let PySimpleSQL process its own events! Simple! elif ss.process_events(event, values): logger.info(f"PySimpleDB event handler handled the event {event}!") - # Code to automatically save and refresh OrderDetails: + # Code to automatically save and refresh order_details: # ---------------------------------------------------- elif ( - "current_row_updated" in event - and values["current_row_updated"]["data_key"] == "OrderDetails" + "after_record_edit" in event + and values["after_record_edit"]["data_key"] == "order_details" ): - dataset = frm["OrderDetails"] + dataset = frm["order_details"] current_row = dataset.get_current_row() - # after a product and quantity is entered, save and requery + # after a product and quantity is entered, grab price & save if ( dataset.row_count - and current_row["ProductID"] not in [None, ss.PK_PLACEHOLDER] - and current_row["Quantity"] + and current_row["product_id"] not in [None, ss.PK_PLACEHOLDER] + and current_row["quantity"] ): - pk_is_virtual = dataset.pk_is_virtual() + # get product_id + product_id = current_row["product_id"] + # get products rows df reference + product_df = frm["products"].rows + # set current rows 'price' to match price as matching product_id + dataset["price"] = product_df.loc[ + product_df["product_id"] == product_id, "price" + ].values[0] + # save the record dataset.save_record(display_message=False) - frm["Orders"].requery(select_first=False) - frm.update_selectors("Orders") - # will need to requery if updating, rather than inserting a new record - if not pk_is_virtual: - pk = current_row[dataset.pk_column] - dataset.requery(select_first=False) - dataset.set_by_pk(pk, skip_prompt_save=True) + # ---------------------------------------------------- # Display the quick_editor for products and customers elif "Edit Products" in event: - frm["Products"].quick_editor() + frm["products"].quick_editor() elif "Edit Customers" in event: - frm["Customers"].quick_editor() + frm["customers"].quick_editor() # call a Form-level save elif "Save" in event: frm.save_records() From b9bebd52ae9a6fd8126a88d1f7382fcc25003dc6 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:21:29 -0400 Subject: [PATCH 008/121] nit: pd.options formatting --- pysimplesql/pysimplesql.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 5257b695..9f4a356b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -105,12 +105,10 @@ # ------------------------------------------- # Set up options for pandas DataFrame display # ------------------------------------------- -pd.set_option("display.max_rows", 15) # Show a maximum of 10 rows -pd.set_option("display.max_columns", 10) # Show a maximum of 5 columns -pd.set_option("display.width", 250) # Set the display width to 1000 characters -pd.set_option( - "display.max_colwidth", 25 -) # Set the maximum column width to 20 characters +pd.set_option("display.max_rows", 15) # Show a maximum of 15 rows +pd.set_option("display.max_columns", 10) # Show a maximum of 10 columns +pd.set_option("display.width", 250) # Set the display width to 250 characters +pd.set_option("display.max_colwidth", 25) # Set the maximum col width to 25 characters pd.set_option("display.precision", 2) # Set the number of decimal places to 2 # --------------------------- From 3d224a04afc2b2c398421b6963c630ce9dc36af7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:21:54 -0400 Subject: [PATCH 009/121] nit: imports --- pysimplesql/pysimplesql.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 9f4a356b..ec645eb8 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -67,9 +67,12 @@ import math import os.path import queue +import re import threading import tkinter as tk import tkinter.font as tkfont +from dataclasses import dataclass +from decimal import Decimal, DecimalException from time import sleep, time from tkinter import ttk from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypedDict, Union From f2f0610b4b0788858d4f6d2f0a2afcdb688b231b Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:22:30 -0400 Subject: [PATCH 010/121] nit: new constant TYPE_INFO used in automatically updating sg.StatusBar and/or sg.Text --- pysimplesql/pysimplesql.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index ec645eb8..a0766625 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -120,6 +120,7 @@ TYPE_RECORD: int = 1 TYPE_SELECTOR: int = 2 TYPE_EVENT: int = 3 +TYPE_INFO: int = 4 # ----------------- # Transform actions From e2a02d13e50d40d45998ba33b959f95900459631 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:25:39 -0400 Subject: [PATCH 011/121] Fix: new constant EMPTY Fixed a placeholder issue with it. --- pysimplesql/pysimplesql.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index a0766625..a325655f 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -215,6 +215,8 @@ # Misc Constants # -------------- PK_PLACEHOLDER = "Null" +EMPTY = ["", None] + class Boolean(enum.Flag): @@ -2496,7 +2498,7 @@ def table_values( rows = self.map_fk_descriptions(rows, columns) # filter rows to only contain search, or virtual/unsaved row - if apply_search_filter and self.search_string not in ["", None]: + if apply_search_filter and self.search_string not in EMPTY: masks = [ rows[col].astype(str).str.contains(self.search_string, case=False) | rows[pk_column].isin(virtual_row_pks) @@ -5469,12 +5471,12 @@ def update(self, *args, **kwargs): # Otherwise, use the current value value = self.get() - if self.active_placeholder and value: + if self.active_placeholder and value not in EMPTY: # Replace the placeholder with the new value super().update(value=value) self.active_placeholder = False self.Widget.config(fg=self.normal_color, font=self.normal_font) - elif not value: + elif value in EMPTY: # If the value is empty, reinsert the placeholder super().update(value=self.placeholder_text, *args, **kwargs) self.active_placeholder = True @@ -8066,7 +8068,7 @@ def _looks_like_function( s: str, ): # check if the string is empty - if not s: + if s in EMPTY: return False # If the entire string is in all caps, it looks like a function From a436909d25981e127e2067b5f694a29ab1133a9d Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:26:27 -0400 Subject: [PATCH 012/121] Refactor: change datetype str to CONSTANTS --- pysimplesql/pysimplesql.py | 41 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index a325655f..b71dc75d 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -217,6 +217,33 @@ PK_PLACEHOLDER = "Null" EMPTY = ["", None] +# -------------------- +# Date formats +# -------------------- +# Format for date only +DATE_FORMAT = "%Y-%m-%d" + +# -------------------- +# DateTime formats +# -------------------- +# Format for date and time without fraction +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" +# Format for date and time with microsecond precision +DATETIME_FORMAT_MICROSECOND = "%Y-%m-%d %H:%M:%S.%f" + +# -------------------- +# Timestamp formats +# -------------------- +# Format for timestamp without fraction +TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" +# Format for timestamp with microsecond precision +TIMESTAMP_FORMAT_MICROSECOND = "%Y-%m-%dT%H:%M:%S.%f" + +# -------------------- +# Time format +# -------------------- +# Format for time only +TIME_FORMAT = "%H:%M:%S" class Boolean(enum.Flag): @@ -5888,7 +5915,7 @@ def pressed_callback(self, event): if bbox and text: self.cal_date = dt.date(self.cal_date.year, self.cal_date.month, int(text)) self.draw_selection(bbox) - self.textvariable.set(self.cal_date.strftime("%Y-%m-%d")) + self.textvariable.set(self.cal_date.strftime(DATE_FORMAT)) def draw_selection(self, bbox): canvas, text = self.canvas, "%02d" % self.cal_date.day @@ -9886,19 +9913,17 @@ def execute( if isinstance(value, jpype.JPackage("java").sql.Timestamp): timestamp_str = value.toInstant().toString()[:-1] if "." in timestamp_str: - timestamp_format = "%Y-%m-%dT%H:%M:%S.%f" + timestamp_format = TIMESTAMP_FORMAT_MICROSECOND else: - timestamp_format = "%Y-%m-%dT%H:%M:%S" + timestamp_format = TIMESTAMP_FORMAT dt_value = dt.datetime.strptime(timestamp_str, timestamp_format) - value = dt_value.strftime("%Y-%m-%d") + value = dt_value.strftime(DATE_FORMAT) elif isinstance(value, jpype.JPackage("java").sql.Date): date_str = value.toString() - date_format = "%Y-%m-%d" - value = dt.datetime.strptime(date_str, date_format).date() + value = dt.datetime.strptime(date_str, DATE_FORMAT).date() elif isinstance(value, jpype.JPackage("java").sql.Time): time_str = value.toString() - time_format = "%H:%M:%S" - value = dt.datetime.strptime(time_str, time_format).time() + value = dt.datetime.strptime(time_str, TIME_FORMAT).time() elif value is not None: value = value # TODO: More conversions? From 0bd86a674184e457c53c5da5abebdad729f59fe3 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:27:19 -0400 Subject: [PATCH 013/121] Refactor: Relationship to dataclass and reorder to be like other classes --- pysimplesql/pysimplesql.py | 112 +++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 62 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index b71dc75d..be4a9e2d 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -318,6 +318,7 @@ def get_instance(self): return self +@dataclass class Relationship: """ @@ -326,12 +327,60 @@ class Relationship: See the following for more information: `Form.add_relationship` and `Form.auto_add_relationships`. - Note: This class is not typically used the end user, + :param join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. + :param child_table: The table name of the child table + :param fk_column: The child table's foreign key column + :param parent_table: The table name of the parent table + :param pk_column: The parent table's primary key column + :param update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' + :param delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' + :param driver: A `SQLDriver` instance + :param frm: A Form instance + :returns: None + + Note: This class is not typically used the end user """ # store our own instances instances = [] + join_type: str + child_table: str + fk_column: Union[str, int] + parent_table: str + pk_column: Union[str, int] + update_cascade: bool + delete_cascade: bool + driver: SQLDriver + frm: Form + + def __post_init__(self): + Relationship.instances.append(self) + + def __str__(self): + """Return a join clause when cast to a string.""" + return self.driver.relationship_to_join_clause(self) + + def __repr__(self): + """Return a more descriptive string for debugging.""" + return ( + f"Relationship (" + f"\n\tjoin={self.join_type}," + f"\n\tchild_table={self.child_table}," + f"\n\tfk_column={self.fk_column}," + f"\n\tparent_table={self.parent_table}," + f"\n\tpk_column={self.pk_column}" + f"\n)" + ) + + @property + def on_update_cascade(self): + return bool(self.update_cascade and self.frm.update_cascade) + + @property + def on_delete_cascade(self): + return bool(self.delete_cascade and self.frm.delete_cascade) + @classmethod def get_relationships(cls, table: str) -> List[Relationship]: """ @@ -456,67 +505,6 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str and not r.on_update_cascade } - def __init__( - self, - join_type: str, - child_table: str, - fk_column: Union[str, int], - parent_table: str, - pk_column: Union[str, int], - update_cascade: bool, - delete_cascade: bool, - driver: SQLDriver, - frm: Form, - ) -> None: - """ - Initialize a new Relationship instance. - - :param join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. - :param child_table: The table name of the child table - :param fk_column: The child table's foreign key column - :param parent_table: The table name of the parent table - :param pk_column: The parent table's primary key column - :param update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' - :param delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' - :param driver: A `SQLDriver` instance - :param frm: A Form instance - :returns: None - """ - self.join_type = join_type - self.child_table = child_table - self.fk_column = fk_column - self.parent_table = parent_table - self.pk_column = pk_column - self.update_cascade = update_cascade - self.delete_cascade = delete_cascade - self.driver = driver - self.frm = frm - Relationship.instances.append(self) - - @property - def on_update_cascade(self): - return bool(self.update_cascade and self.frm.update_cascade) - - @property - def on_delete_cascade(self): - return bool(self.delete_cascade and self.frm.delete_cascade) - - def __str__(self): - """Return a join clause when cast to a string.""" - return self.driver.relationship_to_join_clause(self) - - def __repr__(self): - """Return a more descriptive string for debugging.""" - return ( - f"Relationship (" - f"\n\tjoin={self.join_type}," - f"\n\tchild_table={self.child_table}," - f"\n\tfk_column={self.fk_column}," - f"\n\tparent_table={self.parent_table}," - f"\n\tpk_column={self.pk_column}" - f"\n)" - ) - class ElementMap(dict): From 6336cd50849c3579310e6f8646e18664590bdd51 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:27:44 -0400 Subject: [PATCH 014/121] Refactor: Elementmap to dataclass --- pysimplesql/pysimplesql.py | 57 ++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index be4a9e2d..f017f92b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -506,7 +506,8 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str } -class ElementMap(dict): +@dataclass +class ElementMap: """ Map a PySimpleGUI element to a specific `DataSet` column. @@ -516,42 +517,32 @@ class ElementMap(dict): the bind parameter of `Form` creation, or by executing `Form.auto_map_elements()` as long as the Table.column naming convention is used, This method can be used to manually map any element to any `DataSet` column regardless of naming convention. + + :param element: A PySimpleGUI Element + :param dataset: A `DataSet` object + :param column: The name of the column to bind to the element + :param where_column: Used for key, value shorthand + :param where_value: Used for key, value shorthand + :returns: None """ - def __init__( - self, - element: sg.Element, - dataset: DataSet, - column: str, - where_column: str = None, - where_value: str = None, - ) -> None: - """ - Create a new ElementMap instance. + element: sg.Element + dataset: DataSet + column: str + where_column: str = None + where_value: str = None - :param element: A PySimpleGUI Element - :param dataset: A `DataSet` object - :param column: The name of the column to bind to the element - :param where_column: Used for key, value shorthand - :param where_value: Used for key, value shorthand - :returns: None - """ - super().__init__() - self["element"] = element - self["dataset"] = dataset - self["table"] = dataset.table - self["column"] = column - self["where_column"] = where_column - self["where_value"] = where_value - - def __getattr__(self, key: str): - try: - return self[key] - except KeyError: - raise KeyError(f"ElementMap has no key {key}.") + def __post_init__(self): + self.table = self.dataset.table - def __setattr__(self, key, value): - self[key] = value + def __getitem__(self, key): + return self.__dict__[key] + + def __setitem__(self, key, value): + self.__dict__[key] = value + + def __contains__(self, item): + return item in self.__dict__ class DataSet: From d99015642daaf0e7dc6e1413ab42512f38ff1ea9 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:28:28 -0400 Subject: [PATCH 015/121] Fix: _invoke_callback --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index f017f92b..3b966b31 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -811,7 +811,7 @@ def set_callback( else: raise RuntimeError(f'Callback "{callback}" not supported.') - def _invoke_callback(callback, *args): + def _invoke_callback(self, callback, *args): # Get the callback's signature signature = inspect.signature(callback) From e42a7b48a9af016d0229c9bb4ab23e9def401617 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:29:30 -0400 Subject: [PATCH 016/121] Rename: 'current_row_updated' -> after_record_edit --- pysimplesql/pysimplesql.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 3b966b31..fb28e638 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -750,7 +750,7 @@ def set_search_order(self, order: List[str]) -> None: self.search_order = order def set_callback( - self, callback: str, fctn: Callable[[Form, sg.Window], bool] + self, callback: str, fctn: Callable[[Form, sg.Window, DataSet.key], bool] ) -> None: """ Set DataSet callbacks. A runtime error will be thrown if the callback is not @@ -781,6 +781,8 @@ def set_callback( after_search called after a search has been performed. The record change will undo if the callback returns False record_changed called after a record has changed (previous,next, etc.) + after_record_edit called after the internal `DataSet` row is edited via a + `sg.Table` cell-edit, or `field` live-update. :param callback: The name of the callback, from the list above :param fctn: The function to call. Note, the function must take at least two @@ -801,7 +803,7 @@ def set_callback( "before_search", "after_search", "record_changed", - "current_row_updated", + "after_record_edit", ] if callback in supported: # handle our convenience aliases @@ -1675,7 +1677,7 @@ def set_current( :param column: The column you want to set the value for :param value: A value to set the current record's column to :param write_event: (optional) If True, writes an event to PySimpleGui - as `current_row_updated`. + as `after_record_edit`. :returns: None """ logger.debug(f"Setting current record for {self.key}.{column} = {value}") @@ -1683,7 +1685,7 @@ def set_current( self.rows.loc[self.rows.index[self.current_index], column] = value if write_event: self.frm.window.write_event_value( - "current_row_updated", + "after_record_edit", { "frm_reference": self.frm, "data_key": self.key, @@ -1692,8 +1694,8 @@ def set_current( }, ) # call callback - if "current_row_updated" in self.callbacks: - self.callbacks["current_row_updated"](self.frm, self.frm.window, self.key) + if "after_record_edit" in self.callbacks: + self.callbacks["after_record_edit"](self.frm, self.frm.window, self.key) def get_keyed_value( self, value_column: str, key_column: str, key_value: Union[str, int] From ab28a195e4b0cbe2020c89cc469e631871e898c5 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:31:25 -0400 Subject: [PATCH 017/121] Feat: new `column_info_settings` for quick_editor This allows setting column_info attributes. The dataset is generated, so this setting custom attributes. --- pysimplesql/pysimplesql.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index fb28e638..139ade41 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2673,6 +2673,7 @@ def quick_editor( pk_update_funct: callable = None, funct_param: any = None, skip_prompt_save: bool = False, + column_info_settings: dict = None, ) -> None: """ The quick editor is a dynamic PySimpleGUI Window for quick editing of tables. @@ -2685,6 +2686,8 @@ def quick_editor( select by default when the quick editor loads. :param funct_param: (optional) A parameter to pass to the `pk_update_funct` :param skip_prompt_save: (Optional) True to skip prompting to save dirty records + :param column_info_settings: (Optional) Set Column attributes in + `DataSet.column_info`, in the form of {column_name {attribute : value}}. :returns: None """ # prompt_save @@ -2778,6 +2781,12 @@ def quick_editor( else: quick_frm[data_key].set_by_pk(pk_update_funct(funct_param)) + if column_info_settings: + for col, kwargs in column_info_settings.items(): + if quick_frm[data_key].column_info[col]: + for attr, value in kwargs.items(): + quick_frm[data_key].column_info[col][attr] = value + while True: event, values = quick_win.read() From 7fd808e8522a458f7f4693050112c4a6c3347b43 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:32:02 -0400 Subject: [PATCH 018/121] Feat: Info StatusBar for quick_editor --- pysimplesql/pysimplesql.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 139ade41..b2fea8fb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2757,6 +2757,13 @@ def quick_editor( fields_layout.append([sg.Sizer(h_pixels=0, v_pixels=y_pad)]) layout.append([sg.Frame("Fields", fields_layout, expand_x=True)]) layout.append([sg.Sizer(h_pixels=0, v_pixels=10)]) + layout.append( + [ + sg.StatusBar( + " " * 100, key="info:quick_editor", metadata={"type": TYPE_INFO} + ) + ], + ) quick_win = sg.Window( lang.quick_edit_title.format_map(LangFormat(data_key=data_key)), From 5b7baf848a67efffe44d3328d2e6d3f80f4b8a44 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:37:12 -0400 Subject: [PATCH 019/121] Feat: an `info_element` Adds an info-element that displays the str of an info msg. Useful to use as a status bar. Form.add_info_element is called in auto_map_elements Then popup handles updating them when an info msg is created. --- pysimplesql/pysimplesql.py | 89 ++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index b2fea8fb..44bdffe4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3217,13 +3217,13 @@ def bind(self, win: sg.Window) -> None: """ logger.info("Binding Window to Form") self.window = win - self.popup = Popup(self.window) + self.popup = Popup(self) self.auto_map_elements(win) self.auto_map_events(win) self.update_elements() # Creating cell edit instance, even if we arn't going to use it. self._celledit = _CellEdit(self) - self.window.TKroot.bind("", self._celledit, "+") + self.window.TKroot.bind("", self._celledit) self._liveupdate = _LiveUpdate(self) if self.live_update: self.set_live_update(enable=True) @@ -3504,6 +3504,18 @@ def map_element( ElementMap(element, dataset, column, where_column, where_value) ) + def add_info_element(self, element: Union[sg.StatusBar, sg.Text]) -> None: + """ + Add an element to be updated with info messages. Must be either + :param element: A PySimpleGUI Element + :returns: None + """ + if not isinstance(element, (sg.StatusBar, sg.Text)): + logger.debug(f"Can only add info {str(element)}") + return + logger.debug(f"Mapping element {element.key}") + self.popup.info_elements.append(element) + def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: """ Automatically map PySimpleGUI Elements to `DataSet` columns. A special naming @@ -3534,7 +3546,12 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: continue # Process the filter to ensure this element should be mapped to this Form - if element.metadata["filter"] == self.filter: + if ( + "filter" in element.metadata + and element.metadata["filter"] == self.filter + ): + element.metadata["Form"] = self + if self.filter is None and "filter" not in element.metadata: element.metadata["Form"] = self # Skip this element if it's an event @@ -3631,6 +3648,9 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: else: logger.debug(f"Can not add selector {str(element)}") + elif element.metadata["type"] == TYPE_INFO: + self.add_info_element(element) + def set_element_clauses( self, element: sg.Element, where_clause: str = None, order_clause: str = None ) -> None: @@ -4675,15 +4695,17 @@ class Popup: Has popup functions for internal use. Stores last info popup as last_info """ - def __init__(self, window=None): + def __init__(self, frm_reference: Form = None): """ Create a new Popup instance :returns: None. """ - self.last_info_msg: str = "" + self.frm = frm_reference self.popup_info = None - if window: - self.window = window + self.last_info_msg: str = "" + self.last_info_time = None + self.info_elements = [] + self._timeout_id = None def ok(self, title, msg): """ @@ -4783,6 +4805,7 @@ def info( if auto_close_seconds is None: auto_close_seconds = themepack.popup_info_auto_close_seconds self.last_info_msg = msg + self.update_info_element() if display_message: msg_lines = msg.splitlines() layout = [[sg.Text(line, font="bold")] for line in msg_lines] @@ -4800,7 +4823,9 @@ def info( enable_close_attempted_event=True, icon=themepack.icon, ) - self.window.TKroot.after(int(auto_close_seconds * 1000), self._auto_close) + self.popup_info.TKroot.after( + int(auto_close_seconds * 1000), self._auto_close + ) def _auto_close(self): """ @@ -4810,6 +4835,54 @@ def _auto_close(self): self.popup_info.close() self.popup_info = None + def update_info_element( + self, + message: str = None, + auto_erase_seconds: int = None, + timeout=False, + erase: bool = False, + ) -> None: + """ + Update any mapped info elements: + + :param message: Text message to update info elements with + :param auto_erase_seconds: The number of seconds before automatically + erasing the information element. If None, the default value from themepack + will be used. + :param timeout: A boolean flag indicating whether to erase the information + element. If True, and the elapsed time since the information element was + last updated exceeds the auto_erase_seconds, the element will be cleared. + :param erase: Default False. Erase info elements + """ + if auto_erase_seconds is None: + auto_erase_seconds = themepack.info_element_auto_erase_seconds + + # set the text-string to update + message = message or self.last_info_msg + if erase: + message = "" + if self._timeout_id: + self.frm.window.TKroot.after_cancel(self._timeout_id) + + elif timeout and self.last_info_time: + elapsed_sec = time() - self.last_info_time + if elapsed_sec >= auto_erase_seconds: + message = "" + + # update elements + for element in self.info_elements: + element.update(message) + + # record time of update, and tk.after + if not erase and self.frm: + self.last_info_time = time() + if self._timeout_id: + self.frm.window.TKroot.after_cancel(self._timeout_id) + self._timeout_id = self.frm.window.TKroot.after( + int(auto_erase_seconds * 1000), + lambda: self.update_info_element(timeout=True), + ) + class ProgressBar: def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100): From 8123e87d56fb9c8f9168fe53d5ac41744511e864 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:40:01 -0400 Subject: [PATCH 020/121] Big Feat: refactor Column and add new specialized Col classes Changed Column to a dataclass Added: -BoolCol -DateCol -DateTimeCol -DecimalCol -FloatCol -IntCol -StrCol -TimeCol and also two `base col classes`: LengthCol MinMaxCol that the others subclass --- pysimplesql/pysimplesql.py | 397 +++++++++++++++++++++++++++---------- 1 file changed, 294 insertions(+), 103 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 44bdffe4..cb21d3ae 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7776,6 +7776,7 @@ class Abstractions: # The column abstraction hides the complexity of dealing with SQL columns, getting their # names, default values, data types, primary key status and notnull status # -------------------------------------------------------------------------------------- +@dataclass(order=True) class Column: """ @@ -7791,54 +7792,27 @@ class Column: :caption: Example code """ - def __init__( - self, - name: str, - domain: str, - notnull: bool, - default: None, - pk: bool, - virtual: bool = False, - generated: bool = False, - ): - self._column = { - "name": name, - "domain": domain, - "notnull": notnull, - "default": default, - "pk": pk, - "virtual": virtual, - "generated": generated, - } - - def __str__(self): - return f"Column: {self._column}" + name: str + domain: str + notnull: bool + default: None + pk: bool + virtual: bool = False + generated: bool = False + python_type: Type[object] = Any + custom_cast_fn: callable = None + custom_validate_fn: callable = None - def __repr__(self): - return f"Column: {self._column}" - - def __getitem__(self, item): - return self._column[item] + def __getitem__(self, key): + return self.__dict__[key] def __setitem__(self, key, value): - self._column[key] = value - - def __lt__(self, other, key): # noqa PLE0302 - return self._column[key] < other._column[key] + self.__dict__[key] = value def __contains__(self, item): - return item in self._column - - def __getattr__(self, key): - return self._column[key] - - def __setattr__(self, key, value): - if key == "_column": - super().__setattr__(key, value) - else: - self._column[key] = value + return item in self.__dict__ - def cast(self, value: any) -> any: + def cast(self, value: Any) -> Any: """ Cast a value to the appropriate data type as defined by the column info for the column. This can be useful for comparing values between the database and the @@ -7847,80 +7821,297 @@ def cast(self, value: any) -> any: :param value: The value you would like to cast :returns: The value, cast to a type as defined by the domain """ - # convert the data into the correct data type using the domain in ColumnInfo - domain = self.domain + if self.custom_cast_fn: + try: + return self.custom_cast_fn(value) + except Exception as e: # noqa: BLE001 + logger.debug(f"Error running custom_cast_fn, {e}") + return str(value) - # String type casting - if domain in ["TEXT", "VARCHAR", "CHAR"]: - # convert to str - value = str(value) + def validate(self, value: Any) -> bool: + value = self.cast(value) - # Integer type casting - elif domain in ["INT", "INTEGER", "BOOLEAN"]: - try: - if isinstance(value, int): - pass - elif isinstance(value, ElementRow): - value = int(value) - elif type(value) is str: - value = float(value) - if value == int(value): - value = int(value) - except (ValueError, TypeError): - value = str(value) + if self.notnull and value in EMPTY: + return ValidateResponse(ValidateRule.REQUIRED, value, self.notnull) - # float type casting - elif domain in ["REAL", "DOUBLE", "DECIMAL", "FLOAT"]: - try: - value = float(value) - except ValueError: - value = str(value) + if value in EMPTY: + return ValidateResponse() - # Date casting - elif domain == "DATE": + if self.custom_validate_fn: try: - if not isinstance(value, dt.date): - value = dt.datetime.strptime(value, "%Y-%m-%d").date() - # TODO: ValueError for sqlserver returns: - # date(): 2023-04-27 15:31:13.170000 - except (TypeError, ValueError) as e: - logger.debug( - f"Unable to cast {value} to a datetime.date object. " - f"Casting to string instead. " - f"{e=}" - ) - value = str(value) + response = self.custom_validate_fn(value) + if response.exception: + return response + except Exception as e: # noqa: BLE001 + logger.debug(f"Error running custom_validate_fn, {e}") + + if self.python_type == Any: + return ValidateResponse() + + if not isinstance(value, self.python_type): + return ValidateResponse( + ValidateRule.PYTHON_TYPE, value, self.python_type.__name__ + ) + + return ValidateResponse() + + +class MinMaxCol(Column): + """ + Column subclass representing a value with minimum and maximum constraints. + + This class extends the functionality of the base `Column` class to include optional + validation based on minimum and maximum values. + + :param min_value: The minimum allowed value for the column (inclusive). Defaults + to None, indicating no minimum constraint. + :type min_value: Any valid value type compatible with the column's data type. + :param max_value: The maximum allowed value for the column (inclusive). Defaults + to None, indicating no maximum constraint. + :type max_value: Any valid value type compatible with the column's data type. + """ + + def __init__(self, min_value=None, max_value=None, **kwargs): + super().__init__(**kwargs) + self.min_value = min_value + self.max_value = max_value + + def validate(self, value): + response = super().validate(value) + if response.exception: + return response + + value = self.cast(value) + + if self.min_value is not None and value < self.min_value: + return ValidateResponse(ValidateRule.MIN_VALUE, value, self.min_value) + + if self.max_value is not None and value > self.max_value: + return ValidateResponse(ValidateRule.MAX_VALUE, value, self.max_value) + + return ValidateResponse() + + +class LengthCol(Column): + """ + Column subclass for length-constrained columns. + + This class represents a column with length constraints. It inherits from the base + `Column` class and adds attributes to store the maximum and minimum length values. + The `validate` method is overridden to include length validations. + + :param max_length: Maximum length allowed for the column value. + :param min_length: Minimum length allowed for the column value. + """ + + def __init__(self, max_length: int = None, min_length: int = None, **kwargs): + super().__init__(**kwargs) + self.max_length = int(max_length) if max_length is not None else None + self.min_length = int(min_length) if min_length is not None else None + + def validate(self, value): + response = super().validate(value) + if response.exception: + return response + + if self.min_length is not None and len(str(value)) < self.min_length: + return ValidateResponse(ValidateRule.MIN_LENGTH, value, self.min_length) + + if self.max_length is not None and len(str(value)) > self.max_length: + return ValidateResponse(ValidateRule.MAX_LENGTH, value, self.max_length) + + return ValidateResponse() + + +class BoolCol(Column): + def __init__(self, *args, **kwargs): + super().__init__(**kwargs) + self.python_type = bool - elif domain == "TIMESTAMP": - timestamp_formats = ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S.%f"] + def cast(self, value): + return checkbox_to_bool(value) - parsed = False - for timestamp_format in timestamp_formats: + +class DateCol(MinMaxCol): + def __init__(self, date_format: str = DATE_FORMAT, **kwargs): + super().__init__(**kwargs) + self.python_type = dt.date + self.date_format = date_format + + def cast(self, value): + if isinstance(value, self.python_type): + return value + try: + return dt.datetime.strptime(value, self.date_format).date() + except (TypeError, ValueError) as e: + # Value contains seconds, remove them and try parsing again + if len(value.split(":")) > 2: + value_without_seconds = ":".join(value.split(":")[:2]) try: - value = dt.datetime.strptime(value, timestamp_format) - # value = dt.datetime() - parsed = True - break + return dt.datetime.strptime( + value_without_seconds, self.date_format + ).date() except ValueError: pass - if not parsed: - logger.debug( - "Unable to cast datetime/time/timestamp. Casting to string instead." - ) - value = str(value) + # try to match partial date + if value.endswith("-"): + value = value.rstrip("-") + sections = re.split(r"(%[^%])", self.date_format) + partial_formats = [ + "".join(sections[: i + 1]) + for i in range(len(sections)) + if sections[i].startswith("%") + ] + for format_str in partial_formats: + try: + return dt.datetime.strptime(value, format_str).date() + except (TypeError, ValueError): + pass + logger.debug( + f"Unable to cast {value} to a datetime.date object. " + f"Casting to string instead. " + f"{e=}" + ) + # else, cast to str + return super().cast(value) - # other date/time casting - # TODO: i'm sure there is a lot of work to do here - elif domain in ["TIME", "DATETIME"]: + +class DateTimeCol(MinMaxCol): + def __init__( + self, + datetime_format_list: List[str] = [ + DATETIME_FORMAT, + DATETIME_FORMAT_MICROSECOND, + TIMESTAMP_FORMAT, + TIMESTAMP_FORMAT_MICROSECOND, + ], + **kwargs, + ): + super().__init__(**kwargs) + self.python_type = dt.datetime + self.datetime_format_list = datetime_format_list + + def cast(self, value): + if isinstance(value, self.python_type): + return value + for datetime_format in self.datetime_format_list: try: - value = dt.date(value) - except TypeError: - print( - "Unable to case datetime/time/timestamp. Casting to string instead." - ) - value = str(value) - return value + return dt.datetime.strptime(value, datetime_format) + except ValueError: + pass + logger.debug( + "Unable to cast datetime/time/timestamp. Casting to string instead." + ) + return super().cast(value) + + +class DecimalCol(MinMaxCol): + def __init__(self, precision=10, scale=2, **kwargs): + super().__init__(**kwargs) + self.python_type = Decimal + self.precision = int(precision) if precision is not None else None + self.scale = int(scale) if scale is not None else None + + def cast(self, value): + if value == "-": + return Decimal(0) + try: + decimal_value = Decimal(value) + return decimal_value.quantize(Decimal("0." + "0" * self.scale)) + except (DecimalException, TypeError): + return super().cast(value) + + def validate(self, value): + response = super().validate(value) + if response.exception: + return response + + value = self.cast(value) + value_precision = len(value.as_tuple().digits) + if self.precision is not None and value_precision > self.precision: + return ValidateResponse(ValidateRule.PRECISION, value, self.precision) + + return ValidateResponse() + + +class FloatCol(LengthCol, MinMaxCol): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.python_type = float + + def cast(self, value): + if value == "-": + return float(0) + try: + return float(value) + except ValueError: + return super().cast(value) + + +class IntCol(LengthCol, MinMaxCol): + def __init__( + self, + *args, + truncate_decimals: bool = False, + **kwargs, + ): + super().__init__(*args, **kwargs) + self.python_type = int + self.truncate_decimals = truncate_decimals + + def cast(self, value, truncate_decimals: bool = None): + truncate_decimals = ( + truncate_decimals + if truncate_decimals is not None + else self.truncate_decimals + ) + value_backup = value + if value in ["-", "", None]: + return None + try: + if isinstance(value, int): + return value + if isinstance(value, ElementRow): + return int(value) + if type(value) is str: + value = float(value) + if type(value) is float: + int_value = int(value) + if value == int_value or self.truncate_decimals: + return int_value + return str(value_backup) + except (ValueError, TypeError): + return super().cast(value_backup) + + +class StrCol(LengthCol): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.python_type = str + + def cast(self, value): + return super().cast(value) + + +class TimeCol(Column): + def __init__(self, time_format: str = TIME_FORMAT, **kwargs): + super().__init__(**kwargs) + self.python_type = dt.time + self.time_format = time_format + + def cast(self, value): + if isinstance(value, self.python_type): + return value + try: + return dt.datetime.strptime(value, self.time_format).time() + except (TypeError, ValueError) as e: + logger.debug( + f"Unable to cast {value} to a datetime.time object. " + f"Casting to string instead. " + f"{e=}" + ) + return super().cast(value) class ColumnInfo(List): From fa7898abe6b4cbe3d887a5041c2d0c6120169325 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:40:45 -0400 Subject: [PATCH 021/121] Fix: bool column mapping was broken --- pysimplesql/pysimplesql.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index cb21d3ae..43050ca4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2525,9 +2525,9 @@ def table_values( and self.column_info[column].domain == "BOOLEAN" ] for col in bool_columns: - rows[col] = ( - themepack.checkbox_true - if checkbox_to_bool(rows[col]) + rows[col] = rows[col].apply( + lambda x: themepack.checkbox_true + if checkbox_to_bool(x) else themepack.checkbox_false ) From 203f7e9314ed55684b5669ca3296930b849ae738 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:41:17 -0400 Subject: [PATCH 022/121] Fix: I broke sorting --- pysimplesql/pysimplesql.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 43050ca4..0be045aa 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2869,11 +2869,11 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: column_copy = self.map_fk_descriptions(column_copy, [column])[column] # Assign the transformed column to the temporary column - temp_column = f"temp_{rel.parent_table}.{rel.pk_column}" - self.rows[temp_column] = column_copy + tmp_column = f"temp_{rel.parent_table}.{rel.pk_column}" + self.rows[tmp_column] = column_copy # Use the temporary column as the new sorting column - column = temp_column + column = tmp_column transformed = True break From 171014f80c67eddfe00b9e00ae25fca8c91477e1 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:45:27 -0400 Subject: [PATCH 023/121] Refactor: use new python_type vs domain --- pysimplesql/pysimplesql.py | 122 ++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 0be045aa..1a08231d 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2517,12 +2517,12 @@ def table_values( rows = rows[mask_pd] # transform bool - if themepack.display_boolean_as_checkbox: + if themepack.display_bool_as_checkbox: bool_columns = [ column for column in columns if self.column_info[column] - and self.column_info[column].domain == "BOOLEAN" + and self.column_info[column].python_type == bool ] for col in bool_columns: rows[col] = rows[col].apply( @@ -2738,7 +2738,10 @@ def quick_editor( # make sure isn't pk if col != self.pk_column: # display checkboxes - if self.column_info[col].domain == "BOOLEAN": + if ( + self.column_info[column] + and self.column_info[column].python_type == bool + ): fields_layout.append([field(column, sg.Checkbox)]) found = True break @@ -2883,13 +2886,7 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: if ( not transformed and self.column_info[column] - and self.column_info[column].domain - in [ - "DATE", - "DATETIME", - "TIME", - "TIMESTAMP", - ] + and self.column_info[column].python_type in (dt.date, dt.time, dt.datetime) ): tmp_column = f"temp_{column}" self.rows[tmp_column] = pd.to_datetime(self.rows[column]) @@ -7084,7 +7081,7 @@ def edit(self, event): ) # or a checkbox - elif self.frm[data_key].column_info[column].domain in ["BOOLEAN"]: + elif self.frm[data_key].column_info[column].python_type == bool: widget_type = TK_CHECKBUTTON width = ( width @@ -7093,7 +7090,7 @@ def edit(self, event): ) # or a date - elif self.frm[data_key].column_info[column].domain in ["DATE"]: + elif self.frm[data_key].column_info[column].python_type == dt.date: text = self.frm[data_key].column_info[column].cast(text) widget_type = TK_DATEPICKER width = ( @@ -7251,7 +7248,7 @@ def accept( new_value = combobox_values[self.field.current()] # if boolean, set - if widget_type == TK_CHECKBUTTON and themepack.display_boolean_as_checkbox: + if widget_type == TK_CHECKBUTTON and themepack.display_bool_as_checkbox: new_value = ( themepack.checkbox_true if checkbox_to_bool(new_value) @@ -8132,45 +8129,34 @@ class ColumnInfo(List): :caption: Example code """ + # List of required SQL types to check against when user sets custom values + _python_types = [ + "str", + "int", + "float", + "Decimal", + "bool", + "time", + "date", + "datetime", + ] + def __init__(self, driver: SQLDriver, table: str): self.driver = driver self.table = table - # List of required SQL types to check against when user sets custom values - self._domains = [ - "TEXT", - "VARCHAR", - "CHAR", - "INTEGER", - "REAL", - "DOUBLE", - "FLOAT", - "DECIMAL", - "BOOLEAN", - "TIME", - "DATE", - "DATETIME", - "TIMESTAMP", - ] - # Defaults to use for Null values returned from the database. These can be # overwritten by the user and support function calls as well by using # ColumnInfo.set_null_default() and ColumnInfo.set_null_defaults() self.null_defaults = { - "TEXT": lang.description_column_str_null_default, - "VARCHAR": lang.description_column_str_null_default, - "CHAR": lang.description_column_str_null_default, - "INT": 0, - "INTEGER": 0, - "REAL": 0.0, - "DOUBLE": 0.0, - "FLOAT": 0.0, - "DECIMAL": 0.0, - "BOOLEAN": 0, - "TIME": lambda x: dt.datetime.now().strftime("%H:%M:%S"), - "DATE": lambda x: dt.date.today().strftime("%Y-%m-%d"), - "TIMESTAMP": lambda x: dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - "DATETIME": lambda x: dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "str": lang.description_column_str_null_default, + "int": 0, + "float": 0.0, + "Decimal": Decimal(0), + "bool": 0, + "time": lambda x: dt.datetime.now().strftime(TIME_FORMAT), + "date": lambda x: dt.date.today().strftime(DATE_FORMAT), + "datetime": lambda x: dt.datetime.now().strftime(DATETIME_FORMAT), } super().__init__() @@ -8226,7 +8212,7 @@ def default_row_dict(self, dataset: DataSet) -> dict: d = {} for c in self: default = c.default - domain = c.domain + python_type = c.python_type.__name__ # First, check to see if the default might be a database function if self._looks_like_function(default): @@ -8252,7 +8238,7 @@ def default_row_dict(self, dataset: DataSet) -> dict: # The stored default is a literal value, lets try to use it: if default in [None, "None"]: try: - null_default = self.null_defaults[domain] + null_default = self.null_defaults[python_type] except KeyError: # Perhaps our default dict does not yet support this datatype null_default = None @@ -8271,11 +8257,9 @@ def default_row_dict(self, dataset: DataSet) -> dict: default = null_default # put defaults in other fields - elif domain not in [ - "TEXT", - "VARCHAR", - "CHAR", - ]: # (don't put 'New Record' in other txt fields) + + # don't put txt in other txt fields + elif c.python_type != str: # If our default is callable, call it. if callable(null_default): default = null_default() @@ -8288,7 +8272,7 @@ def default_row_dict(self, dataset: DataSet) -> dict: else: # Load the default that was fetched from the database # during ColumnInfo creation - if domain in ["TEXT", "VARCHAR", "CHAR"]: + if c.python_type == str: # strip quotes from default strings as they seem to get passed with # some database-stored defaults # strip leading and trailing quotes @@ -8302,38 +8286,40 @@ def default_row_dict(self, dataset: DataSet) -> dict: dataset.transform(dataset, d, TFORM_DECODE) return d - def set_null_default(self, domain: str, value: object) -> None: + def set_null_default(self, python_type: str, value: object) -> None: """ - Set a Null default for a single SQL type. + Set a Null default for a single python type. - :param domain: The SQL type to set the default for - ('INTEGER', 'TEXT', 'BOOLEAN', etc.) + :param python_type: This should be equal to what calling `.__name__` on the + Column `python_type` would equal: 'str', 'int', 'float', 'Decimal', 'bool', + 'time', 'date', or 'datetime'. :param value: The new value to set the SQL type to. This can be a literal or even a callable :returns: None """ - if domain not in self._domains: + if python_type not in self._python_types: RuntimeError( - f"Unsupported SQL Type: {domain}. Supported types are: {self._domains}" + f"Unsupported SQL Type: {python_type}. Supported types are: " + f"{self._python_types}" ) - self.null_defaults[domain] = value + self.null_defaults[python_type] = value def set_null_defaults(self, null_defaults: dict) -> None: """ - Set Null defaults for all SQL types. + Set Null defaults for all python types. - supported types: 'TEXT','VARCHAR', 'CHAR', 'INTEGER', 'REAL', 'DOUBLE', - 'FLOAT', 'DECIMAL', 'BOOLEAN', 'TIME', 'DATE', 'DATETIME', 'TIMESTAMP' - :param null_defaults: A dict of SQL types and default values. This can be a + Supported types: 'str', 'int', 'float', 'Decimal', 'bool', + 'time', 'date', or 'datetime'. + :param null_defaults: A dict of python types and default values. This can be a literal or even a callable :returns: None """ # Check if the null_defaults dict has all the required keys: - if not all(key in null_defaults for key in self._domains): + if not all(key in null_defaults for key in self._python_types): RuntimeError( f"The supplied null_defaults dictionary does not havle all required SQL" - f" types. Required: {self._domains}" + f" types. Required: {self._python_types}" ) self.null_defaults = null_defaults @@ -8864,11 +8850,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: pk_column = self.quote_column(dataset.pk_column) # Set description if TEXT - if dataset.column_info[dataset.description_column].domain in [ - "TEXT", - "VARCHAR", - "CHAR", - ]: + if dataset.column_info[dataset.description_column].python_type == str: description_column = self.quote_column(dataset.description_column) description = ( f"{lang.duplicate_prepend}{dataset.get_description_for_pk(pk)}" From 31b485aa355eef723e93dfeafdfa50c54ed4f40b Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:47:32 -0400 Subject: [PATCH 024/121] Refactor/Feat: SqlDriver new functions: - parse_domain - get_column_class --- pysimplesql/pysimplesql.py | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1a08231d..2c883e8f 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -8463,6 +8463,7 @@ def __init__( to close the database. """ # Be sure to call super().__init__() in derived class! + self.SQL_CONSTANTS = [] self.con = None self.name = name self.requires = requires @@ -8490,17 +8491,6 @@ def __init__( # override this in derived __init__() (defaults to single quotes) self.quote_value_char = value_quote - def import_failed(self, exception) -> None: - popup = Popup() - requires = ", ".join(self.requires) - popup.ok( - lang.import_module_failed_title, - lang.import_module_failed.format_map( - LangFormat(name=self.name, requires=requires, exception=exception) - ), - ) - exit(0) - def check_reserved_keywords(self, value: bool) -> None: """ SQLDrivers can check to make sure that field names respect their own reserved @@ -8545,7 +8535,7 @@ def execute( """ raise NotImplementedError - def execute_script(self, script: str, silent: bool = False): + def execute_script(self, script: str, encoding: str): raise NotImplementedError def get_tables(self): @@ -8948,7 +8938,7 @@ def save_record( # Set empty fields to None for k, v in changed_row.items(): - if v == "": # noqa: PLC1901 + if v == "": changed_row[k] = None # quote appropriately @@ -8989,6 +8979,39 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): values = [value for key, value in row.items()] return self.execute(query, tuple(values)) + # --------------------------------------------------------------------- + # Probably won't need to implement the following functions + # --------------------------------------------------------------------- + + def import_failed(self, exception) -> None: + popup = Popup() + requires = ", ".join(self.requires) + popup.ok( + lang.import_module_failed_title, + lang.import_module_failed.format_map( + LangFormat(name=self.name, requires=requires, exception=exception) + ), + ) + exit(0) + + def parse_domain(self, domain): + domain_parts = domain.split("(") + domain_name = domain_parts[0].strip().upper() + + if len(domain_parts) > 1: + domain_args = domain_parts[1].rstrip(")").split(",") + domain_args = [arg.strip() for arg in domain_args] + else: + domain_args = [] + + return domain_name, domain_args + + def get_column_class(self, domain): + if domain in self.COLUMN_CLASS_MAP: + return self.COLUMN_CLASS_MAP[domain] + logger.info(f"Mapping {domain} to generic Column class") + return Column + # -------------------------------------------------------------------------------------- # SQLITE3 DRIVER From 2d9a5880deefd68cb90c88180f2c4c7202383d6c Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:49:19 -0400 Subject: [PATCH 025/121] Refactor/Feat/Fix: Sqlite driver added Decimal handling for sqlite Added: COLUMN_CLASS_MAP SQL_CONSTANTS Fixed: generated capturing Moved: execute_script --- pysimplesql/pysimplesql.py | 65 ++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 2c883e8f..507c39a2 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9021,6 +9021,35 @@ class Sqlite(SQLDriver): The SQLite driver supports SQLite3 databases. """ + DECIMAL_DOMAINS = ["DECIMAL", "DECTEXT", "MONEY", "NUMERIC"] + + COLUMN_CLASS_MAP = { + "BOOLEAN": BoolCol, + "CLOB": StrCol, + "CHARACTER": StrCol, + "DATE": DateCol, + "DATETIME": DateTimeCol, + "DECIMAL": DecimalCol, + "DECTEXT": DecimalCol, + "INTEGER": IntCol, + "MONEY": DecimalCol, + "NATIVE CHARACTER": StrCol, + "NCHAR": StrCol, + "NVARCHAR": StrCol, + "NUMERIC": DecimalCol, + "REAL": FloatCol, + "TEXT": StrCol, + "VARCHAR": StrCol, + "VARYING CHARACTER": StrCol, + } + + SQL_CONSTANTS = [ + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "NULL", + ] + def __init__( self, db_path=None, @@ -9039,6 +9068,12 @@ def __init__( self.import_required_modules() + # Register the adapter + sqlite3.register_adapter(Decimal, self.adapt_decimal) + # Register the converter + for domain in self.DECIMAL_DOMAINS: + sqlite3.register_converter(domain, self.convert_decimal) + new_database = False if db_path is not None: logger.info(f"Opening database: {db_path}") @@ -9075,7 +9110,9 @@ def import_required_modules(self): self.import_failed(e) def connect(self, database): - self.con = sqlite3.connect(database) + self.con = sqlite3.connect( + database, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES + ) def execute( self, @@ -9090,6 +9127,7 @@ def execute( cursor = self.con.cursor() exception = None + try: cur = cursor.execute(query, values) if values else cursor.execute(query) except sqlite3.Error as e: @@ -9113,6 +9151,11 @@ def execute( [dict(row) for row in rows], lastrowid, exception, column_info ) + def execute_script(self, script, encoding): + with open(script, "r", encoding=encoding) as file: + logger.info(f"Loading script {script} into database.") + self.con.executescript(file.read()) + def close(self): # Only do cleanup if this is not an imported database if not self.imported_database: @@ -9139,15 +9182,22 @@ def column_info(self, table): col_info = ColumnInfo(self, table) for _, row in rows.iterrows(): + domain, domain_args = self.parse_domain(row["type"]) + col_class = self.get_column_class(domain) + + # TODO: should we exclude hidden columns? + # if row["hidden"] == 1: + # continue name = row["name"] names.append(name) domain = row["type"] notnull = row["notnull"] default = row["dflt_value"] pk = row["pk"] - generated = row["hidden"] + generated = row["hidden"] in [2, 3] col_info.append( - Column( + col_class( + *domain_args, name=name, domain=domain, notnull=notnull, @@ -9191,10 +9241,11 @@ def relationships(self): relationships.append(dic) return relationships - def execute_script(self, script, encoding): - with open(script, "r", encoding=encoding) as file: - logger.info(f"Loading script {script} into database.") - self.con.executescript(file.read()) + def adapt_decimal(self, d): + return str(d) + + def convert_decimal(self, s): + return Decimal(s.decode("utf-8")) # -------------------------------------------------------------------------------------- From feff8b31d4d0bd4ff589a0cf4f2d5d244179f9d4 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:51:29 -0400 Subject: [PATCH 026/121] Refactor/Feat: MysqlDriver Added: COLUMN_CLASS_MAP SQL_CONSTANTS new arg: tinyint1_is_boolean Fixed executescript (mysql doesn't have one) Converted to use new specialized type Cols Fixed duplicate handling - mysql doesn't have a 'RETURNING' --- pysimplesql/pysimplesql.py | 155 ++++++++++++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 21 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 507c39a2..1a77827c 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9428,8 +9428,45 @@ class Mysql(SQLDriver): The Mysql driver supports MySQL databases. """ + COLUMN_CLASS_MAP = { + "BIT": BoolCol, + "BIGINT": IntCol, + "CHAR": StrCol, + "DATE": DateCol, + "DATETIME": DateTimeCol, + "DECIMAL": DecimalCol, + "DOUBLE": FloatCol, + "FLOAT": FloatCol, + "INT": IntCol, + "INTEGER": IntCol, + "LONGTEXT": StrCol, + "MEDIUMINT": IntCol, + "MEDIUMTEXT": StrCol, + "MULTILINESTRING": StrCol, + "NUMERIC": DecimalCol, + "REAL": FloatCol, + "SMALLINT": IntCol, + "TEXT": StrCol, + "TIME": TimeCol, + "TIMESTAMP": DateTimeCol, + "TINYINT": IntCol, + "TINYTEXT": StrCol, + "VARCHAR": StrCol, + "YEAR": IntCol, + } + + SQL_CONSTANTS = ["CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP"] + def __init__( - self, host, user, password, database, sql_script=None, sql_commands=None + self, + host, + user, + password, + database, + sql_script=None, + sql_script_encoding: str = "utf-8", + sql_commands=None, + tinyint1_is_boolean=True, ): super().__init__(name="MySQL", requires=["mysql-connector-python"]) @@ -9440,6 +9477,7 @@ def __init__( self.user = user self.password = password self.database = database + self.tinyint1_is_boolean = tinyint1_is_boolean self.con = self.connect() self.win_pb.update(lang.sqldriver_execute, 50) @@ -9447,12 +9485,23 @@ def __init__( # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") logger.debug(sql_commands) - self.con.executescript(sql_commands) + cursor = self.con.cursor() + for result in cursor.execute(sql_commands, multi=True): + if result.with_rows: + print("Rows produced by statement '{}':".format(result.statement)) + print(result.fetchall()) + else: + print( + "Number of rows affected by statement '{}': {}".format( + result.statement, result.rowcount + ) + ) self.con.commit() + cursor.close() if sql_script is not None: # run SQL script from the file if the database does not yet exist logger.info("Executing sql script from file passed in") - self.execute_script(sql_script) + self.execute_script(sql_script, sql_script_encoding) self.win_pb.close() @@ -9467,7 +9516,7 @@ def connect(self, retries=3): attempt = 0 while attempt < retries: try: - con = mysql.connector.connect( + return mysql.connector.connect( host=self.host, user=self.user, password=self.password, @@ -9518,6 +9567,14 @@ def execute( [dict(row) for row in rows], lastrowid, exception, column_info ) + def execute_script(self, script, encoding): + with open(script, "r", encoding=encoding) as file: + logger.info(f"Loading script {script} into database.") + cursor = self.con.cursor() + cursor.execute(file.read(), multi=True) + self.con.commit() + cursor.close() + def get_tables(self): query = ( "SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = %s" @@ -9527,27 +9584,56 @@ def get_tables(self): def column_info(self, table): # Return a list of column names - query = "DESCRIBE {}".format(table) + query = f"SELECT * FROM information_schema.columns WHERE table_name = '{table}'" rows = self.execute(query, silent=True) col_info = ColumnInfo(self, table) - + rows = rows.fillna("") for _, row in rows.iterrows(): - name = row["Field"] + name = row["COLUMN_NAME"] # Check if the value is a bytes-like object, and decode if necessary type_value = ( - row["Type"].decode("utf-8") - if isinstance(row["Type"], bytes) - else row["Type"] + row["COLUMN_TYPE"].decode("utf-8") + if isinstance(row["COLUMN_TYPE"], bytes) + else row["COLUMN_TYPE"] ) # Capitalize and get rid of the extra information of the row type # I.e. varchar(255) becomes VARCHAR - domain = type_value.split("(")[0].upper() - notnull = row["Null"] == "NO" - default = row["Default"] - pk = row["Key"] == "PRI" + domain, domain_args = self.parse_domain(type_value) + + # TODO, think about an Enum or SetCol + # # domain_args for enum/set are actually a list + # if domain in ["ENUM", "SET"]: + # domain_args = [domain_args] + + if ( + self.tinyint1_is_boolean + and domain == "TINYINT" + and domain_args == ["1"] + ): + col_class = BoolCol + + else: + col_class = self.get_column_class(domain) + if col_class == DecimalCol: + domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] + elif col_class in [FloatCol, IntCol]: + domain_args = [row["NUMERIC_PRECISION"]] + elif col_class == StrCol: + domain_args = [row["CHARACTER_MAXIMUM_LENGTH"]] + + notnull = row["IS_NULLABLE"] == "NO" + default = row["COLUMN_DEFAULT"] + pk = row["COLUMN_KEY"] == "PRI" + generated = row["EXTRA"] in ["VIRTUAL GENERATED", "STORED GENERATED"] col_info.append( - Column( - name=name, domain=domain, notnull=notnull, default=default, pk=pk + col_class( + *domain_args, + name=name, + domain=domain, + notnull=notnull, + default=default, + pk=pk, + generated=generated, ) ) @@ -9588,11 +9674,6 @@ def relationships(self): relationships.append(dic) return relationships - def execute_script(self, script): - with open(script, "r"): - logger.info(f"Loading script {script} into database.") - # TODO - # Not required for SQLDriver def constraint(self, constraint_name): query = ( @@ -9610,6 +9691,38 @@ def constraint(self, constraint_name): delete_rule = row["DELETE_RULE"] return update_rule, delete_rule + def _insert_duplicate_record( + self, table: str, columns: str, pk_column: str, pk: int + ) -> pd.DataFrame: + """ + Inserts duplicate record, sets attrs["lastrowid"] to new record's pk. + + Used by `SQLDriver.duplicate_record` to handle database-specific differences in + returning new primary keys. + + :param table: Escaped table name of record to be duplicated + :param columns: Escaped and comman (,) seperated list of columns + :param pk_column: Non-escaped pk_column + :param pk: Primary key of record + """ + query = ( + f"INSERT INTO {table} ({columns}) " + f"SELECT {columns} FROM {table} " + f"WHERE {self.quote_column(pk_column)} = {pk};" + ) + res = self.execute(query) + if res.attrs["exception"]: + return res + + query = "SELECT LAST_INSERT_ID();" + + res = self.execute(query) + if res.attrs["exception"]: + return res + + res.attrs["lastrowid"] = res.iloc[0]["LAST_INSERT_ID()"].tolist() + return res + # -------------------------------------------------------------------------------------- # MARIADB DRIVER From c12336bff55f68febd3306d8b54ba88679ab9344 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:56:06 -0400 Subject: [PATCH 027/121] Feat/Refactor: Postgres driver Added COLUMN_CLASS_MAP SQL_CONSTANTS added an execute_script function, and correct sql_commands function --- pysimplesql/pysimplesql.py | 91 ++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1a77827c..70e98d59 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9750,6 +9750,30 @@ class Postgres(SQLDriver): The Postgres driver supports PostgreSQL databases. """ + COLUMN_CLASS_MAP = { + "BIGINT": IntCol, + "BIGSERIAL": IntCol, + "BOOLEAN": BoolCol, + "CHARACTER": StrCol, + "CHARACTER VARYING": StrCol, + "DATE": DateCol, + "DOUBLE PRECISION": FloatCol, + "INTEGER": IntCol, + "MONEY": DecimalCol, + "NUMERIC": DecimalCol, + "REAL": FloatCol, + "SMALLINT": IntCol, + "SMALLSERIAL": IntCol, + "SERIAL": IntCol, + "TEXT": StrCol, + "TIME": TimeCol, + "TIMETZ": TimeCol, + "TIMESTAMP": DateTimeCol, + "TIMESTAMPTZ": DateTimeCol, + } + + SQL_CONSTANTS = ["CURRENT_USER", "SESSION_USER", "USER"] + def __init__( self, host, @@ -9757,6 +9781,7 @@ def __init__( password, database, sql_script=None, + sql_script_encoding: str = "utf-8", sql_commands=None, sync_sequences=False, ): @@ -9806,17 +9831,19 @@ def __init__( q = f"SELECT setval('{seq}', 1, false);" self.execute(q, silent=True, auto_commit_rollback=True) - self.win_pb.update("executing SQL commands", 50) + self.win_pb.update(lang.sqldriver_execute, 50) if sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") logger.debug(sql_commands) - self.con.executescript(sql_commands) + cursor = self.con.cursor() + cursor.execute(sql_commands) self.con.commit() + cursor.close() if sql_script is not None: # run SQL script from the file if the database does not yet exist logger.info("Executing sql script from file passed in") - self.execute_script(sql_script) + self.execute_script(sql_script, sql_script_encoding) self.win_pb.close() def import_required_modules(self): @@ -9831,14 +9858,13 @@ def connect(self, retries=3): attempt = 0 while attempt < retries: try: - con = psycopg2.connect( + return psycopg2.connect( host=self.host, user=self.user, password=self.password, database=self.database, # connect_timeout=3, ) - return con except psycopg2.Error as e: print(f"Failed to connect to database ({attempt + 1}/{retries})") print(e) @@ -9883,6 +9909,14 @@ def execute( [dict(row) for row in rows], exception=exception, column_info=column_info ) + def execute_script(self, script, encoding): + with open(script, "r", encoding=encoding) as file: + logger.info(f"Loading script {script} into database.") + cursor = self.con.cursor() + cursor.execute(file.read()) + self.con.commit() + cursor.close() + def get_tables(self): query = ( "SELECT table_name FROM information_schema.tables WHERE " @@ -9896,22 +9930,35 @@ def column_info(self, table: str) -> ColumnInfo: # Return a list of column names query = f"SELECT * FROM information_schema.columns WHERE table_name = '{table}'" rows = self.execute(query, silent=True) - col_info = ColumnInfo(self, table) pk_column = self.pk_column(table) for _, row in rows.iterrows(): name = row["column_name"] domain = row["data_type"].upper() + col_class = self.get_column_class(domain) + domain_args = [] + if col_class == DecimalCol: + domain_args = [row["numeric_precision"], row["numeric_scale"]] + elif col_class in [FloatCol, IntCol]: + domain_args = [row["numeric_precision"]] + elif col_class == StrCol: + domain_args = [row["character_maximum_length"]] notnull = row["is_nullable"] != "YES" default = row["column_default"] # Fix the default value by removing the datatype that is appended to the end if default is not None and "::" in default: default = default[: default.index("::")] - pk = name == pk_column + generated = row["is_generated"] == "ALWAYS" col_info.append( - Column( - name=name, domain=domain, notnull=notnull, default=default, pk=pk + col_class( + *domain_args, + name=name, + domain=domain, + notnull=notnull, + default=default, + pk=pk, + generated=generated, ) ) @@ -9922,7 +9969,7 @@ def pk_column(self, table): "SELECT column_name FROM information_schema.table_constraints tc JOIN " "information_schema.key_column_usage kcu ON tc.constraint_name = " "kcu.constraint_name WHERE tc.constraint_type = 'PRIMARY KEY' AND " - f"tc.table_name = '{table}' " + f"tc.table_name = '{table}';" ) rows = self.execute(query, silent=True) return rows.iloc[0]["column_name"] @@ -10011,9 +10058,6 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): result.attrs["lastid"] = pk return result - def execute_script(self, script): - pass - # -------------------------------------------------------------------------------------- # MS SQLSERVER DRIVER @@ -10234,6 +10278,27 @@ def _insert_duplicate_record( res.attrs["lastrowid"] = res.iloc[0][pk_column].tolist() return res + def insert_record(self, table: str, pk: int, pk_column: str, row: dict): + # Remove the pk column + row = {self.quote_column(k): v for k, v in row.items() if k != pk_column} + + # quote appropriately + table = self.quote_table(table) + + # Remove the primary key column to ensure autoincrement is used! + query = ( + f"INSERT INTO {table} ({', '.join(key for key in row)}) " + f"OUTPUT inserted.{self.quote_column(pk_column)} " + f"VALUES " + f"({','.join(self.placeholder for _ in range(len(row)))}); " + ) + values = [value for key, value in row.items()] + res = self.execute(query, tuple(values)) + if res.attrs["exception"]: + return res + res.attrs["lastrowid"] = res.iloc[0][pk_column].tolist() + return res + # -------------------------------------------------------------------------------------- # MS ACCESS DRIVER From 5673f4e2070030d4068ca5ed571761de9e203de4 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:59:54 -0400 Subject: [PATCH 028/121] Refactor/Feat: SqlServer Add and use COLUMN_CLASS_MAP Add SQL_CONSTANTS get generated columns fix execute_script / sql_commands --- pysimplesql/pysimplesql.py | 98 ++++++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 70e98d59..71a1c53a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -10067,15 +10067,57 @@ class Sqlserver(SQLDriver): The Sqlserver driver supports Microsoft SQL Server databases. """ + COLUMN_CLASS_MAP = { + "BIGINT": IntCol, + "BIT": BoolCol, + "CHAR": StrCol, + "DATE": DateCol, + "DATETIME": DateTimeCol, + "DATETIME2": DateTimeCol, + "DATETIMEOFFSET": DateTimeCol, + "DECIMAL": DecimalCol, + "FLOAT": FloatCol, + "INT": IntCol, + "MONEY": DecimalCol, + "NCHAR": StrCol, + "NTEXT": StrCol, + "NUMERIC": DecimalCol, + "NVARCHAR": StrCol, + "REAL": FloatCol, + "SMALLDATETIME": DateTimeCol, + "SMALLINT": IntCol, + "SMALLMONEY": DecimalCol, + "TEXT": StrCol, + "TIME": TimeCol, + "TIMESTAMP": DateTimeCol, + "TINYINT": IntCol, + "VARCHAR": StrCol, + } + + SQL_CONSTANTS = [ + "CURRENT_USER", + "HOST_NAME", + "NULL", + "SESSION_USER", + "SYSTEM_USER", + "USER", + ] + def __init__( - self, host, user, password, database, sql_script=None, sql_commands=None + self, + host, + user, + password, + database, + sql_script=None, + sql_script_encoding: str = "utf-8", + sql_commands=None, ): super().__init__( name="Sqlserver", requires=["pyodbc"], table_quote="[]", placeholder="?" ) self.import_required_modules() - self.name = "Sqlserver" # is this redundant? self.host = host self.user = user @@ -10087,13 +10129,14 @@ def __init__( # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") logger.debug(sql_commands) - self.con.executescript(sql_commands) + cursor = self.con.cursor() + cursor.execute(sql_commands) self.con.commit() + cursor.close() if sql_script is not None: # run SQL script from the file if the database does not yet exist logger.info("Executing sql script from file passed in") - self.execute_script(sql_script) - + self.execute_script(sql_script, sql_script_encoding) self.win_pb.close() def import_required_modules(self): @@ -10107,7 +10150,7 @@ def connect(self, retries=3, timeout=3): attempt = 0 while attempt < retries: try: - con = pyodbc.connect( + return pyodbc.connect( f"DRIVER={{ODBC Driver 17 for SQL Server}};" f"SERVER={self.host};" f"DATABASE={self.database};" @@ -10115,7 +10158,6 @@ def connect(self, retries=3, timeout=3): f"PWD={self.password}", timeout=timeout, ) - return con except pyodbc.Error as e: print(f"Failed to connect to database ({attempt + 1}/{retries})") print(e) @@ -10165,6 +10207,14 @@ def execute( column_info, ) + def execute_script(self, script, encoding): + with open(script, "r", encoding=encoding) as file: + logger.info(f"Loading script {script} into database.") + cursor = self.con.cursor() + cursor.execute(file.read()) + self.con.commit() + cursor.close() + def get_tables(self): query = ( "SELECT table_name FROM information_schema.tables WHERE table_catalog = ?" @@ -10177,7 +10227,6 @@ def column_info(self, table): query = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?" rows = self.execute(query, [table], silent=True) col_info = ColumnInfo(self, table) - # Get the primary key column(s) pk_columns = [] pk_query = """ @@ -10189,9 +10238,31 @@ def column_info(self, table): for _, pk_row in pk_rows.iterrows(): pk_columns.append(pk_row["COLUMN_NAME"]) + # get the generated columns: + gen_query = ( + "SELECT name " + "FROM sys.columns " + "WHERE object_id = OBJECT_ID(?) " + "AND is_computed = 1;" + ) + generated_columns = [] + gen_rows = self.execute(gen_query, [table], silent=True) + for _, row in gen_rows.iterrows(): + generated_columns.append(row[0]) + + rows = rows.fillna("") + # setup all the variables to be passed to col_info for _, row in rows.iterrows(): name = row["COLUMN_NAME"] domain = row["DATA_TYPE"].upper() + col_class = self.get_column_class(domain) + domain_args = [] + if col_class == DecimalCol: + domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] + elif col_class in [FloatCol, IntCol]: + domain_args = [row["NUMERIC_PRECISION"]] + elif col_class == StrCol: + domain_args = [row["CHARACTER_MAXIMUM_LENGTH"]] notnull = row["IS_NULLABLE"] == "NO" if row["COLUMN_DEFAULT"]: col_default = row["COLUMN_DEFAULT"] @@ -10204,9 +10275,16 @@ def column_info(self, table): else: default = None pk = name in pk_columns + generated = name in generated_columns col_info.append( - Column( - name=name, domain=domain, notnull=notnull, default=default, pk=pk + col_class( + *domain_args, + name=name, + domain=domain, + notnull=notnull, + default=default, + pk=pk, + generated=generated, ) ) From 7ce0f5e1971ab67f1404cc72ed3b80b5b516a8d1 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:01:42 -0400 Subject: [PATCH 029/121] Refactor/Feat: MsAccess driver -Add/use COLUMN_CLASS_MAP -add ability to create an access file, and overwrite one -infer datetype column from column default (if there is one) create/fix sql_commands/sql_file --- pysimplesql/pysimplesql.py | 184 ++++++++++++++++++++++++++++++++++--- 1 file changed, 173 insertions(+), 11 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 71a1c53a..76033150 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -10386,18 +10386,98 @@ class MSAccess(SQLDriver): The MSAccess driver supports Microsoft Access databases. Note that only database interactions are supported, including stored Queries, but not operations dealing with Forms, Reports, etc. + + Note: `Jackcess` and `UCanAccess` libraries may not accurately report decimal places + for `Number` or `Currency` columns. Manual configuration of decimal places may + be required by replacing the placeholders as follows: + frm[DATASET KEY].column_info[COLUMN NAME].scale = 2 """ - def __init__(self, database_file): + COLUMN_CLASS_MAP = { + "BIG_INT": IntCol, + "BOOLEAN": BoolCol, + "DECIMAL": DecimalCol, + "INTEGER": IntCol, + "VARCHAR": StrCol, + "TIMESTAMP": DateTimeCol, + } + + def __init__( + self, + database_file, + overwrite_file: bool = False, + sql_commands: str = None, + sql_script=None, + sql_script_encoding: str = "utf-8", + infer_datetype_from_default_function: bool = True, + use_newer_jackcess: bool = False, + ): + """ + Initialize the MSAccess class. + + :param database_file: The path to the MS Access database file. + :param overwrite_file: If True, prompts the user if the file already exists. If + the user declines to overwrite the file, the provided SQL commands or script + will not be executed. + :param sql_commands: Optional SQL commands to execute after opening the + database. + :param sql_script: Optional SQL script file to execute after opening the + database. + :param sql_script_encoding: The encoding of the SQL script file. Defaults to + 'utf-8'. + :param infer_datetype_from_default_function: If True, specializes a DateTime + column by examining the column's default function. A DateTime column with + '=Date()' will be treated as a 'DateCol', and '=Time()' will be treated as a + 'TimeCol'. Defaults to True. + :param use_newer_jackcess: If True, uses a newer version of the Jackcess library + for improved compatibility, specifically allowing handling of 'attachment' + columns. Defaults to False. + """ + super().__init__( name="MSAccess", requires=["Jype1"], table_quote="[]", placeholder="?" ) - self.import_required_modules() - self.database_file = database_file + self.infer_datetype_from_default_function = infer_datetype_from_default_function + self.use_newer_jackcess = use_newer_jackcess + + if not self.start_jvm(): + logger.debug("Failed to start jvm") + exit() + + # handle if file doesn't exist or user wants to overwrite_file + create_access_file = False + if not os.path.exists(self.database_file): + create_access_file = True + elif os.path.exists(self.database_file) and overwrite_file: + text = sg.popup_get_text(lang.overwrite, title=lang.overwrite_title) + if text == lang.overwrite_prompt: + create_access_file = True + else: + sql_script = None + sql_commands = None + + if create_access_file: + self._create_access_file() + + # then connect self.con = self.connect() + self.win_pb.update(lang.sqldriver_execute, 50) + if sql_commands is not None: + # run SQL script if the database does not yet exist + logger.info("Executing sql commands passed in") + logger.debug(sql_commands) + queries = sql_commands.split(";") # Split the query string by semicolons + for query in queries: + self.execute(query) + if sql_script is not None: + # run SQL script from the file if the database does not yet exist + logger.info("Executing sql script from file passed in") + self.execute_script(sql_script, sql_script_encoding) + self.win_pb.close() + import os import sys @@ -10408,17 +10488,23 @@ def import_required_modules(self): except ModuleNotFoundError as e: self.import_failed(e) - def connect(self): + def start_jvm(self): # Get the path to the 'lib' folder current_path = os.path.dirname(os.path.abspath(__file__)) lib_path = os.path.join(current_path, "lib", "UCanAccess-5.0.1.bin") + jackcess_file = ( + "jackcess-3.0.1.jar" + if not self.use_newer_jackcess + else "jackcess-4.0.5.jar" + ) + jars = [ "ucanaccess-5.0.1.jar", os.path.join("lib", "commons-lang3-3.8.1.jar"), os.path.join("lib", "commons-logging-1.2.jar"), os.path.join("lib", "hsqldb-2.5.0.jar"), - os.path.join("lib", "jackcess-3.0.1.jar"), + os.path.join("lib", jackcess_file), os.path.join("loader", "ucanload.jar"), ] classpath = os.pathsep.join([os.path.join(lib_path, jar) for jar in jars]) @@ -10427,7 +10513,10 @@ def connect(self): jpype.startJVM( jpype.getDefaultJVMPath(), "-ea", f"-Djava.class.path={classpath}" ) + return True + return True + def connect(self): driver_manager = jpype.JPackage("java").sql.DriverManager con_str = f"jdbc:ucanaccess://{self.database_file}" return driver_manager.getConnection(con_str) @@ -10468,6 +10557,7 @@ def execute( metadata = rs.getMetaData() column_count = metadata.getColumnCount() rows = [] + lastrowid = None while rs.next(): row = {} @@ -10505,7 +10595,6 @@ def execute( rows.append(row) # Set the last row ID - lastrowid = None if "insert" in query.lower(): res = self.execute("SELECT @@IDENTITY AS ID") lastrowid = res.iloc[0]["ID"] @@ -10515,23 +10604,71 @@ def execute( stmt.getUpdateCount() return Result.set([], None, exception, column_info) + def execute_script(self, script, encoding): + with open(script, "r", encoding=encoding) as file: + logger.info(f"Loading script {script} into the database.") + script_content = file.read() # Read the entire script content + queries = script_content.split(";") # Split the script by semicolons + for query in queries: + q = query.strip() # Remove leading/trailing whitespace + if q: + self.execute(q) + def column_info(self, table): meta_data = self.con.getMetaData() + + # get column info rs = meta_data.getColumns(None, None, table, None) col_info = ColumnInfo(self, table) pk_columns = [self.pk_column(table)] while rs.next(): + # for debugging + debug = False + if debug: + # fmt: off + columns = ['TABLE_CAT', 'TABLE_SCHEM', 'TABLE_NAME', 'COLUMN_NAME', + 'DATA_TYPE', 'TYPE_NAME', 'COLUMN_SIZE', 'BUFFER_LENGTH', + 'DECIMAL_DIGITS', 'NUM_PREC_RADIX', 'NULLABLE', 'REMARKS', + 'COLUMN_DEF', 'SQL_DATA_TYPE', 'SQL_DATETIME_SUB', + 'CHAR_OCTET_LENGTH', 'ORDINAL_POSITION', 'IS_NULLABLE', + 'SCOPE_CATALOG', 'SCOPE_SCHEMA', 'SCOPE_TABLE', + 'SOURCE_DATA_TYPE', 'IS_AUTOINCREMENT', 'IS_GENERATEDCOLUMN', + 'ORIGINAL_TYPE'] + # fmt: on + for col in columns: + value = str(rs.getString(col)) + print(f"{col}: {value}") name = str(rs.getString("column_name")) domain = str(rs.getString("TYPE_NAME")).upper() notnull = str(rs.getString("IS_NULLABLE")) == "NO" default = str(rs.getString("COLUMN_DEF")) pk = name in pk_columns + generated = str(rs.getString("IS_GENERATEDCOLUMN")) == "YES" + col_class = self.get_column_class(domain) + + domain_args = [] + # handling Date/Time columns, since they are all reported as DateTime + if self.infer_datetype_from_default_function and col_class == DateTimeCol: + if default == "=Date()": + col_class = DateCol + elif default == "=Time()": + col_class = TimeCol + if col_class in [DecimalCol, FloatCol, IntCol, StrCol]: + domain_args = [str(rs.getString("COLUMN_SIZE"))] + if col_class == DecimalCol: + domain_args.append(str(rs.getString("DECIMAL_DIGITS"))) col_info.append( - Column( - name=name, domain=domain, notnull=notnull, default=default, pk=pk + col_class( + *domain_args, + name=name, + domain=domain, + notnull=notnull, + default=default, + pk=pk, + generated=generated, ) ) @@ -10614,9 +10751,7 @@ def _get_column_definitions(self, table_name): cols = "" for c in columns: cols += f"{c['name']} {c['domain']}, " - cols = cols[:-2] - - return cols + return cols[:-2] def _insert_duplicate_record( self, table: str, columns: str, pk_column: str, pk: int @@ -10648,6 +10783,33 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): values = [value for key, value in row.items()] return self.execute(query, tuple(values)) + def _create_access_file(self): + try: + db_builder = jpype.JClass( + "com.healthmarketscience.jackcess.DatabaseBuilder" + ) + if self.database_file.endswith(".mdb"): + db_file_format = jpype.JClass( + "com.healthmarketscience.jackcess.Database$FileFormat" + ).V2003 + elif self.database_file.endswith(".accdb"): + db_file_format = jpype.JClass( + "com.healthmarketscience.jackcess.Database$FileFormat" + ).V2016 + else: + sg.popup("Access file name must end with .accdb or .mdb") + return False + access_db = ( + db_builder(jpype.JClass("java.io.File")(self.database_file)) + .setFileFormat(db_file_format) + .create() + ) + access_db.close() + except Exception as e: # noqa BLE001 + print("Error creating access file:", e) + return False + return True + # -------------------------- # TYPEDDICTS AND TYPEALIASES From 80463014cdb57381c3fdfe16daefb738b22643ff Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:02:03 -0400 Subject: [PATCH 030/121] Nit: forgot line in sqlite driver --- pysimplesql/pysimplesql.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 76033150..9cf6b734 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9180,7 +9180,6 @@ def column_info(self, table): rows = self.execute(q, silent=True) names = [] col_info = ColumnInfo(self, table) - for _, row in rows.iterrows(): domain, domain_args = self.parse_domain(row["type"]) col_class = self.get_column_class(domain) @@ -9523,7 +9522,6 @@ def connect(self, retries=3): database=self.database, # connect_timeout=3, ) - return con except mysql.connector.Error as e: print(f"Failed to connect to database ({attempt + 1}/{retries})") print(e) From 090754be9a3979ae72790354aa9f5cafd9b9031c Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:02:36 -0400 Subject: [PATCH 031/121] Refactor _looks_like_a_function to use SQL_CONSTANTS and simpler regex --- pysimplesql/pysimplesql.py | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 9cf6b734..08869a32 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -8337,40 +8337,19 @@ def _contains_key_value_pair(self, key, value): # used by __contains__ return any(key in d and d[key] == value for d in self) # TODO: check if something looks like a statement for complex defaults? Regex? - @staticmethod - def _looks_like_function( - s: str, - ): + def _looks_like_function(self, s: str): # check if the string is empty if s in EMPTY: return False - # If the entire string is in all caps, it looks like a function + # If string is in the driver's list of sql_constants # (like in MySQL CURRENT_TIMESTAMP) - if s.isupper(): + if s.upper() in self.driver.SQL_CONSTANTS: return True - # find the index of the first opening parenthesis - open_paren_index = s.find("(") - - # if there is no opening parenthesis, the string is not a function - if open_paren_index == -1: - return False - - # check if there is a name before the opening parenthesis - name = s[:open_paren_index].strip() - if not name.isidentifier(): - return False - - # find the index of the last closing parenthesis - close_paren_index = s.rfind(")") - - # if there is no closing parenthesis, the string is not a function - if close_paren_index == -1 or close_paren_index <= open_paren_index: - return False - - # if all checks pass, the string looks like a function - return True + # Check if the string starts with a valid function name followed by parentheses + pattern = r"^\w+\(.*\)$" + return bool(re.match(pattern, s)) def _get_list(self, key: str) -> List: # returns a list of any key in the underlying Column instances. For example, From 8dbe5f830cd782fc683c9a4ce70b91d3b985b7f6 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:03:10 -0400 Subject: [PATCH 032/121] Feat: _shake_animation --- pysimplesql/pysimplesql.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 08869a32..0a652f9b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5484,6 +5484,20 @@ def __setattr__(self, name, value): super().__setattr__(name, value) +def _shake_animation(widget, dx=5, delay=50, ignore_themepack=False): + if ignore_themepack or themepack.shake_gui_widget_on_invalid_input: + original_options = widget.pack_info() + original_padx = original_options.pop("padx", 0) + + for _ in range(themepack.shake_animation_loops): + widget.pack_configure(padx=original_padx + dx) + widget.update() + widget.after(delay) + widget.pack_configure(padx=original_padx) + widget.update() + widget.after(delay) + + class _PlaceholderText(abc.ABC): """ An abstract class for PySimpleGUI text-entry elements that allows for the display of From a498ee2d37bbd85b2049de163ad4d30269279ac4 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:03:26 -0400 Subject: [PATCH 033/121] Refactor: _PlaceholderText --- pysimplesql/pysimplesql.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 0a652f9b..cfcd0d3e 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5504,21 +5504,20 @@ class _PlaceholderText(abc.ABC): a placeholder text when the input is empty. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.placeholder_feature_enabled = False - self.normal_color = None - self.normal_font = None - self.placeholder_text = "" - self.placeholder_color = None - self.placeholder_font = None - self.active_placeholder = False - # fmt: off - self._non_keys = ["Control_L","Control_R","Alt_L","Alt_R","Shift_L","Shift_R", - "Caps_Lock","Return","Escape","Tab","BackSpace","Up","Down","Left", - "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4","F5", - "F6","F7","F8","F9","F10","F11","F12", "Delete"] - # fmt: on + binds = {} + placeholder_feature_enabled = False + normal_color = None + normal_font = None + placeholder_text = "" + placeholder_color = None + placeholder_font = None + active_placeholder = False + # fmt: off + _non_keys = ["Control_L","Control_R","Alt_L","Alt_R","Shift_L","Shift_R", + "Caps_Lock","Return","Escape","Tab","BackSpace","Up","Down","Left", + "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4","F5", + "F6","F7","F8","F9","F10","F11","F12", "Delete"] + # fmt: on def add_placeholder(self, placeholder: str, color: str = None, font: str = None): """ From f74f37d918492f38c1a09b261f4f9d8e9e52cd28 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:04:02 -0400 Subject: [PATCH 034/121] Refactor: Reuse code in Combo classes --- pysimplesql/pysimplesql.py | 154 +++++++++++++++---------------------- 1 file changed, 61 insertions(+), 93 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index cfcd0d3e..3ff2e521 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5765,76 +5765,55 @@ def _on_search_string_change(self, *args): self.insert_placeholder() -def _autocomplete_combo(widget, completion_list, delta=0): - """Perform autocompletion on a Combobox widget based on the current input.""" - if delta: - # Delete text from current position to end - widget.delete(widget.position, tk.END) - else: - # Set the position to the length of the current input text - widget.position = len(widget.get()) - - prefix = widget.get().lower() - hits = [ - element for element in completion_list if element.lower().startswith(prefix) - ] - # Create a list of elements that start with the lowercase prefix - - if hits: - closest_match = min(hits, key=len) - if prefix != closest_match.lower(): - # Insert the closest match at the beginning, move the cursor to the end - widget.delete(0, tk.END) - widget.insert(0, closest_match) - widget.icursor(len(closest_match)) - - # Highlight the remaining text after the closest match - widget.select_range(widget.position, tk.END) - - if len(hits) == 1 and closest_match.lower() != prefix: - # If there is only one hit and it's not equal to the lowercase prefix, - # open dropdown - widget.event_generate("") - widget.event_generate("<>") - - else: - # If there are no hits, move the cursor to the current position - widget.icursor(widget.position) - - return hits +class _AutoCompleteLogic: + _completion_list = [] + _hits = [] + _hit_index = 0 + position = 0 + finalized = False + + def _autocomplete_combo(self, completion_list, delta=0): + widget = self.Widget + """Perform autocompletion on a Combobox widget based on the current input.""" + if delta: + # Delete text from current position to end + widget.delete(widget.position, tk.END) + else: + # Set the position to the length of the current input text + widget.position = len(widget.get()) + prefix = widget.get().lower() + hits = [ + element for element in completion_list if element.lower().startswith(prefix) + ] + # Create a list of elements that start with the lowercase prefix -class _AutocompleteCombo(sg.Combo): - """Customized Combo widget with autocompletion feature. + if hits: + closest_match = min(hits, key=len) + if prefix != closest_match.lower(): + # Insert the closest match at the beginning, move the cursor to the end + widget.delete(0, tk.END) + widget.insert(0, closest_match) + widget.icursor(len(closest_match)) - Please note that due to how PySimpleSql initilizes widgets, you must call update() - once to activate autocompletion, eg `window['combo_key'].update(values=values)` - """ + # Highlight the remaining text after the closest match + widget.select_range(widget.position, tk.END) - def __init__(self, *args, **kwargs): - """Initialize the Combo widget.""" - self._completion_list = [] - self._hits = [] - self._hit_index = 0 - self.position = 0 - self.finalized = False + if len(hits) == 1 and closest_match.lower() != prefix: + # If there is only one hit and it's not equal to the lowercase prefix, + # open dropdown + widget.event_generate("") + widget.event_generate("<>") - super().__init__(*args, **kwargs) + else: + # If there are no hits, move the cursor to the current position + widget.icursor(widget.position) - def update(self, *args, **kwargs): - """Update the Combo widget with new values.""" - if "values" in kwargs and kwargs["values"] is not None: - self._completion_list = [str(row) for row in kwargs["values"]] - if not self.finalized: - self.Widget.bind("", self.handle_keyrelease, "+") - self._hits = [] - self._hit_index = 0 - self.position = 0 - super().update(*args, **kwargs) + return hits def autocomplete(self, delta=0): """Perform autocompletion based on the current input.""" - self._hits = _autocomplete_combo(self.Widget, self._completion_list, delta) + self._hits = self._autocomplete_combo(self._completion_list, delta) self._hit_index = 0 def handle_keyrelease(self, event): @@ -5859,45 +5838,34 @@ def handle_keyrelease(self, event): self.autocomplete() -class _TtkCombo(ttk.Combobox): +class _AutocompleteCombo(_AutoCompleteLogic, sg.Combo): + """Customized Combo widget with autocompletion feature. + + Please note that due to how PySimpleSql initilizes widgets, you must call update() + once to activate autocompletion, eg `window['combo_key'].update(values=values)` + """ + + def update(self, *args, **kwargs): + """Update the Combo widget with new values.""" + if "values" in kwargs and kwargs["values"] is not None: + self._completion_list = [str(row) for row in kwargs["values"]] + if not self.finalized: + self.Widget.bind("", self.handle_keyrelease, "+") + self._hits = [] + self._hit_index = 0 + self.position = 0 + super().update(*args, **kwargs) + + +class _TtkCombo(_AutoCompleteLogic, ttk.Combobox): """Customized Combo widget with autocompletion feature.""" def __init__(self, *args, **kwargs): """Initialize the Combo widget.""" self._completion_list = [str(row) for row in kwargs["values"]] - self._hits = [] - self._hit_index = 0 - self.position = 0 - self.finalized = False - + self.Widget = self super().__init__(*args, **kwargs) - def autocomplete(self, delta=0): - """Perform autocompletion based on the current input.""" - self._hits = _autocomplete_combo(self, self._completion_list, delta) - self._hit_index = 0 - - def handle_keyrelease(self, event): - """Handle key release event for autocompletion and navigation.""" - if event.keysym == "BackSpace": - self.delete(self.position, tk.END) - self.position = self.position - if event.keysym == "Left": - if self.position < self.index(tk.END): - self.delete(self.position, tk.END) - else: - self.position -= 1 - self.delete(self.position, tk.END) - if event.keysym == "Right": - self.position = self.index(tk.END) - if event.keysym == "Return": - self.icursor(tk.END) - self.selection_clear() - return - - if len(event.keysym) == 1: - self.autocomplete() - class _TtkCalendar(ttk.Frame): """Internal Class.""" From 5db5ddc0a3816f719b355c63e54b6c20a072b6cf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:04:38 -0400 Subject: [PATCH 035/121] Fix: broken datepicker entry --- pysimplesql/pysimplesql.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 3ff2e521..8513f667 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5878,7 +5878,7 @@ def __init__(self, master, init_date, textvariable, **kwargs): sel_bg = kwargs.pop("selectbackground", "#ecffc4") sel_fg = kwargs.pop("selectforeground", "#05640e") - super().__init__(master, class_="ttkcalendar", **kwargs) + super().__init__(master, **kwargs) self.master = master self.cal_date = init_date @@ -6010,13 +6010,16 @@ def minsize(self, e): class _DatePicker(ttk.Entry): - def __init__(self, master, frm_reference, init_date, **kwargs): - self.frm = frm_reference + def __init__(self, master, dataset, column_name, init_date, **kwargs): + self.dataset = dataset + self.column_name = column_name textvariable = kwargs["textvariable"] - self.calendar = _TtkCalendar(self.frm.window.TKroot, init_date, textvariable) + self.calendar = _TtkCalendar( + self.dataset.frm.window.TKroot, init_date, textvariable + ) self.calendar.place_forget() self.button = ttk.Button(master, text="▼", width=2, command=self.show_calendar) - super().__init__(master, class_="Datepicker", **kwargs) + super().__init__(master, **kwargs) self.bind("", self.on_entry_key_release, "+") self.calendar.bind("", self.hide_calendar, "+") From 2acff5992f8d23c46392fdb9c6d8e5058e810b27 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:05:14 -0400 Subject: [PATCH 036/121] Nit: use tk contants in DatePicker --- pysimplesql/pysimplesql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8513f667..f24c7bf0 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -6025,13 +6025,13 @@ def __init__(self, master, dataset, column_name, init_date, **kwargs): self.calendar.bind("", self.hide_calendar, "+") def show_calendar(self, event=None): - self.configure(state="disabled") + self.configure(state=tk.DISABLED) self.calendar.place(in_=self, relx=0, rely=1) self.calendar.focus_force() self.calendar.select_date() def hide_calendar(self, event=None): - self.configure(state="!disabled") + self.configure(state=tk.NORMAL) self.calendar.place_forget() self.focus_force() From eb1e6983ba0e13413e19169144bfc8dd7338f19a Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:05:30 -0400 Subject: [PATCH 037/121] Feat: use cast in DatePicker --- pysimplesql/pysimplesql.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index f24c7bf0..0b84fcdf 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -6036,11 +6036,10 @@ def hide_calendar(self, event=None): self.focus_force() def on_entry_key_release(self, event=None): + date = self.get() + date = self.dataset.column_info[self.column_name].cast(date) # Check if the user has typed a valid date - try: - date_str = self.get() - date = dt.datetime.strptime(date_str, "%Y-%m-%d") - except ValueError: + if not isinstance(date, dt.date): return # Update the calendar to show the new date From 52410deb81c73cb7e593bcf64556a012d0c6715c Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:06:30 -0400 Subject: [PATCH 038/121] Rename TableHeading args --- pysimplesql/pysimplesql.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 0b84fcdf..18a3fc34 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2703,7 +2703,9 @@ def quick_editor( keygen.reset() data_key = self.key layout = [] - headings = TableHeadings(sort_enable=True, edit_enable=True, save_enable=True) + headings = TableHeadings( + sort_enable=True, allow_cell_edits=True, add_save_heading_button=True + ) for col in self.column_info.names(): # set widths @@ -3112,9 +3114,8 @@ def __init__( given priority. If no match is found, the second column is used. Default list: ['description', 'name', 'title']. :param live_update: (optional) Default value is False. If True, changes made in - a field will be immediately pushed to associated selectors. In addition, - editing the description column will trigger the update of comboboxes. - If False, changes will be pushed only after a save action. + a field will be immediately pushed to associated selectors. If False, + changes will be pushed only after a save action. :param auto_add_relationships: (optional) Controls the invocation of auto_add_relationships. Default is True. Set it to False when creating a new `Form` with pre-existing `Relationship` instances. @@ -6766,7 +6767,7 @@ def selector( # the heading doesn't display dicts when it first loads # The TableHeadings instance is already stored in metadata if isinstance(kwargs["headings"], TableHeadings): - if kwargs["headings"].save_enable: + if kwargs["headings"].add_save_heading_button: kwargs["headings"].insert(0, themepack.unsaved_column_header) else: kwargs["headings"].insert(0, "") @@ -6797,24 +6798,26 @@ class TableHeadings(list): def __init__( self, sort_enable: bool = True, - edit_enable: bool = False, - save_enable: bool = False, + allow_cell_edits: bool = False, + add_save_heading_button: bool = False, apply_search_filter: bool = False, ) -> None: """ Create a new TableHeadings object. - :param sort_enable: True to enable sorting by heading column - :param edit_enable: Enables cell editing if True. Accepted edits update both - `sg.Table` and associated `field` element. - :param save_enable: Enables saving record by double-clicking unsaved marker col. + :param sort_enable: True to enable sorting by heading column. + :param allow_cell_edits: Double-click to edit a cell value if True. Accepted + edits update both `sg.Table` and associated `field` element. Note: primary + key, generated, or `readonly` columns don't allow cell edits. + :param add_save_heading_button: Adds a save button to the left-most heading + column if True. :param apply_search_filter: Filter rows to only those columns in `DataSet.search_order` that contain `Dataself.search_string`. :returns: None """ self.sort_enable = sort_enable - self.edit_enable = edit_enable - self.save_enable = save_enable + self.allow_cell_edits = allow_cell_edits + self.add_save_heading_button = add_save_heading_button self.apply_search_filter = apply_search_filter self._width_map = [] self._visible_map = [] @@ -6843,7 +6846,7 @@ def add_column( column would be the primary key column if any. This is also useful if the `DataSet.rows` DataFrame has information that you don't want to display. :param readonly: Indicates if the column is read-only when - `TableHeading.edit_enable` is True. + `TableHeading.allow_cell_edits` is True. :returns: None """ self.append({"heading": heading_column, "column": column}) @@ -6940,7 +6943,7 @@ def enable_heading_function(self, element: sg.Table, fn: callable) -> None: i, command=functools.partial(fn, self[i]["column"], False) ) self.update_headings(element) - if self.save_enable: + if self.add_save_heading_button: element.widget.heading(0, command=functools.partial(fn, None, save=True)) def insert(self, idx, heading_column: str, column: str = None, *args, **kwargs): @@ -7043,7 +7046,7 @@ def edit(self, event): logger.debug(f"{column} is a generated column") return - if not table_heading.edit_enable: + if not table_heading.allow_cell_edits: logger.debug("This Table element does not allow editing") return From 797e28a27f6c33086be7735948c8fbd18876be7e Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:08:02 -0400 Subject: [PATCH 039/121] Feat: New Validate classes enums: ValidateRule dataclass: ValidateRepsonse --- pysimplesql/pysimplesql.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 18a3fc34..a399a3dc 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -251,6 +251,24 @@ class Boolean(enum.Flag): FALSE = False +class ValidateRule(str, enum.Enum): + REQUIRED = "required" + PYTHON_TYPE = "python_type" + PRECISION = "precision" + MIN_VALUE = "min_value" + MAX_VALUE = "max_value" + MIN_LENGTH = "min_length" + MAX_LENGTH = "max_length" + CUSTOM = "custom" + + +@dataclass +class ValidateResponse: + exception: Union[ValidateRule, None] = None + value: str = None + rule: str = None + + # ------- # CLASSES # ------- @@ -7683,6 +7701,17 @@ class LanguagePack: # ------------------------------------------------------------------------------ "import_module_failed_title": "Problem importing module", "import_module_failed": "Unable to import module neccessary for {name}\nException: {exception}\n\nTry `pip install {requires}`", # fmt: skip # noqa: E501 + # ------------------------------------------------------------------------------ + # Invalid Input msgs + # ------------------------------------------------------------------------------ + ValidateRule.REQUIRED: "Field is required", + ValidateRule.PYTHON_TYPE: "{value} could not be cast to correct type, {rule}", + ValidateRule.PRECISION: "{value} exceeds max precision length, {rule}", + ValidateRule.MIN_VALUE: "{value} less than minimum value, {rule}", + ValidateRule.MAX_VALUE: "{value} more than max value, {rule}", + ValidateRule.MIN_LENGTH: "{value} less than minimum length, {rule}", + ValidateRule.MAX_LENGTH: "{value} more than max length, {rule}", + ValidateRule.CUSTOM: "{value}{rule}", } """ Default LanguagePack. @@ -7702,6 +7731,15 @@ def __getattr__(self, key): except KeyError: raise AttributeError(f"LanguagePack object has no attribute '{key}'") + def __getitem__(self, key): + try: + return self.lp_dict[key] + except KeyError: + try: + return type(self).default[key] + except KeyError: + raise AttributeError(f"LanguagePack object has no attribute '{key}'") + def __call__(self, lp_dict={}): """Update the LanguagePack instance.""" # For default use cases, load the default directly to avoid the overhead From 9a56a3539fe5544cde22d876bf06b6b515f9f620 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:08:37 -0400 Subject: [PATCH 040/121] Feat: Use Validate and _shake_animation --- pysimplesql/pysimplesql.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index a399a3dc..fa7f3a48 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7137,7 +7137,11 @@ def edit(self, event): if widget_type == TK_DATEPICKER: text = dt.date.today() if type(text) is str else text self.field = _DatePicker( - frame, self.frm, init_date=text, textvariable=field_var + frame, + self.frm[data_key], + column_name=column, + init_date=text, + textvariable=field_var, ) expand = True @@ -7231,6 +7235,20 @@ def accept( dataset = self.frm[data_key] + for col in dataset.column_info: + if col.name == column: + response = col.validate(new_value) + if response.exception: + self.frm.popup.info( + lang[response.exception].format_map( + LangFormat(value=response.value, rule=response.rule) + ), + display_message=False, + ) + _shake_animation(self.field) + return + self.frm.popup.update_info_element(erase=True) + # see if there was a change old_value = dataset.get_current_row().copy()[column] cast_new_value = dataset.value_changed( @@ -7394,6 +7412,17 @@ def sync(self, widget, widget_type): # get cast new value to correct type for col in dataset.column_info: if col.name == column: + response = col.validate(new_value) + if response.exception: + self.frm.popup.info( + lang[response.exception].format_map( + LangFormat(value=response.value, rule=response.rule) + ), + display_message=False, + ) + _shake_animation(e["element"].widget) + return + self.frm.popup.update_info_element(erase=True) new_value = col.cast(new_value) break From 68f24a3bd4218e82621a1abe706ee519aa868d21 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:08:48 -0400 Subject: [PATCH 041/121] Nit: ruff fix --- pysimplesql/pysimplesql.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index fa7f3a48..8f868822 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5099,10 +5099,9 @@ async def _gui(self): async def run_process(self, fn: callable, *args, **kwargs): loop = asyncio.get_running_loop() try: - result = await loop.run_in_executor( + return await loop.run_in_executor( None, functools.partial(fn, *args, **kwargs) ) - return result except Exception as e: # noqa: BLE001 print(f"\nAn error occurred in the process: {e}") raise e # Pass the exception along to the caller From 0673ace318620595e428aab460ea669982b30989 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:09:10 -0400 Subject: [PATCH 042/121] Fix: use get_pk_ignore_placeholder in LiveUpdate --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8f868822..2caf3550 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7402,7 +7402,7 @@ def sync(self, widget, widget_type): column = e["column"] element = e["element"] if widget_type == TK_COMBOBOX and isinstance(element.get(), ElementRow): - new_value = element.get().get_pk() + new_value = element.get().get_pk_ignore_placeholder() else: new_value = element.get() From 6c4dbc9d2599eb4f0cc189d54eb837c6e79c3c66 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:09:37 -0400 Subject: [PATCH 043/121] Update Themepack to match new features --- pysimplesql/pysimplesql.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 2caf3550..58c80b96 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7371,7 +7371,7 @@ def __init__(self, frm_reference: Form): self.frm = frm_reference self.last_event_widget = None self.last_event_time = None - self.delay_seconds = 0.25 + self.delay_seconds = themepack.live_update_typing_delay_seconds def __call__(self, event): # keep track of time on same widget @@ -7509,10 +7509,12 @@ class ThemePack: # ---------------------------------------- "marker_sort_asc": "▼", "marker_sort_desc": "▲", - # Info Popup defaults + # GUI settings # ---------------------------------------- - "popup_info_auto_close_seconds": 1, + "popup_info_auto_close_seconds": 1.5, "popup_info_alpha_channel": 0.85, + "info_element_auto_erase_seconds": 5, + "live_update_typing_delay_seconds": 0.75, # Default sizes for elements # --------------------------- # Label Size @@ -7534,10 +7536,13 @@ class ThemePack: "combobox_min_width": 80, "checkbox_min_width": 75, "datepicker_min_width": 80, - # Display boolean columns as checkboxes in sg.Tables - "display_boolean_as_checkbox": True, + # Display python_type `bool` columns as checkboxes in sg.Tables + "display_bool_as_checkbox": True, "checkbox_true": "☑", "checkbox_false": "☐", + # Shake the gui widget on an invalid input + "shake_gui_widget_on_invalid_input": False, + "shake_animation_loops": 3, } """ Default Themepack. From 0335ed3c9b2f4b00fcfb2fbff60283142f23596f Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:09:48 -0400 Subject: [PATCH 044/121] Update LanguagePack to use new features --- pysimplesql/pysimplesql.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 58c80b96..ecddecb5 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7633,6 +7633,9 @@ class LanguagePack: "button_yes": " Yes ", "button_no": " No ", # ------------------------------------------------------------------------------ + # General + # ------------------------------------------------------------------------------ + # ------------------------------------------------------------------------------ # Prepopulate record values/prepends # ------------------------------------------------------------------------------ # Text, Varchar, Char, Null Default, used exclusively for description_column @@ -7726,6 +7729,10 @@ class LanguagePack: "duplicate_failed_title": "Problem Duplicating", "duplicate_failed": "Query failed: {exception}.", # ------------------------------------------------------------------------------ + # General OK poups + # ------------------------------------------------------------------------------ + "error_title": "Error", + # ------------------------------------------------------------------------------ # Quick Editor # ------------------------------------------------------------------------------ "quick_edit_title": "Quick Edit - {data_key}", @@ -7735,6 +7742,12 @@ class LanguagePack: "import_module_failed_title": "Problem importing module", "import_module_failed": "Unable to import module neccessary for {name}\nException: {exception}\n\nTry `pip install {requires}`", # fmt: skip # noqa: E501 # ------------------------------------------------------------------------------ + # Overwrite file prompt + # ------------------------------------------------------------------------------ + "overwrite_title": "Overwrite file?", + "overwrite": "File exists, type YES to overwrite", + "overwrite_prompt": "YES", + # ------------------------------------------------------------------------------ # Invalid Input msgs # ------------------------------------------------------------------------------ ValidateRule.REQUIRED: "Field is required", From ddd835d03376a935ac315f771ac20edd7c2b991f Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:35:31 -0400 Subject: [PATCH 045/121] Nit: remove order=True Not needed for comparison of individual values --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index ecddecb5..4ff82e63 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7843,7 +7843,7 @@ class Abstractions: # The column abstraction hides the complexity of dealing with SQL columns, getting their # names, default values, data types, primary key status and notnull status # -------------------------------------------------------------------------------------- -@dataclass(order=True) +@dataclass class Column: """ From 41806dae0cd920ad367ed0a1c1068080e8be5c1f Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 29 Jun 2023 16:56:16 -0400 Subject: [PATCH 046/121] Feat: Add validate checking before save to DataSet save_records --- pysimplesql/pysimplesql.py | 68 ++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 4ff82e63..164da496 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -591,6 +591,7 @@ def __init__( prompt_save: int = None, save_quiet: bool = None, duplicate_children: bool = None, + validate_fields: bool = None, ) -> None: """ Initialize a new `DataSet` instance. @@ -619,6 +620,7 @@ def __init__( save. Error popups will still be shown. :param duplicate_children: (optional) Default: Set in `Form`. If record has children, prompt user to choose to duplicate current record, or both. + :param validate_fields: Validate fields before saving to database. :returns: None """ DataSet.instances.append(self) @@ -650,18 +652,22 @@ def __init__( self.callbacks: CallbacksDict = {} self.transform: Optional[Callable[[pd.DataFrame, int], None]] = None self.filtered: bool = filtered - if prompt_save is None: - self._prompt_save = self.frm._prompt_save - else: - self._prompt_save: int = prompt_save - if save_quiet is None: - self.save_quiet = self.frm.save_quiet - else: - self.save_quiet: bool = save_quiet - if duplicate_children is None: - self.duplicate_children = self.frm.duplicate_children - else: - self.duplicate_children: bool = duplicate_children + self._prompt_save = ( + self.frm._prompt_save if prompt_save is None else int(prompt_save) + ) + self.save_quiet = ( + self.frm.save_quiet if save_quiet is None else bool(save_quiet) + ) + self.duplicate_children = ( + self.frm.duplicate_children + if duplicate_children is None + else bool(duplicate_children) + ) + self.validate_fields = ( + self.frm.validate_fields + if validate_fields is None + else bool(validate_fields) + ) self._simple_transform: SimpleTransformsDict = {} # Override the [] operator to retrieve current columns by key @@ -1852,7 +1858,10 @@ def insert_record( self.requery_dependents() def save_record( - self, display_message: bool = None, update_elements: bool = True + self, + display_message: bool = None, + update_elements: bool = True, + validate_fields: bool = None, ) -> int: """ Save the currently selected record. @@ -1864,12 +1873,16 @@ def save_record( :param display_message: Displays a message "Updates saved successfully", otherwise is silent on success. :param update_elements: Update the GUI elements after saving + :param validate_fields: Validate fields before saving to database. :returns: SAVE_NONE, SAVE_FAIL or SAVE_SUCCESS masked with SHOW_MESSAGE """ logger.debug(f"Saving records for table {self.table}...") if display_message is None: display_message = not self.save_quiet + if validate_fields is None: + validate_fields = self.validate_fields + # Ensure that there is actually something to save if not self.row_count: self.frm.popup.info( @@ -1992,6 +2005,26 @@ def save_record( self.frm.update_fields(self.key) return SAVE_NONE + SHOW_MESSAGE + # check to make sure we have valid inputs + if validate_fields: + invalid_response = {} + for col, value in changed_row_dict.items(): + response = self.column_info[col].validate(value) + if response.exception: + invalid_response[col] = response + if invalid_response: + msg = f"{lang.dataset_save_validate_error_header}" + for col, response in invalid_response.items(): + field = lang.dataset_save_validate_error_field.format_map( + LangFormat(field=col) + ) + exception = lang[response.exception].format_map( + LangFormat(value=response.value, rule=response.rule) + ) + msg += f"{field}{exception}\n" + self.frm.popup.ok(lang.dataset_save_validate_error_title, msg) + return SAVE_FAIL + # check to see if cascading-fk has changed before we update database cascade_fk_changed = False cascade_fk_column = Relationship.get_update_cascade_fk_column(self.table) @@ -3099,6 +3132,7 @@ def __init__( description_column_names: List[str] = None, live_update: bool = False, auto_add_relationships: bool = True, + validate_fields: bool = True, ) -> None: """ Initialize a new `Form` instance. @@ -3137,6 +3171,8 @@ def __init__( :param auto_add_relationships: (optional) Controls the invocation of auto_add_relationships. Default is True. Set it to False when creating a new `Form` with pre-existing `Relationship` instances. + :param validate_fields: Passed to `DataSet` init to validate fields before + saving to database. :returns: None """ win_pb = ProgressBar(lang.startup_form) @@ -3171,6 +3207,7 @@ def __init__( else: self.description_column_names = description_column_names self.live_update: bool = live_update + self.validate_fields: bool = validate_fields # empty variables, just in-case bind() never called self.popup = None @@ -7748,8 +7785,11 @@ class LanguagePack: "overwrite": "File exists, type YES to overwrite", "overwrite_prompt": "YES", # ------------------------------------------------------------------------------ - # Invalid Input msgs + # Validate Msgs # ------------------------------------------------------------------------------ + "dataset_save_validate_error_title": "Error: Invalid Input(s)", + "dataset_save_validate_error_header": "The following fields(s) have issues:\n", + "dataset_save_validate_error_field": "{field}: ", ValidateRule.REQUIRED: "Field is required", ValidateRule.PYTHON_TYPE: "{value} could not be cast to correct type, {rule}", ValidateRule.PRECISION: "{value} exceeds max precision length, {rule}", From abd9fbfbbb4eac09c105fee7a7beeaff429444a7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 30 Jun 2023 10:09:05 -0400 Subject: [PATCH 047/121] Refactor: new Dataset function validate_field --- pysimplesql/pysimplesql.py | 78 ++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 164da496..dc6ef637 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3103,6 +3103,43 @@ def insert_row(self, row: dict, idx: int = None) -> None: self.rows.attrs["virtual"].append(row[self.pk_column]) + def validate_field( + self, + column_name: str, + new_value: Any, + tk_widget=None, + display_message: bool = False, + ) -> bool: + """ + Validate the given field value for the specified column. + + :param column_name: The name of the column to validate the field against. + :param new_value: The new value to validate. + :param tk_widget: The Tkinter widget associated with the field. (Optional) + :param display_message: Flag to display validation messages. (Default: False) + :return: True if the field value is valid, False otherwise. + """ + + if column_name in self.column_info: + # Validate the new value against the column's validation rules + response = self.column_info[column_name].validate(new_value) + # If validation fails, display an error message and return False + if response.exception: + self.frm.popup.info( + lang[response.exception].format_map( + LangFormat(value=response.value, rule=response.rule) + ), + display_message=display_message, + ) + if tk_widget: + _shake_animation(tk_widget) + return False + # If validation passes, update the info element and return True + self.frm.popup.update_info_element(erase=True) + return True + logger.debug(f"{column_name} not in dataset.column_info!") + return None + class Form: @@ -7271,19 +7308,11 @@ def accept( dataset = self.frm[data_key] - for col in dataset.column_info: - if col.name == column: - response = col.validate(new_value) - if response.exception: - self.frm.popup.info( - lang[response.exception].format_map( - LangFormat(value=response.value, rule=response.rule) - ), - display_message=False, - ) - _shake_animation(self.field) - return - self.frm.popup.update_info_element(erase=True) + # validate the field + if dataset.validate_fields: + valid = dataset.validate_field(column, new_value, self.field) + if not valid: + return # see if there was a change old_value = dataset.get_current_row().copy()[column] @@ -7445,22 +7474,13 @@ def sync(self, widget, widget_type): dataset = self.frm[data_key] - # get cast new value to correct type - for col in dataset.column_info: - if col.name == column: - response = col.validate(new_value) - if response.exception: - self.frm.popup.info( - lang[response.exception].format_map( - LangFormat(value=response.value, rule=response.rule) - ), - display_message=False, - ) - _shake_animation(e["element"].widget) - return - self.frm.popup.update_info_element(erase=True) - new_value = col.cast(new_value) - break + # validate the field + if dataset.validate_fields: + valid = dataset.validate_field( + column, new_value, e["element"].widget + ) + if not valid: + return # see if there was a change old_value = dataset.get_current_row()[column] From 0a8bc7033c2a538855a3001deabcde95d4e6f465 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 1 Jul 2023 14:33:23 -0400 Subject: [PATCH 048/121] Fix: _shake_animation, don't move other elements --- pysimplesql/pysimplesql.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index dc6ef637..2c869b4c 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5576,13 +5576,22 @@ def __setattr__(self, name, value): super().__setattr__(name, value) -def _shake_animation(widget, dx=5, delay=50, ignore_themepack=False): +def _shake_animation(widget, pixels=4, delay=50, ignore_themepack=False): if ignore_themepack or themepack.shake_gui_widget_on_invalid_input: original_options = widget.pack_info() - original_padx = original_options.pop("padx", 0) + original_padx = original_options.get("padx", 0) + + if isinstance(original_padx, tuple): + padx_plus = original_padx[0] + pixels + padx_minus = original_padx[1] - pixels + new_padx = (padx_plus, padx_minus) + else: + padx_plus = original_padx + pixels + padx_minus = max(original_padx - pixels, 0) + new_padx = (padx_plus, padx_minus) for _ in range(themepack.shake_animation_loops): - widget.pack_configure(padx=original_padx + dx) + widget.pack_configure(padx=new_padx) widget.update() widget.after(delay) widget.pack_configure(padx=original_padx) @@ -7571,7 +7580,7 @@ class ThemePack: "popup_info_auto_close_seconds": 1.5, "popup_info_alpha_channel": 0.85, "info_element_auto_erase_seconds": 5, - "live_update_typing_delay_seconds": 0.75, + "live_update_typing_delay_seconds": 0.3, # Default sizes for elements # --------------------------- # Label Size From 63a20db303fabf2f60a4a751a7d12d959b206202 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 2 Jul 2023 00:19:22 -0400 Subject: [PATCH 049/121] Refactor/ allow validate exception animation to be more easily changed --- examples/orders_multiple_databases.py | 3 - pysimplesql/pysimplesql.py | 89 +++++++++++++++++---------- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 44997091..6e203378 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -36,9 +36,6 @@ "ttk_theme": os_ttktheme, "marker_sort_asc": " ⬇", "marker_sort_desc": " ⬆", - "shake_gui_widget_on_invalid_input": True, - # Recommended to set a lower delay if 'shake' is disabled - # "live_update_typing_delay_seconds": 0.25, } custom = custom | os_tp ss.themepack(custom) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 2c869b4c..74496942 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3107,7 +3107,7 @@ def validate_field( self, column_name: str, new_value: Any, - tk_widget=None, + widget=None, display_message: bool = False, ) -> bool: """ @@ -3115,7 +3115,7 @@ def validate_field( :param column_name: The name of the column to validate the field against. :param new_value: The new value to validate. - :param tk_widget: The Tkinter widget associated with the field. (Optional) + :param widget: The widget associated with the field. (Optional) :param display_message: Flag to display validation messages. (Default: False) :return: True if the field value is valid, False otherwise. """ @@ -3131,8 +3131,8 @@ def validate_field( ), display_message=display_message, ) - if tk_widget: - _shake_animation(tk_widget) + if widget and themepack.validate_exception_animation is not None: + themepack.validate_exception_animation(widget) return False # If validation passes, update the info element and return True self.frm.popup.update_info_element(erase=True) @@ -4777,6 +4777,45 @@ def checkbox_to_bool(value): ] +def shake_widget(widget: Union[sg.Element, tk.Widget], pixels=4, delay_ms=50, repeat=2): + """ + Shakes the given widget by modifying its padx attribute. + + :param widget: The widget to shake. Must be an instance of sg.Element or tk.Widget. + :param pixels: The number of pixels by which to shake the widget horizontally. + :param delay_ms: The delay in milliseconds between each shake movement. + :param repeat: The number of times to repeat the shaking movement. + + """ + if isinstance(widget, sg.Element): + widget = widget.Widget + elif not isinstance(widget, tk.Widget): + logger.debug(f"{widget} not a valid sg.Element or tk.Widget") + return + padx = widget.pack_info().get("padx", 0) + + # Adjust padx based on its current value + if isinstance(padx, tuple): + padx_left = padx[0] + pixels + padx_right = padx[1] - pixels + new_padx = (padx_left, padx_right) + else: + padx_left = padx + pixels + padx_right = max(padx - pixels, 0) + new_padx = (padx_left, padx_right) + + widget.update() + + # Perform the shaking movement + for _ in range(repeat): + widget.pack_configure(padx=new_padx) + widget.update() + widget.after(delay_ms) + widget.pack_configure(padx=padx) + widget.update() + widget.after(delay_ms) + + class Popup: """ @@ -5576,29 +5615,6 @@ def __setattr__(self, name, value): super().__setattr__(name, value) -def _shake_animation(widget, pixels=4, delay=50, ignore_themepack=False): - if ignore_themepack or themepack.shake_gui_widget_on_invalid_input: - original_options = widget.pack_info() - original_padx = original_options.get("padx", 0) - - if isinstance(original_padx, tuple): - padx_plus = original_padx[0] + pixels - padx_minus = original_padx[1] - pixels - new_padx = (padx_plus, padx_minus) - else: - padx_plus = original_padx + pixels - padx_minus = max(original_padx - pixels, 0) - new_padx = (padx_plus, padx_minus) - - for _ in range(themepack.shake_animation_loops): - widget.pack_configure(padx=new_padx) - widget.update() - widget.after(delay) - widget.pack_configure(padx=original_padx) - widget.update() - widget.after(delay) - - class _PlaceholderText(abc.ABC): """ An abstract class for PySimpleGUI text-entry elements that allows for the display of @@ -7319,7 +7335,12 @@ def accept( # validate the field if dataset.validate_fields: - valid = dataset.validate_field(column, new_value, self.field) + widget = ( + self.field + if themepack.validate_exception_animation is not None + else None + ) + valid = dataset.validate_field(column, new_value, widget) if not valid: return @@ -7485,9 +7506,12 @@ def sync(self, widget, widget_type): # validate the field if dataset.validate_fields: - valid = dataset.validate_field( - column, new_value, e["element"].widget + widget = ( + e["element"].Widget + if themepack.validate_exception_animation is not None + else None ) + valid = dataset.validate_field(column, new_value, widget) if not valid: return @@ -7606,9 +7630,8 @@ class ThemePack: "display_bool_as_checkbox": True, "checkbox_true": "☑", "checkbox_false": "☐", - # Shake the gui widget on an invalid input - "shake_gui_widget_on_invalid_input": False, - "shake_animation_loops": 3, + # invalid input animation + "validate_exception_animation": lambda widget: shake_widget(widget), } """ Default Themepack. From 0b936f104c22a770cef89559b0c0c8bb7c7b0445 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 2 Jul 2023 00:32:28 -0400 Subject: [PATCH 050/121] Nit: change indent --- pysimplesql/pysimplesql.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 74496942..dabc28cf 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3867,11 +3867,13 @@ def auto_map_events(self, win: sg.Window) -> None: search_box = f"{search_element}:search_input" if data_key: funct = functools.partial(self[data_key].search, search_box) - self.window[search_box].add_placeholder( - placeholder=lang.search_placeholder, - color=themepack.placeholder_color, - ) - self.window[search_box].bind_dataset(self[data_key]) + # add placeholder + self.window[search_box].add_placeholder( + placeholder=lang.search_placeholder, + color=themepack.placeholder_color, + ) + # bind dataset + self.window[search_box].bind_dataset(self[data_key]) # elif event_type==EVENT_SEARCH_DB: elif event_type == EVENT_QUICK_EDIT: referring_table = table From 98410a8418fbaa13ad60a32cef9281439002d54e Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 2 Jul 2023 00:57:59 -0400 Subject: [PATCH 051/121] Feat: Allow passing column attrs to quick_editor in fields --- examples/orders_multiple_databases.py | 47 ++++++++++++++++----------- pysimplesql/pysimplesql.py | 24 ++++++++++---- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 6e203378..9184c7bf 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -101,6 +101,25 @@ def render(self, context): return self.template_string.format_map(lang_format) +# create your own validator to be passed to a +# frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn +# used below in the quick_editor arguments +def is_valid_email(email): + valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None + if not valid_email: + return ss.ValidateResponse( + ss.ValidateRule.CUSTOM, email, " is not a valid email" + ) + return ss.ValidateResponse() + + +quick_editor_kwargs = { + "column_attributes": { + "email": {"custom_validate_fn": lambda value: is_valid_email(value)} + } +} + + # SQL Statement # ====================================================================================== sql = """ @@ -413,7 +432,14 @@ def render(self, context): orderdetails_layout = [ [sg.Sizer(h_pixels=0, v_pixels=10)], - [ss.field("orders.customer_id", sg.Combo, label="Customer")], + [ + ss.field( + "orders.customer_id", + sg.Combo, + label="Customer", + quick_editor_kwargs=quick_editor_kwargs, + ) + ], [ ss.field("orders.date", label="Date"), ], @@ -544,19 +570,6 @@ def update_orders(frm_reference, window, data_key): frm["order_details"].set_callback("after_save", update_orders) frm["order_details"].set_callback("after_delete", update_orders) - -# create your own validator to be passed to a -# frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn -# used below in the quick_editor arguments -def is_valid_email(email): - valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None - if not valid_email: - return ss.ValidateResponse( - ss.ValidateRule.CUSTOM, email, " is not a valid email" - ) - return ss.ValidateResponse() - - # --------- # MAIN LOOP # --------- @@ -600,11 +613,7 @@ def is_valid_email(email): elif "Edit Products" in event: frm["products"].quick_editor() elif "Edit Customers" in event: - frm["customers"].quick_editor( - column_info_settings={ - "email": {"custom_validate_fn": lambda value: is_valid_email(value)} - } - ) + frm["customers"].quick_editor(**quick_editor_kwargs) # call a Form-level save elif "Save" in event: frm.save_records() diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index dabc28cf..5fd71640 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2724,7 +2724,7 @@ def quick_editor( pk_update_funct: callable = None, funct_param: any = None, skip_prompt_save: bool = False, - column_info_settings: dict = None, + column_attributes: dict = None, ) -> None: """ The quick editor is a dynamic PySimpleGUI Window for quick editing of tables. @@ -2737,8 +2737,9 @@ def quick_editor( select by default when the quick editor loads. :param funct_param: (optional) A parameter to pass to the `pk_update_funct` :param skip_prompt_save: (Optional) True to skip prompting to save dirty records - :param column_info_settings: (Optional) Set Column attributes in - `DataSet.column_info`, in the form of {column_name {attribute : value}}. + :param column_attributes: (Optional) Dictionary specifying column attributes + for `DataSet.column_info`. The dictionary should be in the form + {column_name: {attribute: value}}. :returns: None """ # prompt_save @@ -2844,8 +2845,8 @@ def quick_editor( else: quick_frm[data_key].set_by_pk(pk_update_funct(funct_param)) - if column_info_settings: - for col, kwargs in column_info_settings.items(): + if column_attributes: + for col, kwargs in column_attributes.items(): if quick_frm[data_key].column_info[col]: for attr, value in kwargs.items(): quick_frm[data_key].column_info[col][attr] = value @@ -3876,12 +3877,16 @@ def auto_map_events(self, win: sg.Window) -> None: self.window[search_box].bind_dataset(self[data_key]) # elif event_type==EVENT_SEARCH_DB: elif event_type == EVENT_QUICK_EDIT: + quick_editor_kwargs = {} + if "quick_editor_kwargs" in element.metadata: + quick_editor_kwargs = element.metadata["quick_editor_kwargs"] referring_table = table table = self[table].get_related_table_for_column(column) funct = functools.partial( self[table].quick_editor, self[referring_table].get_current, column, + **quick_editor_kwargs if quick_editor_kwargs else {}, ) elif event_type == EVENT_FUNCTION: funct = function @@ -6200,6 +6205,7 @@ def field( no_label: bool = False, label_above: bool = False, quick_editor: bool = True, + quick_editor_kwargs: dict = None, filter=None, key=None, use_ttk_buttons=None, @@ -6225,6 +6231,7 @@ def field( :param label_above: Place the label above the element instead of to the left. :param quick_editor: For records that reference another table, place a quick edit button next to the element + :param quick_editor_kwargs: Additional keyword arguments to pass to quick editor. :param filter: Can be used to reference different `Form`s in the same layout. Use a matching filter when creating the `Form` with the filter parameter. :param key: (optional) The key to give this element. See note above about the @@ -6242,7 +6249,7 @@ def field( if use_ttk_buttons is None: use_ttk_buttons = themepack.use_ttk_buttons if pad is None: - pad = themepack.quick_editor_button_pad + pad = themepack.default_element_pad # if Record imply a where clause (indicated by ?) If so, strip out the info we need if "?" in field: @@ -6280,6 +6287,7 @@ def field( "field": field, "data_key": key, }, + pad=pad, **kwargs, ) else: @@ -6294,6 +6302,7 @@ def field( "field": field, "data_key": key, }, + pad=pad, **kwargs, ) layout_label = sg.Text( @@ -6331,6 +6340,7 @@ def field( "function": None, "Form": None, "filter": filter, + "quick_editor_kwargs": quick_editor_kwargs, } if type(themepack.quick_edit) is bytes: layout[-1].append( @@ -7570,7 +7580,7 @@ class ThemePack: # Defaults for actions() buttons & popups # ---------------------------------------- "use_ttk_buttons": True, - "quick_editor_button_pad": (3, 0), + "default_element_pad": (5, 0), "action_button_pad": (3, 0), "popup_button_pad": (5, 5), # Action buttons From 4d75bebb99105523a46a84759c50ab4fe43d17e7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 2 Jul 2023 16:05:01 -0400 Subject: [PATCH 052/121] Nit: change default Column python_type to object, ditch the special-cased if --- pysimplesql/pysimplesql.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 5fd71640..521c0e78 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7970,7 +7970,7 @@ class Column: pk: bool virtual: bool = False generated: bool = False - python_type: Type[object] = Any + python_type: Type[object] = object custom_cast_fn: callable = None custom_validate_fn: callable = None @@ -8016,9 +8016,6 @@ def validate(self, value: Any) -> bool: except Exception as e: # noqa: BLE001 logger.debug(f"Error running custom_validate_fn, {e}") - if self.python_type == Any: - return ValidateResponse() - if not isinstance(value, self.python_type): return ValidateResponse( ValidateRule.PYTHON_TYPE, value, self.python_type.__name__ From ecd6c8e444970e819cdd1f00cd4fb4e82724645a Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:52:22 -0400 Subject: [PATCH 053/121] Better Sqlite Column mapping Since you can basically use whatever name you want in sqlite, lets make it more flexible. --- examples/orders_multiple_databases.py | 4 +- pysimplesql/pysimplesql.py | 61 +++++++++++++++------------ 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 9184c7bf..4105c3c6 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -135,7 +135,7 @@ def is_valid_email(email): CREATE TABLE orders ( order_id {pk_type} NOT NULL PRIMARY KEY {autoincrement}, customer_id {integer_type} NOT NULL, - date DATE NOT NULL DEFAULT {date_default}, + date {date_type} NOT NULL DEFAULT {date_default}, total {numeric_type}, completed {boolean_type} NOT NULL, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) @@ -292,7 +292,7 @@ def is_valid_email(email): "text_type": "TEXT", "integer_type": "INTEGER", "date_type": "DATE", - "numeric_type": "NUMERIC(10,2)", + "numeric_type": "DECTEXT(10,2)", "date_default": "(date('now'))", "boolean_type": "BOOLEAN", "default_string": "'New Product'", diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 521c0e78..97a686cd 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9156,11 +9156,11 @@ def parse_domain(self, domain): return domain_name, domain_args - def get_column_class(self, domain): + def get_column_class(self, domain) -> ColumnClass: if domain in self.COLUMN_CLASS_MAP: return self.COLUMN_CLASS_MAP[domain] logger.info(f"Mapping {domain} to generic Column class") - return Column + return None # -------------------------------------------------------------------------------------- @@ -9173,25 +9173,7 @@ class Sqlite(SQLDriver): DECIMAL_DOMAINS = ["DECIMAL", "DECTEXT", "MONEY", "NUMERIC"] - COLUMN_CLASS_MAP = { - "BOOLEAN": BoolCol, - "CLOB": StrCol, - "CHARACTER": StrCol, - "DATE": DateCol, - "DATETIME": DateTimeCol, - "DECIMAL": DecimalCol, - "DECTEXT": DecimalCol, - "INTEGER": IntCol, - "MONEY": DecimalCol, - "NATIVE CHARACTER": StrCol, - "NCHAR": StrCol, - "NVARCHAR": StrCol, - "NUMERIC": DecimalCol, - "REAL": FloatCol, - "TEXT": StrCol, - "VARCHAR": StrCol, - "VARYING CHARACTER": StrCol, - } + COLUMN_CLASS_MAP = {} SQL_CONSTANTS = [ "CURRENT_DATE", @@ -9332,8 +9314,7 @@ def column_info(self, table): col_info = ColumnInfo(self, table) for _, row in rows.iterrows(): domain, domain_args = self.parse_domain(row["type"]) - col_class = self.get_column_class(domain) - + col_class = self.get_column_class(domain) or Column # TODO: should we exclude hidden columns? # if row["hidden"] == 1: # continue @@ -9392,6 +9373,31 @@ def relationships(self): def adapt_decimal(self, d): return str(d) + def get_column_class(self, domain): + if self.COLUMN_CLASS_MAP: + col_class = super().get_column_class(domain) + if col_class is not None: + return col_class + if "DATETIME" in domain or "TIMESTAMP" in domain: + return DateTimeCol + if "DATE" in domain: + return DateCol + if "TIME" in domain: + return TimeCol + if any(col_name in domain for col_name in self.DECIMAL_DOMAINS): + return DecimalCol + if "BOOL" in domain: + return BoolCol + if "INT" in domain: + return IntCol + if any(col_name in domain for col_name in ["TEXT", "CHAR", "CLOB"]): + return StrCol + if any(col_name in domain for col_name in ["REAL", "FLOA", "DOUB"]): + return FloatCol + if "BLOB" in domain or domain == "": + return Column + return None + def convert_decimal(self, s): return Decimal(s.decode("utf-8")) @@ -9761,7 +9767,7 @@ def column_info(self, table): col_class = BoolCol else: - col_class = self.get_column_class(domain) + col_class = self.get_column_class(domain) or Column if col_class == DecimalCol: domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] elif col_class in [FloatCol, IntCol]: @@ -10083,7 +10089,7 @@ def column_info(self, table: str) -> ColumnInfo: for _, row in rows.iterrows(): name = row["column_name"] domain = row["data_type"].upper() - col_class = self.get_column_class(domain) + col_class = self.get_column_class(domain) or Column domain_args = [] if col_class == DecimalCol: domain_args = [row["numeric_precision"], row["numeric_scale"]] @@ -10403,7 +10409,7 @@ def column_info(self, table): for _, row in rows.iterrows(): name = row["COLUMN_NAME"] domain = row["DATA_TYPE"].upper() - col_class = self.get_column_class(domain) + col_class = self.get_column_class(domain) or Column domain_args = [] if col_class == DecimalCol: domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] @@ -10794,7 +10800,7 @@ def column_info(self, table): default = str(rs.getString("COLUMN_DEF")) pk = name in pk_columns generated = str(rs.getString("IS_GENERATEDCOLUMN")) == "YES" - col_class = self.get_column_class(domain) + col_class = self.get_column_class(domain) or Column domain_args = [] # handling Date/Time columns, since they are all reported as DateTime @@ -10988,6 +10994,7 @@ class SimpleTransform(TypedDict): decode: Dict[str, Callable[[str, str], None]] encode: Dict[str, Callable[[str, str], None]] +ColumnClass = TypeVar('T', bound=Column) SimpleTransformsDict = Dict[str, SimpleTransform] From 12e1b22008d29769838dc3722b1c89d2570d461e Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:53:00 -0400 Subject: [PATCH 054/121] Feat: adapters for date/datetime/time sqlite --- pysimplesql/pysimplesql.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 97a686cd..076fa51b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9371,8 +9371,6 @@ def relationships(self): relationships.append(dic) return relationships - def adapt_decimal(self, d): - return str(d) def get_column_class(self, domain): if self.COLUMN_CLASS_MAP: col_class = super().get_column_class(domain) @@ -9398,9 +9396,17 @@ def get_column_class(self, domain): return Column return None + def _register_type_callables(self): + # Register datetime adapters/converters + # python 3.12 will depreciate dt.date/dt.datetime default adapters + sqlite3.register_adapter(dt.date, lambda val: val.isoformat()) + sqlite3.register_adapter(dt.datetime, lambda val: val.isoformat(" ")) + sqlite3.register_adapter(dt.time, lambda val: val.isoformat()) - def convert_decimal(self, s): - return Decimal(s.decode("utf-8")) + # Register Decimal adapter/converter + sqlite3.register_adapter(Decimal, str) + for domain in self.DECIMAL_DOMAINS: + sqlite3.register_converter(domain, lambda val: Decimal(val.decode("utf-8"))) # -------------------------------------------------------------------------------------- From b10cc2118f34c2a7cb8012360eb12424dca8b6b5 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:54:49 -0400 Subject: [PATCH 055/121] Convert all Col classes to dataclasses, use __post_init__ to assign domain_args This allows automatic type-hinting/args/kwargs in pycharm. I didn't feel good about how we were 'magically' assigning domain_args by putting them first anyway. --- pysimplesql/pysimplesql.py | 148 ++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 076fa51b..e5fc5910 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -58,6 +58,7 @@ import asyncio import calendar import contextlib +import dataclasses as dc import datetime as dt import enum import functools @@ -71,11 +72,21 @@ import threading import tkinter as tk import tkinter.font as tkfont -from dataclasses import dataclass from decimal import Decimal, DecimalException from time import sleep, time from tkinter import ttk -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypedDict, Union +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Tuple, + Type, + TypedDict, + TypeVar, + Union, +) import numpy as np import pandas as pd @@ -262,7 +273,7 @@ class ValidateRule(str, enum.Enum): CUSTOM = "custom" -@dataclass +@dc.dataclass class ValidateResponse: exception: Union[ValidateRule, None] = None value: str = None @@ -336,7 +347,7 @@ def get_instance(self): return self -@dataclass +@dc.dataclass class Relationship: """ @@ -524,7 +535,7 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str } -@dataclass +@dc.dataclass class ElementMap: """ @@ -7941,13 +7952,16 @@ class Abstractions: pass +T = TypeVar("T") + + # ====================================================================================== # COLUMN ABSTRACTION # ====================================================================================== # The column abstraction hides the complexity of dealing with SQL columns, getting their # names, default values, data types, primary key status and notnull status # -------------------------------------------------------------------------------------- -@dataclass +@dc.dataclass class Column: """ @@ -7966,13 +7980,14 @@ class Column: name: str domain: str notnull: bool - default: None + default: Any pk: bool virtual: bool = False generated: bool = False - python_type: Type[object] = object + python_type: Type[T] = object custom_cast_fn: callable = None custom_validate_fn: callable = None + domain_args: List[str, int] = None def __getitem__(self, key): return self.__dict__[key] @@ -7982,6 +7997,9 @@ def __setitem__(self, key, value): def __contains__(self, item): return item in self.__dict__ + + def __post_init__(self): + pass def cast(self, value: Any) -> Any: """ @@ -8023,7 +8041,7 @@ def validate(self, value: Any) -> bool: return ValidateResponse() - +@dc.dataclass class MinMaxCol(Column): """ Column subclass representing a value with minimum and maximum constraints. @@ -8039,10 +8057,8 @@ class MinMaxCol(Column): :type max_value: Any valid value type compatible with the column's data type. """ - def __init__(self, min_value=None, max_value=None, **kwargs): - super().__init__(**kwargs) - self.min_value = min_value - self.max_value = max_value + min_value: Any = None + max_value: Any = None def validate(self, value): response = super().validate(value) @@ -8060,6 +8076,7 @@ def validate(self, value): return ValidateResponse() +@dc.dataclass class LengthCol(Column): """ Column subclass for length-constrained columns. @@ -8072,10 +8089,14 @@ class LengthCol(Column): :param min_length: Minimum length allowed for the column value. """ - def __init__(self, max_length: int = None, min_length: int = None, **kwargs): - super().__init__(**kwargs) - self.max_length = int(max_length) if max_length is not None else None - self.min_length = int(min_length) if min_length is not None else None + min_length: int = None + max_length: int = None + + def __post_init__(self): + super().__post_init__() + if self.domain_args and self.max_length is None: + self.max_length = int(self.domain_args[0]) + def validate(self, value): response = super().validate(value) @@ -8091,20 +8112,23 @@ def validate(self, value): return ValidateResponse() +@dc.dataclass class BoolCol(Column): - def __init__(self, *args, **kwargs): - super().__init__(**kwargs) + def __post_init__(self): + super().__post_init__() self.python_type = bool def cast(self, value): return checkbox_to_bool(value) +@dc.dataclass class DateCol(MinMaxCol): - def __init__(self, date_format: str = DATE_FORMAT, **kwargs): - super().__init__(**kwargs) + date_format: str = DATE_FORMAT + + def __post_init__(self): + super().__post_init__() self.python_type = dt.date - self.date_format = date_format def cast(self, value): if isinstance(value, self.python_type): @@ -8145,20 +8169,20 @@ def cast(self, value): return super().cast(value) +@dc.dataclass class DateTimeCol(MinMaxCol): - def __init__( - self, - datetime_format_list: List[str] = [ + datetime_format_list: List[str] = dc.field( + default_factory=lambda: [ DATETIME_FORMAT, DATETIME_FORMAT_MICROSECOND, TIMESTAMP_FORMAT, TIMESTAMP_FORMAT_MICROSECOND, - ], - **kwargs, - ): - super().__init__(**kwargs) + ] + ) + + def __post_init__(self): + super().__post_init__() self.python_type = dt.datetime - self.datetime_format_list = datetime_format_list def cast(self, value): if isinstance(value, self.python_type): @@ -8174,12 +8198,18 @@ def cast(self, value): return super().cast(value) +@dc.dataclass class DecimalCol(MinMaxCol): - def __init__(self, precision=10, scale=2, **kwargs): - super().__init__(**kwargs) + precision: int = 10 + scale: int = 2 + + def __post_init__(self): + super().__post_init__() self.python_type = Decimal - self.precision = int(precision) if precision is not None else None - self.scale = int(scale) if scale is not None else None + if self.domain_args: + self.precision = int(self.domain_args[0]) if self.precision == 10 else 10 + if len(self.domain_args) == 2: + self.scale = int(self.domain_args[1]) if self.scale == 2 else 2 def cast(self, value): if value == "-": @@ -8203,9 +8233,10 @@ def validate(self, value): return ValidateResponse() +@dc.dataclass class FloatCol(LengthCol, MinMaxCol): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __post_init__(self): + super().__post_init__() self.python_type = float def cast(self, value): @@ -8217,16 +8248,13 @@ def cast(self, value): return super().cast(value) +@dc.dataclass class IntCol(LengthCol, MinMaxCol): - def __init__( - self, - *args, - truncate_decimals: bool = False, - **kwargs, - ): - super().__init__(*args, **kwargs) + truncate_decimals: bool = False + + def __post_init__(self): + super().__post_init__() self.python_type = int - self.truncate_decimals = truncate_decimals def cast(self, value, truncate_decimals: bool = None): truncate_decimals = ( @@ -8253,20 +8281,23 @@ def cast(self, value, truncate_decimals: bool = None): return super().cast(value_backup) +@dc.dataclass class StrCol(LengthCol): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __post_init__(self): + super().__post_init__() self.python_type = str def cast(self, value): return super().cast(value) -class TimeCol(Column): - def __init__(self, time_format: str = TIME_FORMAT, **kwargs): - super().__init__(**kwargs) +@dc.dataclass +class TimeCol(MinMaxCol): + time_format: str = TIME_FORMAT + + def __post_init__(self): + super().__post_init__() self.python_type = dt.time - self.time_format = time_format def cast(self, value): if isinstance(value, self.python_type): @@ -9200,11 +9231,8 @@ def __init__( self.import_required_modules() - # Register the adapter - sqlite3.register_adapter(Decimal, self.adapt_decimal) - # Register the converter - for domain in self.DECIMAL_DOMAINS: - sqlite3.register_converter(domain, self.convert_decimal) + # register adapters and converters + self._register_type_callables() new_database = False if db_path is not None: @@ -9327,13 +9355,13 @@ def column_info(self, table): generated = row["hidden"] in [2, 3] col_info.append( col_class( - *domain_args, name=name, domain=domain, notnull=notnull, default=default, pk=pk, generated=generated, + domain_args=domain_args, ) ) @@ -9787,13 +9815,13 @@ def column_info(self, table): generated = row["EXTRA"] in ["VIRTUAL GENERATED", "STORED GENERATED"] col_info.append( col_class( - *domain_args, name=name, domain=domain, notnull=notnull, default=default, pk=pk, generated=generated, + domain_args=domain_args, ) ) @@ -10112,13 +10140,13 @@ def column_info(self, table: str) -> ColumnInfo: generated = row["is_generated"] == "ALWAYS" col_info.append( col_class( - *domain_args, name=name, domain=domain, notnull=notnull, default=default, pk=pk, generated=generated, + domain_args=domain_args, ) ) @@ -10438,13 +10466,13 @@ def column_info(self, table): generated = name in generated_columns col_info.append( col_class( - *domain_args, name=name, domain=domain, notnull=notnull, default=default, pk=pk, generated=generated, + domain_args=domain_args, ) ) @@ -10822,13 +10850,13 @@ def column_info(self, table): col_info.append( col_class( - *domain_args, name=name, domain=domain, notnull=notnull, default=default, pk=pk, generated=generated, + domain_args=domain_args, ) ) From 826e3064c1fe5ce1d9cc02fdd9f1f7fa982be6bf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:56:17 -0400 Subject: [PATCH 056/121] Nit: ruff formatting --- pysimplesql/pysimplesql.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index e5fc5910..32eaee90 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7997,7 +7997,7 @@ def __setitem__(self, key, value): def __contains__(self, item): return item in self.__dict__ - + def __post_init__(self): pass @@ -8041,6 +8041,7 @@ def validate(self, value: Any) -> bool: return ValidateResponse() + @dc.dataclass class MinMaxCol(Column): """ @@ -8097,7 +8098,6 @@ def __post_init__(self): if self.domain_args and self.max_length is None: self.max_length = int(self.domain_args[0]) - def validate(self, value): response = super().validate(value) if response.exception: @@ -11028,7 +11028,8 @@ class SimpleTransform(TypedDict): decode: Dict[str, Callable[[str, str], None]] encode: Dict[str, Callable[[str, str], None]] -ColumnClass = TypeVar('T', bound=Column) + +ColumnClass = TypeVar("T", bound=Column) SimpleTransformsDict = Dict[str, SimpleTransform] From 7e1abe1f3f8882f0ca4dbcfc04c4fe2f0a4ad284 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:59:06 -0400 Subject: [PATCH 057/121] Fix: typing hint --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 32eaee90..eb5f5aef 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -11029,7 +11029,7 @@ class SimpleTransform(TypedDict): encode: Dict[str, Callable[[str, str], None]] -ColumnClass = TypeVar("T", bound=Column) +ColumnClass = TypeVar("ColumnClass", bound=Column) SimpleTransformsDict = Dict[str, SimpleTransform] From 01c7b11c3b8ad261cae90ca6e4336e4070c7b5ba Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 6 Jul 2023 11:09:45 -0400 Subject: [PATCH 058/121] Cleanup: ColClasses I didn't need to use __post_init__, I just needed to add type-hints, then they would be overwrite Column python_type --- pysimplesql/pysimplesql.py | 43 +++++++++----------------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index eb5f5aef..68fb24f4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -7998,9 +7998,6 @@ def __setitem__(self, key, value): def __contains__(self, item): return item in self.__dict__ - def __post_init__(self): - pass - def cast(self, value: Any) -> Any: """ Cast a value to the appropriate data type as defined by the column info for the @@ -8094,7 +8091,6 @@ class LengthCol(Column): max_length: int = None def __post_init__(self): - super().__post_init__() if self.domain_args and self.max_length is None: self.max_length = int(self.domain_args[0]) @@ -8114,9 +8110,7 @@ def validate(self, value): @dc.dataclass class BoolCol(Column): - def __post_init__(self): - super().__post_init__() - self.python_type = bool + python_type: Type[bool] = dc.field(default=bool, init=False) def cast(self, value): return checkbox_to_bool(value) @@ -8125,10 +8119,7 @@ def cast(self, value): @dc.dataclass class DateCol(MinMaxCol): date_format: str = DATE_FORMAT - - def __post_init__(self): - super().__post_init__() - self.python_type = dt.date + python_type: Type[dt.date] = dc.field(default=dt.date, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -8179,10 +8170,7 @@ class DateTimeCol(MinMaxCol): TIMESTAMP_FORMAT_MICROSECOND, ] ) - - def __post_init__(self): - super().__post_init__() - self.python_type = dt.datetime + python_type: Type[dt.datetime] = dc.field(default=dt.datetime, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -8202,10 +8190,9 @@ def cast(self, value): class DecimalCol(MinMaxCol): precision: int = 10 scale: int = 2 + python_type: Type[Decimal] = dc.field(default=Decimal, init=False) def __post_init__(self): - super().__post_init__() - self.python_type = Decimal if self.domain_args: self.precision = int(self.domain_args[0]) if self.precision == 10 else 10 if len(self.domain_args) == 2: @@ -8235,9 +8222,7 @@ def validate(self, value): @dc.dataclass class FloatCol(LengthCol, MinMaxCol): - def __post_init__(self): - super().__post_init__() - self.python_type = float + python_type: Type[float] = dc.field(default=float, init=False) def cast(self, value): if value == "-": @@ -8251,10 +8236,7 @@ def cast(self, value): @dc.dataclass class IntCol(LengthCol, MinMaxCol): truncate_decimals: bool = False - - def __post_init__(self): - super().__post_init__() - self.python_type = int + python_type: Type[int] = dc.field(default=int, init=False) def cast(self, value, truncate_decimals: bool = None): truncate_decimals = ( @@ -8283,9 +8265,7 @@ def cast(self, value, truncate_decimals: bool = None): @dc.dataclass class StrCol(LengthCol): - def __post_init__(self): - super().__post_init__() - self.python_type = str + python_type: Type[str] = dc.field(default=str, init=False) def cast(self, value): return super().cast(value) @@ -8294,10 +8274,7 @@ def cast(self, value): @dc.dataclass class TimeCol(MinMaxCol): time_format: str = TIME_FORMAT - - def __post_init__(self): - super().__post_init__() - self.python_type = dt.time + python_type: Type[dt.time] = dc.field(default=dt.time, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -9187,7 +9164,7 @@ def parse_domain(self, domain): return domain_name, domain_args - def get_column_class(self, domain) -> ColumnClass: + def get_column_class(self, domain) -> Union[ColumnClass, None]: if domain in self.COLUMN_CLASS_MAP: return self.COLUMN_CLASS_MAP[domain] logger.info(f"Mapping {domain} to generic Column class") @@ -9399,7 +9376,7 @@ def relationships(self): relationships.append(dic) return relationships - def get_column_class(self, domain): + def get_column_class(self, domain) -> Union[ColumnClass, None]: if self.COLUMN_CLASS_MAP: col_class = super().get_column_class(domain) if col_class is not None: From 04f4e334adc749d60d2baa3aeae7aca593225840 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:30:13 -0400 Subject: [PATCH 059/121] Cleanup: DecimalCol Match Pony's defaults of 12/2. I debated using max_digits, and decimal_places... but I think precision/scale are more common --- pysimplesql/pysimplesql.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 68fb24f4..02293237 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -227,6 +227,8 @@ # -------------- PK_PLACEHOLDER = "Null" EMPTY = ["", None] +DECIMAL_PRECISION = 12 +DECIMAL_SCALE = 2 # -------------------- # Date formats @@ -8188,15 +8190,27 @@ def cast(self, value): @dc.dataclass class DecimalCol(MinMaxCol): - precision: int = 10 - scale: int = 2 + precision: int = DECIMAL_PRECISION + scale: int = DECIMAL_SCALE python_type: Type[Decimal] = dc.field(default=Decimal, init=False) def __post_init__(self): if self.domain_args: - self.precision = int(self.domain_args[0]) if self.precision == 10 else 10 - if len(self.domain_args) == 2: - self.scale = int(self.domain_args[1]) if self.scale == 2 else 2 + try: + self.precision = int(self.domain_args[0]) + except ValueError: + logger.debug( + f"Unable to set {self.name} column decimal precision to " + f"{self.domain_args[0]}" + ) + if len(self.domain_args) >= 2: + try: + self.scale = int(self.domain_args[1]) + except ValueError: + logger.debug( + f"Unable to set {self.name} column decimal scale to " + f"{self.domain_args[1]}" + ) def cast(self, value): if value == "-": From 9a27cdcaf54e91a9569f1899a24cb98dbcda524e Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:43:42 -0400 Subject: [PATCH 060/121] Refactor: popup Go back to using window, rather than needing a frm Consolidate window kwargs --- pysimplesql/pysimplesql.py | 54 +++++++++++++------------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 02293237..7442ee83 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3321,7 +3321,7 @@ def bind(self, win: sg.Window) -> None: """ logger.info("Binding Window to Form") self.window = win - self.popup = Popup(self) + self.popup = Popup(self.window) self.auto_map_elements(win) self.auto_map_events(win) self.update_elements() @@ -4844,17 +4844,25 @@ class Popup: Has popup functions for internal use. Stores last info popup as last_info """ - def __init__(self, frm_reference: Form = None): + def __init__(self, window: sg.Window = None): """ Create a new Popup instance :returns: None. """ - self.frm = frm_reference + self.window = window self.popup_info = None self.last_info_msg: str = "" self.last_info_time = None self.info_elements = [] self._timeout_id = None + self._window_kwargs = { + "keep_on_top": True, + "element_justification": "center", + "enable_close_attempted_event": True, + "icon": themepack.icon, + "ttk_theme": themepack.ttk_theme, + "finalize": True, + } def ok(self, title, msg): """ @@ -4872,17 +4880,7 @@ def ok(self, title, msg): pad=themepack.popup_button_pad, ) ) - popup_win = sg.Window( - title, - layout=[layout], - keep_on_top=True, - modal=True, - finalize=True, - ttk_theme=themepack.ttk_theme, - element_justification="center", - enable_close_attempted_event=True, - icon=themepack.icon, - ) + popup_win = sg.Window(title, layout=[layout], modal=True, **self._window_kwargs) while True: event, values = popup_win.read() @@ -4914,17 +4912,7 @@ def yes_no(self, title, msg): pad=themepack.popup_button_pad, ) ) - popup_win = sg.Window( - title, - layout=[layout], - keep_on_top=True, - modal=True, - finalize=True, - ttk_theme=themepack.ttk_theme, - element_justification="center", - enable_close_attempted_event=True, - icon=themepack.icon, - ) + popup_win = sg.Window(title, layout=[layout], modal=True, **self._window_kwargs) while True: event, values = popup_win.read() @@ -4963,14 +4951,8 @@ def info( self.popup_info = sg.Window( title=title, layout=layout, - no_titlebar=False, - keep_on_top=True, - finalize=True, alpha_channel=themepack.popup_info_alpha_channel, - element_justification="center", - ttk_theme=themepack.ttk_theme, - enable_close_attempted_event=True, - icon=themepack.icon, + **self._window_kwargs, ) self.popup_info.TKroot.after( int(auto_close_seconds * 1000), self._auto_close @@ -5011,7 +4993,7 @@ def update_info_element( if erase: message = "" if self._timeout_id: - self.frm.window.TKroot.after_cancel(self._timeout_id) + self.window.TKroot.after_cancel(self._timeout_id) elif timeout and self.last_info_time: elapsed_sec = time() - self.last_info_time @@ -5023,11 +5005,11 @@ def update_info_element( element.update(message) # record time of update, and tk.after - if not erase and self.frm: + if not erase and self.window: self.last_info_time = time() if self._timeout_id: - self.frm.window.TKroot.after_cancel(self._timeout_id) - self._timeout_id = self.frm.window.TKroot.after( + self.window.TKroot.after_cancel(self._timeout_id) + self._timeout_id = self.window.TKroot.after( int(auto_erase_seconds * 1000), lambda: self.update_info_element(timeout=True), ) From b20cfc96b6f971c9f9603727eb75adf4423cd906 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:59:24 -0400 Subject: [PATCH 061/121] Fixes for examples Going through examples and fixing them for ColumnClasses, and other little things. --- .../journal_sqlserver_docker.py | 19 +++++-- examples/SQLite_examples/Journal.db | Bin 12288 -> 16384 bytes examples/SQLite_examples/address_book.py | 27 +--------- examples/SQLite_examples/journal_internal.py | 2 +- .../journal_with_data_manipulation.py | 3 +- examples/SQLite_examples/orders.py | 1 + examples/SQLite_examples/password_callback.py | 2 +- pysimplesql/pysimplesql.py | 48 ++++++++++++++---- 8 files changed, 61 insertions(+), 41 deletions(-) diff --git a/examples/SQLServer_examples/journal_sqlserver_docker.py b/examples/SQLServer_examples/journal_sqlserver_docker.py index 41394380..028a4551 100644 --- a/examples/SQLServer_examples/journal_sqlserver_docker.py +++ b/examples/SQLServer_examples/journal_sqlserver_docker.py @@ -25,6 +25,20 @@ ports={"1433/tcp": ("127.0.0.1", 1433)}, ) +# The original docker has DEFAULT for entry_date column as GETDATE() +# which returns a DateTime. We just want the date +sql_commands = """ +DECLARE @ConstraintName nvarchar(200) +SELECT @ConstraintName = Name FROM SYS.DEFAULT_CONSTRAINTS +WHERE PARENT_OBJECT_ID = OBJECT_ID('Journal') +AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns + WHERE NAME = N'entry_date' + AND object_id = OBJECT_ID(N'Journal')) +IF @ConstraintName IS NOT NULL +EXEC('ALTER TABLE Journal DROP CONSTRAINT ' + @ConstraintName); +ALTER TABLE [Journal] ADD DEFAULT CAST(GETDATE() AS DATE) FOR [entry_date]; +""" + # ------------------------- # CREATE PYSIMPLEGUI LAYOUT # ------------------------- @@ -69,9 +83,8 @@ } # Create the Window, Driver and Form win = sg.Window("Journal example: MS SQLServer", layout, finalize=True) -driver = ss.Driver.sqlserver( - **sqlserver_docker -) # Use the postgres examples database credentials +# Use the postgres examples database credentials +driver = ss.Driver.sqlserver(**sqlserver_docker, sql_commands=sql_commands) frm = ss.Form(driver, bind_window=win) # <=== Here is the magic! # Reverse the default sort order so new journal entries appear at the top diff --git a/examples/SQLite_examples/Journal.db b/examples/SQLite_examples/Journal.db index a49c2dca08249e786242c82770c73f88a66ff853..46e64241adec838099229edf461357afdb021785 100644 GIT binary patch delta 768 zcmZojXlP)ZAT4Okz`(!)#f(6jW1^0+ur-68`wCvZ3I--VCI&txzE?cwHWtS4)bmv^ znuzP`GqwnpBqrsg78m9u#h0Y!7Q}zu0aZZ{vir}p*}u3K=G2ylAKf}&Jfp#5Cs=kH%H}ApAZFgztnOC*SwOV zN_CK$R1hmZC9wpk&;@AKWOaUpdZ2i2ett?kE`!0EAO-`?2nh1@bqtDB@OF*V0J%jI zDCXuLgF2c>gVhltf1rzv_T1?flIT5iCtV&l(Eqp6cUs9`7%La zS~R(aFRH#torN6~Xw9{VKx+~Pib+eu#JuuLi}Dh4pcGo5K}AUmDnv+04z{4;1%?ba zw+RFP8h$4}!5e&Sdy%{Gr fNoGxUlFyp#Cmpg#K|l^@2pE7(!7yZFAt5sW9KX=1 delta 420 zcmYk2!A`(lrSK7_46;-740cIKaNHanft)0=rTd`wZ)mUlK{?DUaWJiI&?yb>cal=vVr zZWulUhY|1Cmu-5-c@(cIN&||hkq&^TIKjlw<9_-l71dQZL%EZhq(Va0?*W-)I8LnH zx_@c5HL@Bh$STSs#Tf|j1S?+Ovf54D-VoOgkV`7aHA(u<*?0i_t*cJ1q@Y4x_8$nz zH7f-Q${_{Gr>F`S9zTiODad5hF1`f=n>u*0j{5^^NHmiGv4C`S&_=}SYXrBTGcsEQ zTc&1UG6t&y<#E-s8+x@OO#&$)a82=CgyQ8;mU|XoMOt|0eu7`YIKSW`?!j(^x95A# oGN!tp(Fx6p-;`<3dhXkPk!C|n3fnW?$;NuD_wA}ZD=%R87xk%bQvd(} diff --git a/examples/SQLite_examples/address_book.py b/examples/SQLite_examples/address_book.py index 7cba97f0..84cc7486 100644 --- a/examples/SQLite_examples/address_book.py +++ b/examples/SQLite_examples/address_book.py @@ -9,7 +9,7 @@ # Zip code validation -def validate_zip(): +def validate_zip(frm, window): zipcode = win['Addresses.zip'].get() if len(zipcode) != 5: sg.popup('Check your zip code and try again!', title="Zip code validation failed!") @@ -97,7 +97,7 @@ def validate_zip(): [sg.Text("Zip:"+" "*63), ss.field("Addresses.zip", size=(6, 1), no_label=True)], [ss.actions("Addresses", edit_protect=False, duplicate=True)], # sg.StatusBar sets character limit based on initial value. Here we are filling it with 100 spaces. - [sg.StatusBar(' '*100, key='status_bar')] + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})] ] win = sg.Window('Address book example', layout, finalize=True, ttk_theme=ss.themepack.ttk_theme) @@ -112,13 +112,6 @@ def validate_zip(): # Use a callback to validate the zip code frm['Addresses'].set_callback('before_save', validate_zip) -# variables for updating our sg.StatusBar -seconds_to_display = 3 -last_val = "" -new_val = "" -counter = 1 - - # --------- # MAIN LOOP # --------- @@ -137,22 +130,6 @@ def validate_zip(): # This could also be done by enabling events in the input controls, but this is much simpler. dirty = frm['Addresses'].records_changed() win['Addresses:db_save'].update(disabled=not dirty) - #-------------------------------------------------- - # Status bar updating - #-------------------------------------------------- - # Using the same timeout, we can update our sg.StatusBar with save messages - counter += 1 - new_val = frm.popup.last_info_msg - # If there is a new info popup msg, reset our counter and update the sg.StatusBar - if new_val != last_val: - counter = 0 - win['status_bar'].update(value=new_val) - last_val = new_val - # After counter reaches seconds limit, clear sg.StatusBar and frm.popup.last_info_msg - if counter > seconds_to_display * 10: - counter = 0 - frm.popup.last_info_msg = "" - win['status_bar'].update(value="") elif ss.process_events(event, values): # <=== let PySimpleSQL process its own events! Simple! logger.info(f'PySimpleDB event handler handled the event {event}!') else: diff --git a/examples/SQLite_examples/journal_internal.py b/examples/SQLite_examples/journal_internal.py index 3a210c39..cc72db6f 100644 --- a/examples/SQLite_examples/journal_internal.py +++ b/examples/SQLite_examples/journal_internal.py @@ -17,7 +17,7 @@ CREATE TABLE Journal( "id" INTEGER NOT NULL PRIMARY KEY, "title" TEXT DEFAULT 'New Entry', - "entry_date" INTEGER NOT NULL DEFAULT (date('now')), + "entry_date" DATE NOT NULL DEFAULT (date('now')), "mood_id" INTEGER NOT NULL, "entry" TEXT, FOREIGN KEY (mood_id) REFERENCES Mood(id) --This line is important to the automatic functionality of PySimpleSQL~ diff --git a/examples/SQLite_examples/journal_with_data_manipulation.py b/examples/SQLite_examples/journal_with_data_manipulation.py index 21c5546c..88c224f4 100644 --- a/examples/SQLite_examples/journal_with_data_manipulation.py +++ b/examples/SQLite_examples/journal_with_data_manipulation.py @@ -16,7 +16,7 @@ CREATE TABLE Journal( "id" INTEGER NOT NULL PRIMARY KEY, "title" TEXT DEFAULT "New Entry", - "entry_date" INTEGER DEFAULT (strftime('%s', 'now')), --Store date information as a unix epoch timestamp + "entry_date" TEXT DEFAULT (strftime('%s', 'now')), --Store date information as a unix epoch timestamp "mood_id" INTEGER, "entry" TEXT, FOREIGN KEY (mood_id) REFERENCES Mood(id) @@ -32,7 +32,6 @@ INSERT INTO Journal (id,mood_id,title,entry)VALUES (1,1,"My first entry!","I am excited to write my thoughts every day"); INSERT INTO Journal (id,mood_id,title,entry)VALUES (2,4,"My 2nd entry!","I feel like Doogie Howser "); """ - # ------------------------- # CREATE PYSIMPLEGUI LAYOUT # ------------------------- diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 9c631696..9b1feda9 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -229,6 +229,7 @@ [ss.field("order_details.price", sg.Text)], [ss.field("order_details.subtotal", sg.Text)], [sg.Sizer(h_pixels=0, v_pixels=10)], + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})] ] layout.append([sg.Frame("Order Details", orderdetails_layout, expand_x=True)]) diff --git a/examples/SQLite_examples/password_callback.py b/examples/SQLite_examples/password_callback.py index f3c40f90..138f27b0 100644 --- a/examples/SQLite_examples/password_callback.py +++ b/examples/SQLite_examples/password_callback.py @@ -50,7 +50,7 @@ def disable(db, win): # Set our callbacks # See documentation for a full list of callbacks supported -frm.set_callback('allow_cell_edits', enable) +frm.set_callback('edit_enable', enable) frm.set_callback('edit_disable', disable) while True: diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7442ee83..8977ce44 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -857,6 +857,10 @@ def _invoke_callback(self, callback, *args): # Get the number of parameters in the signature expected_args = len(signature.parameters) + # handling a no-argument callback + if expected_args == 0: + return callback() + if expected_args == 3 or (expected_args == 2 and len(args) == 2): # Pass all arguments if callback supports same length. # len(args) == 2, for backwards compatibility while converting code @@ -8076,7 +8080,9 @@ class LengthCol(Column): def __post_init__(self): if self.domain_args and self.max_length is None: - self.max_length = int(self.domain_args[0]) + self.max_length = ( + int(self.domain_args[0]) if self.domain_args[0] is not None else None + ) def validate(self, value): response = super().validate(value) @@ -8179,7 +8185,11 @@ class DecimalCol(MinMaxCol): def __post_init__(self): if self.domain_args: try: - self.precision = int(self.domain_args[0]) + self.precision = ( + int(self.domain_args[0]) + if self.domain_args[0] is not None + else None + ) except ValueError: logger.debug( f"Unable to set {self.name} column decimal precision to " @@ -8187,7 +8197,11 @@ def __post_init__(self): ) if len(self.domain_args) >= 2: try: - self.scale = int(self.domain_args[1]) + self.scale = ( + int(self.domain_args[1]) + if self.domain_args[1] is not None + else None + ) except ValueError: logger.debug( f"Unable to set {self.name} column decimal scale to " @@ -8598,6 +8612,8 @@ class SQLDriver: SQLDriver.insert_record() """ + SQL_CONSTANTS = [] + # --------------------------------------------------------------------- # MUST implement # in order to function @@ -8617,7 +8633,6 @@ def __init__( to close the database. """ # Be sure to call super().__init__() in derived class! - self.SQL_CONSTANTS = [] self.con = None self.name = name self.requires = requires @@ -8793,11 +8808,17 @@ def relationship_to_join_clause(self, r_obj: Relationship): def min_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MIN({pk_column}) as min_pk FROM {table}") - return rows.iloc[0]["min_pk"].tolist() + if rows.iloc[0]["min_pk"] is not None: + return rows.iloc[0]["min_pk"].tolist() + else: + return 0 def max_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MAX({pk_column}) as max_pk FROM {table}") - return rows.iloc[0]["max_pk"].tolist() + if rows.iloc[0]["max_pk"] is not None: + return rows.iloc[0]["max_pk"].tolist() + else: + return 0 def generate_join_clause(self, dataset: DataSet) -> str: """ @@ -9933,7 +9954,16 @@ class Postgres(SQLDriver): "TIMESTAMPTZ": DateTimeCol, } - SQL_CONSTANTS = ["CURRENT_USER", "SESSION_USER", "USER"] + SQL_CONSTANTS = [ + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "LOCALTIME", + "LOCALTIMESTAMP", + "CURRENT_USER", + "SESSION_USER", + "USER", + ] def __init__( self, @@ -10410,7 +10440,6 @@ def column_info(self, table): gen_rows = self.execute(gen_query, [table], silent=True) for _, row in gen_rows.iterrows(): generated_columns.append(row[0]) - rows = rows.fillna("") # setup all the variables to be passed to col_info for _, row in rows.iterrows(): @@ -10423,7 +10452,8 @@ def column_info(self, table): elif col_class in [FloatCol, IntCol]: domain_args = [row["NUMERIC_PRECISION"]] elif col_class == StrCol: - domain_args = [row["CHARACTER_MAXIMUM_LENGTH"]] + # SqlServer apparently uses -1.0 for None + domain_args = [row["CHARACTER_MAXIMUM_LENGTH"]] if not -1.0 else None notnull = row["IS_NULLABLE"] == "NO" if row["COLUMN_DEFAULT"]: col_default = row["COLUMN_DEFAULT"] From 27b0b46a4ed864f2b8131d3ed7c9b1d3c453b796 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 13:23:02 -0400 Subject: [PATCH 062/121] Fix: Move transform above validate in DataSet.save_record Not sure what the end-story is for transform, but it makes sense to apply encode-transforms before validating. --- examples/SQLite_examples/journal_with_data_manipulation.py | 2 +- pysimplesql/pysimplesql.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/SQLite_examples/journal_with_data_manipulation.py b/examples/SQLite_examples/journal_with_data_manipulation.py index 88c224f4..63fa6f4c 100644 --- a/examples/SQLite_examples/journal_with_data_manipulation.py +++ b/examples/SQLite_examples/journal_with_data_manipulation.py @@ -16,7 +16,7 @@ CREATE TABLE Journal( "id" INTEGER NOT NULL PRIMARY KEY, "title" TEXT DEFAULT "New Entry", - "entry_date" TEXT DEFAULT (strftime('%s', 'now')), --Store date information as a unix epoch timestamp + "entry_date" INTEGER DEFAULT (strftime('%s', 'now')), --Store date information as a unix epoch timestamp "mood_id" INTEGER, "entry" TEXT, FOREIGN KEY (mood_id) REFERENCES Mood(id) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8977ce44..1f2cb5d8 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2022,6 +2022,10 @@ def save_record( self.frm.update_fields(self.key) return SAVE_NONE + SHOW_MESSAGE + # apply any transformations + if self.transform is not None: + self.transform(self, changed_row_dict, TFORM_ENCODE) + # check to make sure we have valid inputs if validate_fields: invalid_response = {} @@ -2056,9 +2060,6 @@ def save_record( # Update the database from the stored rows # ---------------------------------------- - if self.transform is not None: - self.transform(self, changed_row_dict, TFORM_ENCODE) - # reset search string self.search_string = "" From 00ada45ffee3b319d633bc35cf97ebe42e8268af Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 13:23:08 -0400 Subject: [PATCH 063/121] Ruff fix --- pysimplesql/pysimplesql.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1f2cb5d8..670c42a1 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -8811,15 +8811,13 @@ def min_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MIN({pk_column}) as min_pk FROM {table}") if rows.iloc[0]["min_pk"] is not None: return rows.iloc[0]["min_pk"].tolist() - else: - return 0 + return 0 def max_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MAX({pk_column}) as max_pk FROM {table}") if rows.iloc[0]["max_pk"] is not None: return rows.iloc[0]["max_pk"].tolist() - else: - return 0 + return 0 def generate_join_clause(self, dataset: DataSet) -> str: """ From e130d464223cce267b15675c277d83c999fbeee9 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:26:22 -0400 Subject: [PATCH 064/121] Fix/Simplify: _invoke_callback --- pysimplesql/pysimplesql.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 670c42a1..9cc68d08 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -857,18 +857,8 @@ def _invoke_callback(self, callback, *args): # Get the number of parameters in the signature expected_args = len(signature.parameters) - # handling a no-argument callback - if expected_args == 0: - return callback() - - if expected_args == 3 or (expected_args == 2 and len(args) == 2): - # Pass all arguments if callback supports same length. - # len(args) == 2, for backwards compatibility while converting code - return callback(*args) - if expected_args == 2 and len(args) == 3: - # for backwards compatibility, pass only first 2 args (frm & win) - return callback(*args[:-1]) - # Handle the case if the callback expects a different number of parameters + if expected_args <= 3: + return callback(*args[:expected_args]) raise ValueError("Unexpected number of parameters in the callback function") def set_transform(self, fn: callable) -> None: From 82ee0c95b5ebe719a907c699b167f151b1052cb0 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:27:22 -0400 Subject: [PATCH 065/121] Ruff: enable pandas-vet, pep8-naming --- examples/SQLite_examples/address_book.py | 2 +- examples/SQLite_examples/orders.py | 4 +-- examples/orders_multiple_databases.py | 2 +- pysimplesql/pysimplesql.py | 41 ++++++++++++------------ ruff.toml | 5 +-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/SQLite_examples/address_book.py b/examples/SQLite_examples/address_book.py index 84cc7486..864b88ed 100644 --- a/examples/SQLite_examples/address_book.py +++ b/examples/SQLite_examples/address_book.py @@ -9,7 +9,7 @@ # Zip code validation -def validate_zip(frm, window): +def validate_zip(): zipcode = win['Addresses.zip'].get() if len(zipcode) != 5: sg.popup('Check your zip code and try again!', title="Zip code validation failed!") diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 9b1feda9..9f8e78c3 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -229,7 +229,7 @@ [ss.field("order_details.price", sg.Text)], [ss.field("order_details.subtotal", sg.Text)], [sg.Sizer(h_pixels=0, v_pixels=10)], - [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})] + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})], ] layout.append([sg.Frame("Order Details", orderdetails_layout, expand_x=True)]) @@ -335,7 +335,7 @@ def update_orders(frm_reference, window, data_key): # set current rows 'price' to match price as matching product_id dataset["price"] = product_df.loc[ product_df["product_id"] == product_id, "price" - ].values[0] + ].to_numpy()[0] # save the record dataset.save_record(display_message=False) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 4105c3c6..b28a30dd 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -603,7 +603,7 @@ def update_orders(frm_reference, window, data_key): # set current rows 'price' to match price as matching product_id dataset["price"] = product_df.loc[ product_df["product_id"] == product_id, "price" - ].values[0] + ].to_numpy()[0] # save the record dataset.save_record(display_message=False) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 9cc68d08..1aed22d0 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2416,7 +2416,7 @@ def get_description_for_pk(self, pk: int) -> Union[str, int, None]: return current_row[self.description_column] try: index = self.rows.loc[self.rows[self.pk_column] == pk].index[0] - return self.rows[self.description_column].iat[index] + return self.rows[self.description_column].iloc[index] except IndexError: return None @@ -2551,7 +2551,7 @@ def table_values( self.rows.loc[ self.rows[pk_column] == self.get_current_row()[pk_column], pk_column, - ].values[0] + ].to_numpy()[0] ) # Create a new column 'marker' with the desired values @@ -2592,7 +2592,7 @@ def table_values( # set the pk to the index to use below rows["pk_idx"] = rows[pk_column].copy() - rows.set_index("pk_idx", inplace=True) + rows = rows.set_index("pk_idx") # insert the marker columns.insert(0, "marker") @@ -2605,7 +2605,7 @@ def table_values( TableRow(pk, values.tolist()) for pk, values in zip( rows.index, - np.vstack((rows.fillna("").astype("O").values.T, rows.index)).T, + np.vstack((rows.fillna("").astype("O").to_numpy().T, rows.index)).T, ) ] @@ -2905,7 +2905,7 @@ def purge_virtual(self) -> None: # remove the rows where virtual is True in place, along with the corresponding # virtual attribute virtual_rows = self.rows[self.rows[self.pk_column].isin(self.virtual_pks)] - self.rows.drop(index=virtual_rows.index, inplace=True) + self.rows = self.rows.drop(index=virtual_rows.index) self.rows.attrs["virtual"] = [] def sort_by_column(self, column: str, table: str, reverse=False) -> None: @@ -2956,17 +2956,16 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: # sort try: - self.rows.sort_values( + self.rows = self.rows.sort_values( column, ascending=not reverse, - inplace=True, ) except (KeyError, TypeError) as e: logger.debug(f"DataFrame could not sort by column {column}. {e}") finally: # Drop the temporary description column (if it exists) if tmp_column is not None: - self.rows.drop(columns=tmp_column, inplace=True, errors="ignore") + self.rows = self.rows.drop(columns=tmp_column, errors="ignore") def sort_by_index(self, index: int, table: str, reverse=False): """ @@ -3009,7 +3008,7 @@ def sort_reset(self) -> None: :returns: None """ # Restore the original sort order - self.rows.sort_index(inplace=True) + self.rows = self.rows.sort_index() def sort(self, table: str, update_elements: bool = True, sort_order=None) -> None: """ @@ -5384,7 +5383,7 @@ class LazyTable(sg.Table): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.values = [] # full set of rows + self.values = [] # noqa PD011 # full set of rows self.data = [] # lazy slice of rows self.Values = self.data @@ -5423,7 +5422,7 @@ def update( return # update total list - self.values = values + self.values = values # noqa PD011 # Update current_index with the selected index self.current_index = select_rows[0] if select_rows else 0 @@ -5518,7 +5517,7 @@ def _handle_start_scroll(self): # determine slice num_rows = min(self._start_index, self.insert_qty) new_start_index = max(0, self._start_index - num_rows) - new_rows = self.values[new_start_index : self._start_index] + new_rows = self.values[new_start_index : self._start_index] # noqa PD011 # insert for row in reversed(new_rows): @@ -5545,7 +5544,7 @@ def _handle_end_scroll(self): # determine slice start_index = max(0, self._end_index) end_index = min(self._end_index + self.insert_qty, num_rows) - new_rows = self.values[start_index:end_index] + new_rows = self.values[start_index:end_index] # noqa PD011 # insert for row in new_rows: @@ -5580,7 +5579,7 @@ def _set_colors(self, iid, toggle_color): return toggle_color @property - def SelectedRows(self): + def SelectedRows(self): # noqa N802 """ Returns the selected row(s) in the LazyTable. @@ -8568,13 +8567,13 @@ def set( :param exception: Exceptions passed back from the SQLDriver :param column_info: An optional ColumnInfo object """ - df = pd.DataFrame(row_data) - df.attrs["lastrowid"] = lastrowid - df.attrs["exception"] = exception - df.attrs["column_info"] = column_info - df.attrs["row_backup"] = row_backup - df.attrs["virtual"] = [] - return df + rows = pd.DataFrame(row_data) + rows.attrs["lastrowid"] = lastrowid + rows.attrs["exception"] = exception + rows.attrs["column_info"] = column_info + rows.attrs["row_backup"] = row_backup + rows.attrs["virtual"] = [] + return rows class ReservedKeywordError(Exception): diff --git a/ruff.toml b/ruff.toml index dff3c660..8f2e19bd 100644 --- a/ruff.toml +++ b/ruff.toml @@ -5,7 +5,7 @@ select = [ "W", #pycodestyle Warning # "C90", #mccabe "I", #isort -# "N", #pep8-naming + "N", #pep8-naming # "D", #pydocstyle # "UP", #pyupgrade "YTT", #flake8-2020 @@ -40,7 +40,7 @@ select = [ # "ARG", #flake8-unused-arguments # "PTH", #flake8-use-pathlib # "ERA", #eradicate -# "PD", #pandas-vet + "PD", #pandas-vet # "PGH", #pygrep-hooks "PLC", #Pylint Convention "PLE", #Pylint Error @@ -54,6 +54,7 @@ ignore = [ "D211", #No blank lines allowed before class docstring "D212", #Multi-line docstring summary should start at the first line "PLC1901", #We compare to "" alot, and for good reason. + "N813", # ignore Camelcase `PySimpleGUI` imported as lowercase `sg` # Will do below later ] From 450a270a70412b98950dcce62dbdc1e58772eb91 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:43:55 -0400 Subject: [PATCH 066/121] Ruff: enable #flake8-comprehensions, #flake8-bugbear --- pysimplesql/pysimplesql.py | 46 +++++++++++++++++++------------------- ruff.toml | 7 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1aed22d0..7c5ac0eb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3282,12 +3282,12 @@ def __del__(self): def __getitem__(self, key: str) -> DataSet: try: return self.datasets[key] - except KeyError: + except KeyError as e: raise RuntimeError( f"The DataSet for `{key}` does not exist. This can be caused because " f"the database does not exist, the database user does not have the " f"proper permissions set, or any number of db configuration issues." - ) + ) from e def close(self, reset_keygen: bool = True): """ @@ -5698,7 +5698,7 @@ def update(self, *args, **kwargs): self.Widget.config(fg=self.normal_color, font=self.normal_font) elif value in EMPTY: # If the value is empty, reinsert the placeholder - super().update(value=self.placeholder_text, *args, **kwargs) + super().update(value=self.placeholder_text, **kwargs) self.active_placeholder = True self.Widget.config(fg=self.placeholder_color, font=self.placeholder_font) else: @@ -7638,8 +7638,8 @@ class ThemePack: Default Themepack. """ - def __init__(self, tp_dict: Dict[str, str] = {}) -> None: - self.tp_dict = ThemePack.default + def __init__(self, tp_dict: Dict[str, str] = None) -> None: + self.tp_dict = tp_dict or ThemePack.default def __getattr__(self, key): # Try to get the key from the internal tp_dict first. @@ -7649,10 +7649,12 @@ def __getattr__(self, key): except KeyError: try: return ThemePack.default[key] - except KeyError: - raise AttributeError(f"ThemePack object has no attribute '{key}'") + except KeyError as e: + raise AttributeError( + f"ThemePack object has no attribute '{key}'" + ) from e - def __call__(self, tp_dict: Dict[str, str] = {}) -> None: + def __call__(self, tp_dict: Dict[str, str] = None) -> None: """ Update the ThemePack object from tp_dict. @@ -7688,10 +7690,7 @@ def __call__(self, tp_dict: Dict[str, str] = {}) -> None: """ # noqa: E501 # For default use cases, load the default directly to avoid the overhead # of __getattr__() going through 2 key reads - if tp_dict == {}: - tp_dict = ThemePack.default - - self.tp_dict = tp_dict + self.tp_dict = tp_dict or ThemePack.default # set a default themepack @@ -7856,8 +7855,8 @@ class LanguagePack: Default LanguagePack. """ - def __init__(self, lp_dict={}): - self.lp_dict = type(self).default + def __init__(self, lp_dict=None): + self.lp_dict = lp_dict or type(self).default def __getattr__(self, key): # Try to get the key from the internal lp_dict first. @@ -7867,8 +7866,10 @@ def __getattr__(self, key): except KeyError: try: return type(self).default[key] - except KeyError: - raise AttributeError(f"LanguagePack object has no attribute '{key}'") + except KeyError as e: + raise AttributeError( + f"LanguagePack object has no attribute '{key}'" + ) from e def __getitem__(self, key): try: @@ -7876,17 +7877,16 @@ def __getitem__(self, key): except KeyError: try: return type(self).default[key] - except KeyError: - raise AttributeError(f"LanguagePack object has no attribute '{key}'") + except KeyError as e: + raise AttributeError( + f"LanguagePack object has no attribute '{key}'" + ) from e - def __call__(self, lp_dict={}): + def __call__(self, lp_dict=None): """Update the LanguagePack instance.""" # For default use cases, load the default directly to avoid the overhead # of __getattr__() going through 2 key reads - if lp_dict == {}: - lp_dict = type(self).default - - self.lp_dict = lp_dict + self.lp_dict = lp_dict or type(self).default # set a default languagepack diff --git a/ruff.toml b/ruff.toml index 8f2e19bd..c4023a71 100644 --- a/ruff.toml +++ b/ruff.toml @@ -13,10 +13,10 @@ select = [ # "S", #flake8-bandit "BLE", #flake8-blind-except # "FBT", #flake8-boolean-trap -# "B", #flake8-bugbear + "B", #flake8-bugbear # "A", #flake8-builtins # "COM", #flake8-commas -# "C4", #flake8-comprehensions + "C4", #flake8-comprehensions # "DTZ", #flake8-datetimez "T10", #flake8-debugger "DJ", #flake8-django @@ -55,7 +55,7 @@ ignore = [ "D212", #Multi-line docstring summary should start at the first line "PLC1901", #We compare to "" alot, and for good reason. "N813", # ignore Camelcase `PySimpleGUI` imported as lowercase `sg` - # Will do below later + "B905", # py310, `zip()` without an explicit `strict=` parameter ] [per-file-ignores] @@ -70,3 +70,4 @@ ignore = [ "tests/*" = ["BLE001", "F405", "PT011", "PT012", "PT015", "PT017", "SIM114"] "pysimplesql/language_pack.py" = ["E501"] "pysimplesql/theme_pack.py" = ["E501"] +"pysimplesql/reserved_sql_keywords.py" = ["C405"] From 85b75fa40fb2b60bc8aa8fc685a3ab6cda1c5f74 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 14:54:46 -0400 Subject: [PATCH 067/121] Ruff: Enable 'PIE' --- pysimplesql/pysimplesql.py | 4 ---- ruff.toml | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7c5ac0eb..4c9adc42 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -4671,8 +4671,6 @@ class Utility: use to the end user. """ - pass - def process_events(event: str, values: list) -> bool: """ @@ -6183,7 +6181,6 @@ class Convenience: use to the end user. """ - pass def field( @@ -7927,7 +7924,6 @@ class Abstractions: use to the end user. """ - pass T = TypeVar("T") diff --git a/ruff.toml b/ruff.toml index c4023a71..508f7bbc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -15,9 +15,9 @@ select = [ # "FBT", #flake8-boolean-trap "B", #flake8-bugbear # "A", #flake8-builtins -# "COM", #flake8-commas +# "COM", #flake8-commas # DONT "C4", #flake8-comprehensions -# "DTZ", #flake8-datetimez +# "DTZ", #flake8-datetimez # TODO "T10", #flake8-debugger "DJ", #flake8-django # "EM", #flake8-errmsg @@ -26,7 +26,7 @@ select = [ # "ICN", #flake8-import-conventions # "G", #flake8-logging-format # "INP", #flake8-no-pep420 -# "PIE", #flake8-pie + "PIE", #flake8-pie # "T20", #flake8-print "PYI", #flake8-pyi "PT", #flake8-pytest-style From 3d8c711e7f6c0e9123d46723c0c897bbe11ce3d5 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:04:04 -0400 Subject: [PATCH 068/121] Ruff: Enable NPY rules --- examples/orders_multiple_databases.py | 18 +++++++++++------- pysimplesql/pysimplesql.py | 2 -- ruff.toml | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index b28a30dd..e0eea100 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -1,7 +1,6 @@ import logging import platform import re -import time import numpy as np import pandas as pd @@ -218,12 +217,15 @@ def is_valid_email(email): # Generate random orders using pandas DataFrame num_orders = 100 +rng = np.random.default_rng() orders_df = pd.DataFrame( { "order_id": np.arange(1, num_orders + 1), - "customer_id": np.random.randint(1, 25, num_orders), - "date": pd.date_range(start=time.strftime("%Y-%m-%d"), periods=num_orders).date, - "completed": np.random.choice(["{true_bool}", "{false_bool}"], num_orders), + "customer_id": rng.integers(1, 25, size=num_orders), + "date": pd.date_range( + start=pd.Timestamp.now().strftime("%Y-%m-%d"), periods=num_orders + ).date.tolist(), + "completed": rng.choice(["{true_bool}", "{false_bool}"], size=num_orders), } ) @@ -231,9 +233,11 @@ def is_valid_email(email): num_order_details = num_orders * 5 order_details_df = pd.DataFrame( { - "order_id": np.random.choice(orders_df["order_id"], num_order_details), - "product_id": np.random.randint(1, 25, num_order_details), - "quantity": np.random.randint(1, 10, num_order_details), + "order_id": rng.choice( + orders_df["order_id"], size=num_order_details, replace=True + ), + "product_id": rng.integers(1, 25, size=num_order_details), + "quantity": rng.integers(1, 10, size=num_order_details), } ) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 4c9adc42..52e8923b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -6182,7 +6182,6 @@ class Convenience: """ - def field( field: str, element: Type[sg.Element] = _EnhancedInput, @@ -7925,7 +7924,6 @@ class Abstractions: """ - T = TypeVar("T") diff --git a/ruff.toml b/ruff.toml index 508f7bbc..e66ed83e 100644 --- a/ruff.toml +++ b/ruff.toml @@ -41,13 +41,13 @@ select = [ # "PTH", #flake8-use-pathlib # "ERA", #eradicate "PD", #pandas-vet -# "PGH", #pygrep-hooks +# "PGH", #pygrep-hooks # DONT "PLC", #Pylint Convention "PLE", #Pylint Error # "PLR", #Pylint Refactor "PLW", #Pylint Warning # "TRY", #tryceratops -# "NPY", #NumPy-specific rules + "NPY", #NumPy-specific rules # "RUF", #Ruff-specific rules ] ignore = [ From cf1a2a25f777a211761d3eea8806e1f410a69885 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:45:15 -0400 Subject: [PATCH 069/121] Ruff: Enable Ruff rules --- .../MySQL_examples/docker/Docker_create.py | 2 +- .../docker/Docker_create.py | 2 +- examples/orders_multiple_databases.py | 6 +- pysimplesql/pysimplesql.py | 108 ++++++++++-------- ruff.toml | 4 +- tests/sqldriver_test.py | 2 +- 6 files changed, 68 insertions(+), 56 deletions(-) diff --git a/examples/MySQL_examples/docker/Docker_create.py b/examples/MySQL_examples/docker/Docker_create.py index 1ee27d0f..d875f2d8 100644 --- a/examples/MySQL_examples/docker/Docker_create.py +++ b/examples/MySQL_examples/docker/Docker_create.py @@ -45,7 +45,7 @@ with tqdm(desc="Starting up container") as pbar: container.reload() while True: - if "Status" in container.attrs["State"]: # noqa: SIM102 + if "Status" in container.attrs["State"]: if container.attrs["State"]["Status"] == "running": break time.sleep(1) diff --git a/examples/PostgreSQL_examples/docker/Docker_create.py b/examples/PostgreSQL_examples/docker/Docker_create.py index b9a79229..45ecae30 100644 --- a/examples/PostgreSQL_examples/docker/Docker_create.py +++ b/examples/PostgreSQL_examples/docker/Docker_create.py @@ -45,7 +45,7 @@ with tqdm(desc="Starting up container") as pbar: container.reload() while True: - if "Status" in container.attrs["State"]: # noqa: SIM102 + if "Status" in container.attrs["State"]: if container.attrs["State"]["Status"] == "running": break time.sleep(1) diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index e0eea100..850add4d 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -270,7 +270,7 @@ def is_valid_email(email): SELECT SUM(subtotal) FROM order_details WHERE order_details.order_id = orders.order_id ); {enable_constraints} -""" # noqa E501 +""" sqlserver_disable_constraints = """ DECLARE @sql nvarchar(MAX) @@ -316,7 +316,7 @@ def is_valid_email(email): "boolean_type": "BIT", "default_string": "'New Product'", "default_boolean": "FALSE", - "generated_column": "DECIMAL(10,2) GENERATED ALWAYS AS (`price` * `quantity`) STORED", # noqa E501 + "generated_column": "DECIMAL(10,2) GENERATED ALWAYS AS (`price` * `quantity`) STORED", "autoincrement": "AUTO_INCREMENT", "false_bool": 0, "true_bool": 1, @@ -333,7 +333,7 @@ def is_valid_email(email): "boolean_type": "BOOLEAN", "default_string": "'New Product'", "default_boolean": "FALSE", - "generated_column": "NUMERIC(10,2) GENERATED ALWAYS AS (price * quantity) STORED", # noqa E501 + "generated_column": "NUMERIC(10,2) GENERATED ALWAYS AS (price * quantity) STORED", "autoincrement": "", "false_bool": False, "true_bool": True, diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 52e8923b..ffc47a19 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -78,6 +78,7 @@ from typing import ( Any, Callable, + ClassVar, Dict, List, Optional, @@ -373,7 +374,7 @@ class Relationship: """ # store our own instances - instances = [] + instances: ClassVar[List[Relationship]] = [] join_type: str child_table: str @@ -589,7 +590,7 @@ class DataSet: typically aren't created manually by the user. """ - instances = [] # Track our own instances + instances: ClassVar[List[DataSet]] = [] # Track our own instances def __init__( self, @@ -3158,8 +3159,8 @@ class Form: `DataSet` objects can be accessed by key, I.e. frm['data_key']. """ - instances = [] # Track our instances - relationships = [] # Track our relationships + instances: ClassVar[List[Form]] = [] # Track our instances + relationships: ClassVar[List[Relationship]] = [] # Track our relationships def __init__( self, @@ -3609,7 +3610,7 @@ def add_info_element(self, element: Union[sg.StatusBar, sg.Text]) -> None: :returns: None """ if not isinstance(element, (sg.StatusBar, sg.Text)): - logger.debug(f"Can only add info {str(element)}") + logger.debug(f"Can only add info {element!s}") return logger.debug(f"Mapping element {element.key}") self.popup.info_elements.append(element) @@ -3744,7 +3745,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: ) else: - logger.debug(f"Can not add selector {str(element)}") + logger.debug(f"Can not add selector {element!s}") elif element.metadata["type"] == TYPE_INFO: self.add_info_element(element) @@ -5149,7 +5150,7 @@ def __init__(self, title: str, config: dict = None): if "phrase_delay" in config and not all( isinstance(v, (int, float)) for v in config["phrase_delay"] - ): # noqa SIM102 + ): raise ValueError("phrase_delay must be numeric") self.config = {**default_config, **config} @@ -5209,7 +5210,7 @@ async def run_process(self, fn: callable, *args, **kwargs): return await loop.run_in_executor( None, functools.partial(fn, *args, **kwargs) ) - except Exception as e: # noqa: BLE001 + except Exception as e: print(f"\nAn error occurred in the process: {e}") raise e # Pass the exception along to the caller finally: @@ -5220,7 +5221,7 @@ async def _dispatch(self, fn: callable, *args, **kwargs): gui_task = asyncio.create_task(self._gui()) result = await self.run_process(fn, *args, **kwargs) await gui_task - return result # noqa RET504 + return result def _animate(self, config: dict = None): def _oscillate_params(oscillator): @@ -5319,7 +5320,7 @@ def get(self, key: str, separator: str = None) -> str: return_key = key if self._keygen[key] > 0: # only modify the key if it is a duplicate! - return_key += f"{separator}{str(self._keygen[key])}" + return_key += f"{separator}{self._keygen[key]!s}" logger.debug(f"Key generated: {return_key}") self._keygen[key] += 1 return return_key @@ -5609,27 +5610,29 @@ def __setattr__(self, name, value): super().__setattr__(name, value) +@dc.dataclass(init=False) class _PlaceholderText(abc.ABC): """ An abstract class for PySimpleGUI text-entry elements that allows for the display of a placeholder text when the input is empty. """ - binds = {} - placeholder_feature_enabled = False - normal_color = None - normal_font = None - placeholder_text = "" - placeholder_color = None - placeholder_font = None - active_placeholder = False # fmt: off - _non_keys = ["Control_L","Control_R","Alt_L","Alt_R","Shift_L","Shift_R", - "Caps_Lock","Return","Escape","Tab","BackSpace","Up","Down","Left", - "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4","F5", - "F6","F7","F8","F9","F10","F11","F12", "Delete"] + _non_keys: ClassVar[List[str]] = {"Control_L","Control_R","Alt_L","Alt_R","Shift_L", + "Shift_R","Caps_Lock","Return","Escape","Tab","BackSpace","Up","Down", + "Left", "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4", + "F5","F6","F7","F8","F9","F10","F11","F12", "Delete"} # fmt: on + binds: dict = dc.field(default_factory=lambda: {}) + placeholder_feature_enabled: bool = False + normal_color: str = None + normal_font: str = None + placeholder_text: str = "" + placeholder_color: str = None + placeholder_font: str = None + active_placeholder: bool = False + def add_placeholder(self, placeholder: str, color: str = None, font: str = None): """ Adds a placeholder text to the element. @@ -5749,7 +5752,7 @@ def on_key(event): return None def on_key_release(event): - if widget.get() == "": # noqa PLC1901 + if widget.get() in EMPTY: with contextlib.suppress(tk.TclError): self.insert_placeholder() widget.icursor(0) @@ -5870,18 +5873,21 @@ def _on_search_string_change(self, *args): if ( not self.active_placeholder and self.get() != self.search_string.get() - and self.search_string.get() == "" # noqa PLC1901 + and self.search_string.get() in EMPTY ): # reinsert placeholder if DataSet.search_string == "" self.insert_placeholder() +@dc.dataclass(init=False) class _AutoCompleteLogic: - _completion_list = [] - _hits = [] - _hit_index = 0 - position = 0 - finalized = False + _completion_list: List[Union[str, ElementRow]] = dc.field( + default_factory=lambda: list + ) + _hits: List[int] = dc.field(default_factory=lambda: list) + _hit_index: int = 0 + position: int = 0 + finalized: bool = False def _autocomplete_combo(self, completion_list, delta=0): widget = self.Widget @@ -6906,7 +6912,7 @@ class TableHeadings(list): """ # store our instances - instances = [] + instances: ClassVar[List[TableHeadings]] = [] def __init__( self, @@ -7556,7 +7562,7 @@ class ThemePack: sg.Button(ss.themepack.search, key='search_button') """ - default = { + default: ClassVar[Dict[Any]] = { # Theme to use with ttk widgets. # ------------------------------- # Choices (on Windows) include: @@ -7709,7 +7715,7 @@ class LanguagePack: lp_en = {'save_success': 'SAVED!'} """ - default = { + default: ClassVar[Dict[Any]] = { # ------------------------------------------------------------------------------ # Buttons # ------------------------------------------------------------------------------ @@ -8303,7 +8309,7 @@ class ColumnInfo(List): """ # List of required SQL types to check against when user sets custom values - _python_types = [ + _python_types: ClassVar[List[str]] = [ "str", "int", "float", @@ -8596,7 +8602,7 @@ class SQLDriver: SQLDriver.insert_record() """ - SQL_CONSTANTS = [] + SQL_CONSTANTS: ClassVar[List[str]] = [] # --------------------------------------------------------------------- # MUST implement @@ -8838,7 +8844,7 @@ def generate_where_clause(dataset: DataSet) -> str: if not parent_pk: parent_pk = PK_PLACEHOLDER - clause = f" WHERE {table}.{r.fk_column}={str(parent_pk)}" + clause = f" WHERE {table}.{r.fk_column}={parent_pk!s}" if where: clause = clause.replace("WHERE", "AND") where += clause @@ -9095,7 +9101,7 @@ def save_record( # Set empty fields to None for k, v in changed_row.items(): - if v == "": + if v in EMPTY: changed_row[k] = None # quote appropriately @@ -9122,7 +9128,7 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # Set empty fields to None for k, v in row.items(): - if v == "": # noqa: PLC1901 + if v in EMPTY: row[k] = None # quote appropriately @@ -9178,11 +9184,11 @@ class Sqlite(SQLDriver): The SQLite driver supports SQLite3 databases. """ - DECIMAL_DOMAINS = ["DECIMAL", "DECTEXT", "MONEY", "NUMERIC"] + DECIMAL_DOMAINS: ClassVar[List[str]] = ["DECIMAL", "DECTEXT", "MONEY", "NUMERIC"] - COLUMN_CLASS_MAP = {} + COLUMN_CLASS_MAP: ClassVar[List[str]] = {} - SQL_CONSTANTS = [ + SQL_CONSTANTS: ClassVar[List[str]] = [ "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", @@ -9396,7 +9402,7 @@ def get_column_class(self, domain) -> Union[ColumnClass, None]: return StrCol if any(col_name in domain for col_name in ["REAL", "FLOA", "DOUB"]): return FloatCol - if "BLOB" in domain or domain == "": + if "BLOB" in domain or domain in EMPTY: return Column return None @@ -9593,7 +9599,7 @@ class Mysql(SQLDriver): The Mysql driver supports MySQL databases. """ - COLUMN_CLASS_MAP = { + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIT": BoolCol, "BIGINT": IntCol, "CHAR": StrCol, @@ -9620,7 +9626,11 @@ class Mysql(SQLDriver): "YEAR": IntCol, } - SQL_CONSTANTS = ["CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP"] + SQL_CONSTANTS: ClassVar[List[str]] = [ + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + ] def __init__( self, @@ -9671,7 +9681,7 @@ def __init__( self.win_pb.close() def import_required_modules(self): - global mysql # noqa PLW0603 + global mysql try: import mysql.connector except ModuleNotFoundError as e: @@ -9914,7 +9924,7 @@ class Postgres(SQLDriver): The Postgres driver supports PostgreSQL databases. """ - COLUMN_CLASS_MAP = { + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIGINT": IntCol, "BIGSERIAL": IntCol, "BOOLEAN": BoolCol, @@ -9936,7 +9946,7 @@ class Postgres(SQLDriver): "TIMESTAMPTZ": DateTimeCol, } - SQL_CONSTANTS = [ + SQL_CONSTANTS: ClassVar[List[str]] = [ "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", @@ -10240,7 +10250,7 @@ class Sqlserver(SQLDriver): The Sqlserver driver supports Microsoft SQL Server databases. """ - COLUMN_CLASS_MAP = { + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIGINT": IntCol, "BIT": BoolCol, "CHAR": StrCol, @@ -10267,7 +10277,7 @@ class Sqlserver(SQLDriver): "VARCHAR": StrCol, } - SQL_CONSTANTS = [ + SQL_CONSTANTS: ClassVar[List[str]] = [ "CURRENT_USER", "HOST_NAME", "NULL", @@ -10566,7 +10576,7 @@ class MSAccess(SQLDriver): frm[DATASET KEY].column_info[COLUMN NAME].scale = 2 """ - COLUMN_CLASS_MAP = { + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIG_INT": IntCol, "BOOLEAN": BoolCol, "DECIMAL": DecimalCol, diff --git a/ruff.toml b/ruff.toml index e66ed83e..238836de 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,3 +1,4 @@ +target-version = "py38" # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. select = [ "F", #Pyflakes @@ -48,7 +49,7 @@ select = [ "PLW", #Pylint Warning # "TRY", #tryceratops "NPY", #NumPy-specific rules -# "RUF", #Ruff-specific rules + "RUF", #Ruff-specific rules ] ignore = [ "D211", #No blank lines allowed before class docstring @@ -56,6 +57,7 @@ ignore = [ "PLC1901", #We compare to "" alot, and for good reason. "N813", # ignore Camelcase `PySimpleGUI` imported as lowercase `sg` "B905", # py310, `zip()` without an explicit `strict=` parameter + "RUF013", #TODO, way it autofixes is '|', should use Union ] [per-file-ignores] diff --git a/tests/sqldriver_test.py b/tests/sqldriver_test.py index cf63e7d9..a8ea471f 100644 --- a/tests/sqldriver_test.py +++ b/tests/sqldriver_test.py @@ -6,7 +6,7 @@ import pytest import pysimplesql as ss -from pysimplesql.docker_utils import * # noqa F403 +from pysimplesql.docker_utils import * # ruff: noqa From 14f082ae1e7b97486023862e05e136fc9e8ee0e1 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:46:37 -0400 Subject: [PATCH 070/121] Added a STRICT validate mode --- pysimplesql/pysimplesql.py | 88 +++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index ffc47a19..bea233c1 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -265,6 +265,11 @@ class Boolean(enum.Flag): FALSE = False +class ValidateMode(str, enum.Enum): + STRICT = "strict" + RELAXED = "relaxed" + + class ValidateRule(str, enum.Enum): REQUIRED = "required" PYTHON_TYPE = "python_type" @@ -605,7 +610,7 @@ def __init__( prompt_save: int = None, save_quiet: bool = None, duplicate_children: bool = None, - validate_fields: bool = None, + validate_mode: ValidateMode = None, ) -> None: """ Initialize a new `DataSet` instance. @@ -634,7 +639,9 @@ def __init__( save. Error popups will still be shown. :param duplicate_children: (optional) Default: Set in `Form`. If record has children, prompt user to choose to duplicate current record, or both. - :param validate_fields: Validate fields before saving to database. + :param validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from + being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures + validation occurs before saving to the database. :returns: None """ DataSet.instances.append(self) @@ -677,10 +684,8 @@ def __init__( if duplicate_children is None else bool(duplicate_children) ) - self.validate_fields = ( - self.frm.validate_fields - if validate_fields is None - else bool(validate_fields) + self.validate_mode = ( + self.frm.validate_mode if validate_mode is None else validate_mode ) self._simple_transform: SimpleTransformsDict = {} @@ -1099,7 +1104,7 @@ def value_changed( # db: {table_val}({type(table_val)})" # ) if element_val != table_val: - return new_value + return new_value if new_value is not None else "" return Boolean.FALSE def prompt_save( @@ -1889,7 +1894,7 @@ def save_record( display_message = not self.save_quiet if validate_fields is None: - validate_fields = self.validate_fields + validate_fields = self.validate_mode # Ensure that there is actually something to save if not self.row_count: @@ -3178,7 +3183,7 @@ def __init__( description_column_names: List[str] = None, live_update: bool = False, auto_add_relationships: bool = True, - validate_fields: bool = True, + validate_mode: ValidateMode = ValidateMode.RELAXED, ) -> None: """ Initialize a new `Form` instance. @@ -3217,8 +3222,10 @@ def __init__( :param auto_add_relationships: (optional) Controls the invocation of auto_add_relationships. Default is True. Set it to False when creating a new `Form` with pre-existing `Relationship` instances. - :param validate_fields: Passed to `DataSet` init to validate fields before - saving to database. + :param validate_mode: Passed to `DataSet` init to set validate mode. + `ss.ValidateMode.STRICT` to prevent invalid values from being entered. + `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation + occurs before saving to the database. :returns: None """ win_pb = ProgressBar(lang.startup_form) @@ -3253,7 +3260,7 @@ def __init__( else: self.description_column_names = description_column_names self.live_update: bool = live_update - self.validate_fields: bool = validate_fields + self.validate_mode: ValidateMode = validate_mode # empty variables, just in-case bind() never called self.popup = None @@ -3706,6 +3713,11 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: placeholder=lang.notnull_placeholder, color=themepack.placeholder_color, ) + if ( + isinstance(element, _EnhancedInput) + and col in self[table].column_info.names() + ): + element.add_validate(self[table], col) # Map Selector Element elif element.metadata["type"] == TYPE_SELECTOR: @@ -5609,6 +5621,34 @@ def __setattr__(self, name, value): return super().__setattr__(name, value) +class _StrictInput: + def strict_validate(self, value, action): + if hasattr(self, 'active_placeholder'): + active_placeholder = self.active_placeholder + else: + active_placeholder = None + + if ( + not active_placeholder + and action == "1" + and self.dataset.validate_mode is ValidateMode.STRICT + ) and not self.dataset.validate_field(self.column_name, value): + return False + return True + + def add_validate(self, dataset: DataSet, column_name: str): + self.dataset: DataSet = dataset + self.column_name = column_name + widget = self.widget if isinstance(self, sg.Input) else self + widget["validate"] = "key" + widget["validatecommand"] = ( + widget.register(self.strict_validate), + "%P", + "%d", + ) + +class _TtkStrictInput(ttk.Entry, _StrictInput): + """ Internal Ttk Entry with validate commands """ @dc.dataclass(init=False) class _PlaceholderText(abc.ABC): @@ -5694,13 +5734,13 @@ def update(self, *args, **kwargs): if self.active_placeholder and value not in EMPTY: # Replace the placeholder with the new value - super().update(value=value) self.active_placeholder = False + super().update(value=value) self.Widget.config(fg=self.normal_color, font=self.normal_font) elif value in EMPTY: # If the value is empty, reinsert the placeholder - super().update(value=self.placeholder_text, **kwargs) self.active_placeholder = True + super().update(value=self.placeholder_text, **kwargs) self.Widget.config(fg=self.placeholder_color, font=self.placeholder_font) else: super().update(*args, **kwargs) @@ -5725,7 +5765,7 @@ def delete_placeholder(self): pass -class _EnhancedInput(_PlaceholderText, sg.Input): +class _EnhancedInput(_PlaceholderText, sg.Input, _StrictInput): """ An Input that allows for the display of a placeholder text when empty. """ @@ -5783,15 +5823,15 @@ def disable_placeholder_select(event): self.insert_placeholder() def insert_placeholder(self): + self.active_placeholder = True self.widget.delete(0, "end") self.widget.insert(0, self.placeholder_text) self.widget.config(fg=self.placeholder_color, font=self.placeholder_font) - self.active_placeholder = True def delete_placeholder(self): + self.active_placeholder = False self.widget.delete(0, "end") self.widget.config(fg=self.normal_color, font=self.normal_font) - self.active_placeholder = False class _EnhancedMultiline(_PlaceholderText, sg.Multiline): @@ -6126,7 +6166,7 @@ def minsize(self, e): self.master.minsize(width, height) -class _DatePicker(ttk.Entry): +class _DatePicker(_TtkStrictInput): def __init__(self, master, dataset, column_name, init_date, **kwargs): self.dataset = dataset self.column_name = column_name @@ -7232,7 +7272,7 @@ def edit(self, event): # entry if widget_type == TK_ENTRY: - self.field = ttk.Entry(frame, textvariable=field_var, justify="left") + self.field = _TtkStrictInput(frame, textvariable=field_var, justify="left") expand = True if widget_type == TK_DATEPICKER: @@ -7254,6 +7294,9 @@ def edit(self, event): self.field.bind("", self.combo_configure) expand = True + if isinstance(self.field, _TtkStrictInput): + self.field.add_validate(self.frm[data_key], column) + # bind text to Return (for save), and Escape (for discard) # event is discarded accept_dict = { @@ -7337,7 +7380,7 @@ def accept( dataset = self.frm[data_key] # validate the field - if dataset.validate_fields: + if dataset.validate_mode: widget = ( self.field if themepack.validate_exception_animation is not None @@ -7508,7 +7551,10 @@ def sync(self, widget, widget_type): dataset = self.frm[data_key] # validate the field - if dataset.validate_fields: + if dataset.validate_mode == ValidateMode.RELAXED or ( + not isinstance(e["element"], _EnhancedInput) + and dataset.validate_mode == ValidateMode.STRICT + ): widget = ( e["element"].Widget if themepack.validate_exception_animation is not None From c6c8d8c42602db46a288e179d4f99a6cb59bd620 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:47:44 -0400 Subject: [PATCH 071/121] nit: black fix --- pysimplesql/pysimplesql.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index bea233c1..7dc032b4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5621,9 +5621,10 @@ def __setattr__(self, name, value): return super().__setattr__(name, value) + class _StrictInput: def strict_validate(self, value, action): - if hasattr(self, 'active_placeholder'): + if hasattr(self, "active_placeholder"): active_placeholder = self.active_placeholder else: active_placeholder = None @@ -5647,8 +5648,10 @@ def add_validate(self, dataset: DataSet, column_name: str): "%d", ) + class _TtkStrictInput(ttk.Entry, _StrictInput): - """ Internal Ttk Entry with validate commands """ + """Internal Ttk Entry with validate commands""" + @dc.dataclass(init=False) class _PlaceholderText(abc.ABC): From 20fabfb93572fdcd79dda1a2d9046fa5a587cc89 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 9 Jul 2023 16:11:14 -0400 Subject: [PATCH 072/121] Feat: Better Locale for Column Casting --- pysimplesql/pysimplesql.py | 68 ++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7dc032b4..c2845c72 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -64,6 +64,7 @@ import functools import inspect import itertools +import locale import logging import math import os.path @@ -126,6 +127,11 @@ pd.set_option("display.max_colwidth", 25) # Set the maximum col width to 25 characters pd.set_option("display.precision", 2) # Set the number of decimal places to 2 +# ---------------------------------------------- +# # Set the locale to the user's default setting +# ---------------------------------------------- +locale.setlocale(locale.LC_ALL, "") + # --------------------------- # Types for automatic mapping # --------------------------- @@ -5653,7 +5659,6 @@ class _TtkStrictInput(ttk.Entry, _StrictInput): """Internal Ttk Entry with validate commands""" -@dc.dataclass(init=False) class _PlaceholderText(abc.ABC): """ An abstract class for PySimpleGUI text-entry elements that allows for the display of @@ -5666,8 +5671,7 @@ class _PlaceholderText(abc.ABC): "Left", "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4", "F5","F6","F7","F8","F9","F10","F11","F12", "Delete"} # fmt: on - - binds: dict = dc.field(default_factory=lambda: {}) + binds: dict = dc.field(default_factory=lambda: dict) placeholder_feature_enabled: bool = False normal_color: str = None normal_font: str = None @@ -5922,7 +5926,6 @@ def _on_search_string_change(self, *args): self.insert_placeholder() -@dc.dataclass(init=False) class _AutoCompleteLogic: _completion_list: List[Union[str, ElementRow]] = dc.field( default_factory=lambda: list @@ -7188,9 +7191,9 @@ def edit(self, event): table_element = element.Widget root = table_element.master - # get cell text, coordinates, width and height - text = table_element.item(row, "values")[col_idx] + # get text, coordinates, width and height x, y, width, height = table_element.bbox(row, col_idx) + text = self.frm[data_key][column] # return early due to following conditions: if col_idx == 0: @@ -7221,6 +7224,8 @@ def edit(self, event): ) if combobox_values: + # overwrite text with description from sg.Table + text = table_element.item(row, "values")[col_idx] widget_type = TK_COMBOBOX width = ( width @@ -8069,7 +8074,7 @@ def validate(self, value: Any) -> bool: @dc.dataclass class MinMaxCol(Column): """ - Column subclass representing a value with minimum and maximum constraints. + Base ColumnClass for columns with minimum and maximum constraints. This class extends the functionality of the base `Column` class to include optional validation based on minimum and maximum values. @@ -8104,7 +8109,7 @@ def validate(self, value): @dc.dataclass class LengthCol(Column): """ - Column subclass for length-constrained columns. + Base ColumnClass for length-constrained columns. This class represents a column with length constraints. It inherits from the base `Column` class and adds attributes to store the maximum and minimum length values. @@ -8123,6 +8128,11 @@ def __post_init__(self): int(self.domain_args[0]) if self.domain_args[0] is not None else None ) + def cast(self, value): + if value in EMPTY: + return "" + return super().cast(value) + def validate(self, value): response = super().validate(value) if response.exception: @@ -8137,6 +8147,33 @@ def validate(self, value): return ValidateResponse() +@dc.dataclass +class LocaleCol(Column): + """ + Base ColumnClass that provides Locale functions + + :param negative_symbol: The symbol representing negative values in the locale. + :param currency_symbol: The symbol representing the currency in the locale. + + Example: + col = LocaleCol() + normalized_value = col.strip_locale("$1,000.50") + """ + + negative_symbol: str = locale.localeconv()["negative_sign"] + currency_symbol: str = locale.localeconv()["currency_symbol"] + + def strip_locale(self, value): + if value == self.negative_symbol: + return "0" + value = str(value) + if value == self.currency_symbol: + return "0" + if self.currency_symbol in value: + value = value.replace(self.currency_symbol, "") + return locale.delocalize(value) + + @dc.dataclass class BoolCol(Column): python_type: Type[bool] = dc.field(default=bool, init=False) @@ -8216,7 +8253,7 @@ def cast(self, value): @dc.dataclass -class DecimalCol(MinMaxCol): +class DecimalCol(LocaleCol, MinMaxCol): precision: int = DECIMAL_PRECISION scale: int = DECIMAL_SCALE python_type: Type[Decimal] = dc.field(default=Decimal, init=False) @@ -8248,8 +8285,7 @@ def __post_init__(self): ) def cast(self, value): - if value == "-": - return Decimal(0) + value = self.strip_locale(value) try: decimal_value = Decimal(value) return decimal_value.quantize(Decimal("0." + "0" * self.scale)) @@ -8270,12 +8306,11 @@ def validate(self, value): @dc.dataclass -class FloatCol(LengthCol, MinMaxCol): +class FloatCol(LocaleCol, LengthCol, MinMaxCol): python_type: Type[float] = dc.field(default=float, init=False) def cast(self, value): - if value == "-": - return float(0) + value = self.strip_locale(value) try: return float(value) except ValueError: @@ -8283,7 +8318,7 @@ def cast(self, value): @dc.dataclass -class IntCol(LengthCol, MinMaxCol): +class IntCol(LocaleCol, LengthCol, MinMaxCol): truncate_decimals: bool = False python_type: Type[int] = dc.field(default=int, init=False) @@ -8294,8 +8329,7 @@ def cast(self, value, truncate_decimals: bool = None): else self.truncate_decimals ) value_backup = value - if value in ["-", "", None]: - return None + value = self.strip_locale(value) try: if isinstance(value, int): return value From 3bab9440f366187fab6007e8a55b09dfe6ca2d7b Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:00:59 -0400 Subject: [PATCH 073/121] Feat: Cell Formatting in table-values! Now you can apply formatting that isn't actually in the Rows dataframe, but only shows up in the table! `frm[data_key].column_info[column_name].cell_format_fn = callable that accepts 1 argument --- pysimplesql/pysimplesql.py | 71 +++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index c2845c72..1e13a728 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -294,6 +294,23 @@ class ValidateResponse: rule: str = None +class CellFormatFn: + @staticmethod + def bool_to_checkbox(val): + return ( + themepack.checkbox_true + if checkbox_to_bool(val) + else themepack.checkbox_false + ) + + @staticmethod + def decimal_places(val, decimal_places): + format_string = f"%.{decimal_places}f" + if val not in EMPTY: + return locale.format_string(format_string, val) + return val + + # ------- # CLASSES # ------- @@ -2527,6 +2544,7 @@ def table_values( columns: List[str] = None, mark_unsaved: bool = False, apply_search_filter: bool = False, + apply_cell_format_fn: bool = True, ) -> List[TableRow]: """ Create a values list of `TableRows`s for use in a PySimpleGUI Table element. @@ -2536,7 +2554,9 @@ def table_values( :param mark_unsaved: Place a marker next to virtual records, or records with unsaved changes. :param apply_search_filter: Filter rows to only those columns in - `DataSet.search_order` that contain `Dataself.search_string`. + `DataSet.search_order` that contain `DataSet.search_string`. + :param apply_cell_format_fn: If set, apply() + `DataSet.column_info[col].cell_format_fn` to rows column :returns: A list of `TableRow`s suitable for using with PySimpleGUI Table element values. """ @@ -2587,20 +2607,12 @@ def table_values( # Apply the mask to filter the DataFrame rows = rows[mask_pd] - # transform bool - if themepack.display_bool_as_checkbox: - bool_columns = [ - column - for column in columns - if self.column_info[column] - and self.column_info[column].python_type == bool - ] - for col in bool_columns: - rows[col] = rows[col].apply( - lambda x: themepack.checkbox_true - if checkbox_to_bool(x) - else themepack.checkbox_false - ) + # apply cell format functions + if apply_cell_format_fn: + for column in columns: + if self.column_info[column] and self.column_info[column].cell_format_fn: + fn = self.column_info[column].cell_format_fn + rows[column] = rows[column].apply(fn) # set the pk to the index to use below rows["pk_idx"] = rows[pk_column].copy() @@ -7418,13 +7430,10 @@ def accept( if widget_type == TK_COMBOBOX: new_value = combobox_values[self.field.current()] - # if boolean, set - if widget_type == TK_CHECKBUTTON and themepack.display_bool_as_checkbox: - new_value = ( - themepack.checkbox_true - if checkbox_to_bool(new_value) - else themepack.checkbox_false - ) + # apply cell format fn + if self.frm[data_key].column_info[column].cell_format_fn: + fn = self.frm[data_key].column_info[column].cell_format_fn + new_value = fn(new_value) # update value row with new text values[col_idx] = new_value @@ -8019,6 +8028,7 @@ class Column: python_type: Type[T] = object custom_cast_fn: callable = None custom_validate_fn: callable = None + cell_format_fn: callable = None domain_args: List[str, int] = None def __getitem__(self, key): @@ -8178,6 +8188,10 @@ def strip_locale(self, value): class BoolCol(Column): python_type: Type[bool] = dc.field(default=bool, init=False) + def __post_init__(self): + if themepack.display_bool_as_checkbox: + self.cell_format_fn: callable = CellFormatFn.bool_to_checkbox + def cast(self, value): return checkbox_to_bool(value) @@ -8283,6 +8297,9 @@ def __post_init__(self): f"Unable to set {self.name} column decimal scale to " f"{self.domain_args[1]}" ) + self.cell_format_fn: callable = lambda x: CellFormatFn.decimal_places( + x, self.scale + ) def cast(self, value): value = self.strip_locale(value) @@ -8329,12 +8346,12 @@ def cast(self, value, truncate_decimals: bool = None): else self.truncate_decimals ) value_backup = value - value = self.strip_locale(value) + if isinstance(value, int): + return value + if isinstance(value, ElementRow): + return int(value) try: - if isinstance(value, int): - return value - if isinstance(value, ElementRow): - return int(value) + value = self.strip_locale(value) if type(value) is str: value = float(value) if type(value) is float: From 8962ce28bdb080aa207486ef04f863ce54c33a75 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:30:38 -0400 Subject: [PATCH 074/121] TableHeadings -> TableBuilder (#339) * TableBuilder initial * Refactoring * update examples to work with new TableBuilder --- examples/Flatfile_examples/csv_test.py | 12 +- .../MSAccess_examples/journal_msaccess.py | 10 +- .../MySQL_examples/journal_mysql_docker.py | 10 +- .../journal_postgres_docker.py | 10 +- .../journal_sqlserver_docker.py | 10 +- examples/SQLite_examples/address_book.py | 12 +- examples/SQLite_examples/checkbox_behavior.py | 8 +- examples/SQLite_examples/journal_external.py | 10 +- examples/SQLite_examples/journal_internal.py | 14 +- .../journal_with_data_manipulation.py | 10 +- examples/SQLite_examples/many_to_many.py | 8 +- examples/SQLite_examples/orders.py | 75 +-- examples/SQLite_examples/selectors_demo.py | 10 +- examples/journal_multiple_databases.py | 12 +- examples/orders_multiple_databases.py | 77 +-- pysimplesql/pysimplesql.py | 500 ++++++++++++------ 16 files changed, 496 insertions(+), 292 deletions(-) diff --git a/examples/Flatfile_examples/csv_test.py b/examples/Flatfile_examples/csv_test.py index deb90c5c..84044903 100644 --- a/examples/Flatfile_examples/csv_test.py +++ b/examples/Flatfile_examples/csv_test.py @@ -13,14 +13,14 @@ # Create a simple layout for working with our flatfile data. # Note that you can set a specific table name to use, but here I am just using the defaul 'Flatfile' # Lets also use some sortable headers so that we can rearrange the flatfile data when saving -headings=ss.TableHeadings() -headings.add_column('name', 'Name', width=12) -headings.add_column('address', 'Address', width=25) -headings.add_column('phone', 'Phone #', width=10) -headings.add_column('email', 'EMail', width=25) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('name', 'Name', width=12) +table_builder.add_column('address', 'Address', width=25) +table_builder.add_column('phone', 'Phone #', width=10) +table_builder.add_column('email', 'EMail', width=25) layout = [ - [ss.selector('Flatfile', sg.Table, num_rows=10, headings=headings)], + [ss.selector('Flatfile', table_builder)], [ss.field('Flatfile.name')], [ss.field('Flatfile.address')], [ss.field('Flatfile.phone')], diff --git a/examples/MSAccess_examples/journal_msaccess.py b/examples/MSAccess_examples/journal_msaccess.py index a1bb5093..6c57d9b4 100644 --- a/examples/MSAccess_examples/journal_msaccess.py +++ b/examples/MSAccess_examples/journal_msaccess.py @@ -16,13 +16,13 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings() -headings.add_column("title", "Title", width=40) -headings.add_column("entry_date", "Date", width=10) -headings.add_column("mood_id", "Mood", width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column("title", "Title", width=40) +table_builder.add_column("entry_date", "Date", width=10) +table_builder.add_column("mood_id", "Mood", width=20) layout = [ - [ss.selector("Journal", sg.Table, num_rows=10, headings=headings)], + [ss.selector("Journal", table_builder)], [ss.actions("Journal")], [ ss.field("Journal.entry_date"), diff --git a/examples/MySQL_examples/journal_mysql_docker.py b/examples/MySQL_examples/journal_mysql_docker.py index 198875bb..b2ba1fbb 100644 --- a/examples/MySQL_examples/journal_mysql_docker.py +++ b/examples/MySQL_examples/journal_mysql_docker.py @@ -25,13 +25,13 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings() -headings.add_column("title", "Title", width=40) -headings.add_column("entry_date", "Date", width=10) -headings.add_column("mood_id", "Mood", width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column("title", "Title", width=40) +table_builder.add_column("entry_date", "Date", width=10) +table_builder.add_column("mood_id", "Mood", width=20) layout = [ - [ss.selector("Journal", sg.Table, num_rows=10, headings=headings)], + [ss.selector("Journal", table_builder)], [ss.actions("Journal")], [ ss.field("Journal.entry_date"), diff --git a/examples/PostgreSQL_examples/journal_postgres_docker.py b/examples/PostgreSQL_examples/journal_postgres_docker.py index ee5847b6..7fdb0e5b 100644 --- a/examples/PostgreSQL_examples/journal_postgres_docker.py +++ b/examples/PostgreSQL_examples/journal_postgres_docker.py @@ -25,13 +25,13 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings() -headings.add_column("title", "Title", width=40) -headings.add_column("entry_date", "Date", width=10) -headings.add_column("mood_id", "Mood", width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column("title", "Title", width=40) +table_builder.add_column("entry_date", "Date", width=10) +table_builder.add_column("mood_id", "Mood", width=20) layout = [ - [ss.selector("Journal", sg.Table, num_rows=10, headings=headings)], + [ss.selector("Journal", table_builder)], [ss.actions("Journal")], [ ss.field("Journal.entry_date"), diff --git a/examples/SQLServer_examples/journal_sqlserver_docker.py b/examples/SQLServer_examples/journal_sqlserver_docker.py index 028a4551..67ab7dc0 100644 --- a/examples/SQLServer_examples/journal_sqlserver_docker.py +++ b/examples/SQLServer_examples/journal_sqlserver_docker.py @@ -44,13 +44,13 @@ # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. # This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) -headings.add_column("title", "Title", width=40) -headings.add_column("entry_date", "Date", width=10) -headings.add_column("mood_id", "Mood", width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column("title", "Title", width=40) +table_builder.add_column("entry_date", "Date", width=10) +table_builder.add_column("mood_id", "Mood", width=20) layout = [ - [ss.selector("Journal", sg.Table, num_rows=10, headings=headings)], + [ss.selector("Journal", table_builder)], [ss.actions("Journal")], [ ss.field("Journal.entry_date"), diff --git a/examples/SQLite_examples/address_book.py b/examples/SQLite_examples/address_book.py index 864b88ed..09d91c81 100644 --- a/examples/SQLite_examples/address_book.py +++ b/examples/SQLite_examples/address_book.py @@ -79,14 +79,14 @@ def validate_zip(): # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector. This will allow entries to be sorted by column! -headings = ss.TableHeadings() -headings.add_column('firstName', 'First name:', 15) -headings.add_column('lastName', 'Last name:', 15) -headings.add_column('city', 'City:', 13) -headings.add_column('fkState', 'State:', 5) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('firstName', 'First name:', 15) +table_builder.add_column('lastName', 'Last name:', 15) +table_builder.add_column('city', 'City:', 13) +table_builder.add_column('fkState', 'State:', 5) layout = [ - [ss.selector("Addresses", sg.Table, headings=headings, num_rows=10)], + [ss.selector("Addresses", table_builder, num_rows=10)], [ss.field("Addresses.fkGroupName", sg.Combo, size=(30, 10), auto_size_text=False)], [ss.field("Addresses.firstName", label="First name:")], [ss.field("Addresses.lastName", label="Last name:")], diff --git a/examples/SQLite_examples/checkbox_behavior.py b/examples/SQLite_examples/checkbox_behavior.py index 5d95e9b3..4006940c 100644 --- a/examples/SQLite_examples/checkbox_behavior.py +++ b/examples/SQLite_examples/checkbox_behavior.py @@ -32,15 +32,15 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Create a table heading object -headings = ss.TableHeadings(allow_cell_edits=True) +table_builder = ss.TableBuilder(allow_cell_edits=True) # Add columns to the table heading -headings.add_column('id', 'id', width=5) +table_builder.add_column('id', 'id', width=5) columns = ['bool_none', 'bool_true', 'bool_false', 'int_none', 'int_true', 'int_false', 'text_none', 'text_true', 'text_false'] for col in columns: - headings.add_column(col, col, width=8) + table_builder.add_column(col, col, width=8) fields = [] for col in columns: @@ -50,7 +50,7 @@ [sg.Text('This test shows pysimplesql checkbox behavior.')], [sg.Text('Each column is labeled as type: bool=BOOLEAN, int=INTEGER, text=TEXT')], [sg.Text("And the DEFAULT set for new records, no default set, True,1,'True', or False,0,'False'")], - [ss.selector('checkboxes', sg.Table, num_rows=10, headings=headings, row_height=25)], + [ss.selector('checkboxes', table_builder, row_height=25)], [ss.actions('checkboxes', edit_protect=False)], fields, ] diff --git a/examples/SQLite_examples/journal_external.py b/examples/SQLite_examples/journal_external.py index 5f60125d..b8e7d5fd 100644 --- a/examples/SQLite_examples/journal_external.py +++ b/examples/SQLite_examples/journal_external.py @@ -12,13 +12,13 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector -headings = ss.TableHeadings() -headings.add_column("title", "Title", width=40) -headings.add_column("entry_date", "Date", width=10) -headings.add_column("mood_id", "Mood", width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column("title", "Title", width=40) +table_builder.add_column("entry_date", "Date", width=10) +table_builder.add_column("mood_id", "Mood", width=20) layout = [ - [ss.selector('Journal', sg.Table, key='sel_journal', num_rows=10, headings=headings)], + [ss.selector('Journal', table_builder, key='sel_journal')], [ss.actions('Journal', 'act_journal', edit_protect=False)], [ss.field('Journal.entry_date')], [ss.field('Journal.mood_id', sg.Combo, size=(30, 10), label='My mood:', auto_size_text=False)], diff --git a/examples/SQLite_examples/journal_internal.py b/examples/SQLite_examples/journal_internal.py index cc72db6f..c8f81238 100644 --- a/examples/SQLite_examples/journal_internal.py +++ b/examples/SQLite_examples/journal_internal.py @@ -49,16 +49,18 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. -headings = ss.TableHeadings( +table_builder = ss.TableBuilder( + num_rows = 10, sort_enable=True, # Click a header to sort - allow_cell_edits=True # Double-click a cell to make edits + allow_cell_edits=True, # Double-click a cell to make edits + style=ss.TableStyler(row_height=25) ) -headings.add_column('title', 'Title', width=40) -headings.add_column('entry_date', 'Date', width=10) -headings.add_column('mood_id', 'Mood', width=20) +table_builder.add_column('title', 'Title', width=40) +table_builder.add_column('entry_date', 'Date', width=10) +table_builder.add_column('mood_id', 'Mood', width=20) layout = [ - [ss.selector('Journal', sg.Table, num_rows=10, headings=headings, row_height=25)], + [ss.selector('Journal', table_builder)], [ss.actions('Journal')], [ss.field('Journal.entry_date'), sg.CalendarButton( diff --git a/examples/SQLite_examples/journal_with_data_manipulation.py b/examples/SQLite_examples/journal_with_data_manipulation.py index 63fa6f4c..d14ea8d1 100644 --- a/examples/SQLite_examples/journal_with_data_manipulation.py +++ b/examples/SQLite_examples/journal_with_data_manipulation.py @@ -36,13 +36,13 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. This will also allow sorting! -headings = ss.TableHeadings() -headings.add_column('title', 'Title', width=40) -headings.add_column('entry_date', 'Date', width=10) -headings.add_column('mood_id', 'Mood', width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('title', 'Title', width=40) +table_builder.add_column('entry_date', 'Date', width=10) +table_builder.add_column('mood_id', 'Mood', width=20) layout=[ - [ss.selector('Journal', sg.Table, key='sel_journal', num_rows=10, headings=headings)], + [ss.selector('Journal', table_builder, key='sel_journal')], [ss.actions('Journal', 'act_journal', edit_protect=False)], [ss.field('Journal.entry_date')], [ss.field('Journal.mood_id', sg.Combo, size=(30, 10), auto_size_text=False)], diff --git a/examples/SQLite_examples/many_to_many.py b/examples/SQLite_examples/many_to_many.py index d83c5946..62d4e9a1 100644 --- a/examples/SQLite_examples/many_to_many.py +++ b/examples/SQLite_examples/many_to_many.py @@ -60,11 +60,11 @@ [ss.field('Color.name', label_above=True)] ] -headings = ss.TableHeadings(sort_enable=True) -headings.add_column('person_id', 'Person', 18) -headings.add_column('color_id', 'Favorite Color', 18) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('person_id', 'Person', 18) +table_builder.add_column('color_id', 'Favorite Color', 18) favorites_layout = [ - [ss.selector('FavoriteColor', sg.Table, key='sel_favorite', num_rows=10, headings=headings)], + [ss.selector('FavoriteColor', table_builder, key='sel_favorite')], [ss.actions('act_favorites', 'FavoriteColor', edit_protect=False, search=False)], [ss.field('FavoriteColor.person_id', element=sg.Combo, size=(30, 10), label='Person:', auto_size_text=False)], [ss.field('FavoriteColor.color_id', element=sg.Combo, size=(30, 10), label='Color:', auto_size_text=False)] diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 9f8e78c3..82389daf 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -29,8 +29,8 @@ # ----------------------------- custom = { "ttk_theme": os_ttktheme, - "marker_sort_asc": " ⬇", - "marker_sort_desc": " ⬆", + "marker_sort_asc": " ⬇ ", + "marker_sort_desc": " ⬆ ", } custom = custom | os_tp ss.themepack(custom) @@ -162,23 +162,38 @@ # fmt: on layout = [[sg.Menu(menu_def, key="-MENUBAR-", font="_ 12")]] -# Define the columns for the table selector using the TableHeading class. -order_heading = ss.TableHeadings( - sort_enable=True, # Click a heading to sort +# Set our universal table options +table_style = ss.TableStyler( + row_height=25, + expand_x=True, + expand_y=True, + frame_pack_kwargs={"expand": True, "fill": "both"}, +) + +# Define the columns for the table selector using the Tabletable class. +order_table = ss.TableBuilder( + num_rows=5, + sort_enable=True, # Click a table to sort allow_cell_edits=True, # Double-click a cell to make edits. # Exempted: Primary Key columns, Generated columns, and columns set as readonly - add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() apply_search_filter=True, # Filter rows as you type in the search input + lazy_loading=True, # For larger DataSets, inserts slice of rows. See `LazyTable` + add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() + style=table_style, ) # Add columns -order_heading.add_column(column="order_id", heading_column="ID", width=5) -order_heading.add_column("customer_id", "Customer", 30) -order_heading.add_column("date", "Date", 20) -order_heading.add_column( - "total", "total", width=10, readonly=True -) # set to True to disable editing for individual columns!) -order_heading.add_column("completed", "✔", 8) +order_table.add_column(column="order_id", heading="ID", width=5) +order_table.add_column("customer_id", "Customer", 30) +order_table.add_column("date", "Date", 20) +order_table.add_column( + column="total", + heading="Total", + width=10, + readonly=True, # set to True to disable editing for individual columns! + col_justify="right", # default, "left". Available: "left", "right", "center" +) +order_table.add_column("completed", "✔", 8) # Layout layout.append( @@ -187,10 +202,7 @@ [ ss.selector( "orders", - sg.Table, - num_rows=5, - headings=order_heading, - row_height=25, + order_table, ) ], [ss.actions("orders")], @@ -198,14 +210,18 @@ ] ) -# order_details TableHeadings: -details_heading = ss.TableHeadings( - sort_enable=True, allow_cell_edits=True, add_save_heading_button=True +# order_details TableBuilder: +details_table = ss.TableBuilder( + num_rows=10, + sort_enable=True, + allow_cell_edits=True, + add_save_heading_button=True, + style=table_style, ) -details_heading.add_column("product_id", "Product", 30) -details_heading.add_column("quantity", "quantity", 10) -details_heading.add_column("price", "price/Ea", 10, readonly=True) -details_heading.add_column("subtotal", "subtotal", 10) +details_table.add_column("product_id", "Product", 30) +details_table.add_column("quantity", "Quantity", 10, col_justify="right") +details_table.add_column("price", "Price/Ea", 10, readonly=True, col_justify="right") +details_table.add_column("subtotal", "Subtotal", 10, readonly=True, col_justify="right") orderdetails_layout = [ [sg.Sizer(h_pixels=0, v_pixels=10)], @@ -217,10 +233,7 @@ [ ss.selector( "order_details", - sg.Table, - num_rows=10, - headings=details_heading, - row_height=25, + details_table, ) ], [ss.actions("order_details", default=False, save=True, insert=True, delete=True)], @@ -244,12 +257,6 @@ icon=ss.themepack.icon, ) -# Expand our sg.Tables so they fill the screen -win["orders:selector"].expand(True, True) -win["orders:selector"].table_frame.pack(expand=True, fill="both") -win["order_details:selector"].expand(True, True) -win["order_details:selector"].table_frame.pack(expand=True, fill="both") - # Init pysimplesql Driver and Form # -------------------------------- diff --git a/examples/SQLite_examples/selectors_demo.py b/examples/SQLite_examples/selectors_demo.py index 78a4bb38..20de92fb 100644 --- a/examples/SQLite_examples/selectors_demo.py +++ b/examples/SQLite_examples/selectors_demo.py @@ -35,10 +35,10 @@ """ # PySimpleGUI™ layout code -headings = ss.TableHeadings() -headings.add_column('name', 'Name', width=10) -headings.add_column('example', 'Example', width=40) -headings.add_column('primary_color', 'Primary Color?', width=15) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('name', 'Name', width=10) +table_builder.add_column('example', 'Example', width=40) +table_builder.add_column('primary_color', 'Primary Color?', width=15) record_columns = [ [ss.field('Colors.name', label='Color name:')], @@ -46,7 +46,7 @@ [ss.field('Colors.primary_color', element=sg.CBox, label='Primary Color?')], ] selectors = [ - [ss.selector('Colors', element=sg.Table, key='tableSelector', headings=headings, num_rows=10)], + [ss.selector('Colors', element=table_builder, key='tableSelector')], [ss.selector('Colors', size=(15, 10), key='selector1')], [ss.selector('Colors', element=sg.Slider, size=(26, 18), key='selector2'), ss.selector('Colors', element=sg.Combo, size=(30, 10), key='selector3')], diff --git a/examples/journal_multiple_databases.py b/examples/journal_multiple_databases.py index 933ba4d9..a5c87846 100644 --- a/examples/journal_multiple_databases.py +++ b/examples/journal_multiple_databases.py @@ -27,14 +27,14 @@ # CREATE PYSIMPLEGUI LAYOUT # ------------------------- # Define the columns for the table selector using the TableHeading convenience class. This will also allow sorting! -headings = ss.TableHeadings(sort_enable=True) -headings.add_column('title', 'Title', width=40) -headings.add_column('entry_date', 'Date', width=10) -headings.add_column('mood_id', 'Mood', width=20) +table_builder = ss.TableBuilder(num_rows=10) +table_builder.add_column('title', 'Title', width=40) +table_builder.add_column('entry_date', 'Date', width=10) +table_builder.add_column('mood_id', 'Mood', width=20) layout = [ [sg.Text('Selected driver: '), sg.Text('', key='driver')], - [ss.selector('Journal', sg.Table, num_rows=10, headings=headings)], + [ss.selector('Journal', table_builder)], [ss.actions('Journal')], [ss.field('Journal.entry_date'), sg.CalendarButton("Select Date", close_when_date_chosen=True, target="Journal.entry_date", # <- target matches field() name @@ -104,5 +104,5 @@ - using Form.field() and Form.selector() functions for easy GUI element creation - using the label keyword argument to Form.record() to define a custom label - using Tables as Form.selector() element types -- Using the TableHeadings() function to define sortable table headings +- Using the TableBuilder() function to define sortable table headings """ diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 850add4d..0451cb60 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -33,8 +33,8 @@ # ----------------------------- custom = { "ttk_theme": os_ttktheme, - "marker_sort_asc": " ⬇", - "marker_sort_desc": " ⬆", + "marker_sort_asc": " ⬇ ", + "marker_sort_desc": " ⬆ ", } custom = custom | os_tp ss.themepack(custom) @@ -216,7 +216,7 @@ def is_valid_email(email): """ # Generate random orders using pandas DataFrame -num_orders = 100 +num_orders = 1000 rng = np.random.default_rng() orders_df = pd.DataFrame( { @@ -389,23 +389,38 @@ def is_valid_email(email): # fmt: on layout = [[sg.Menu(menu_def, key="-MENUBAR-", font="_ 12")]] -# Define the columns for the table selector using the TableHeading class. -order_heading = ss.TableHeadings( - sort_enable=True, # Click a heading to sort +# Set our universal table options +table_style = ss.TableStyler( + row_height=25, + expand_x=True, + expand_y=True, + frame_pack_kwargs={"expand": True, "fill": "both"}, +) + +# Define the columns for the table selector using the Tabletable class. +order_table = ss.TableBuilder( + num_rows=5, + sort_enable=True, # Click a table to sort allow_cell_edits=True, # Double-click a cell to make edits. # Exempted: Primary Key columns, Generated columns, and columns set as readonly - add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() apply_search_filter=True, # Filter rows as you type in the search input + lazy_loading=True, # For larger DataSets, inserts slice of rows. See `LazyTable` + add_save_heading_button=True, # Click 💾 in sg.Table Heading to trigger DataSet.save_record() + style=table_style, ) # Add columns -order_heading.add_column(column="order_id", heading_column="ID", width=5) -order_heading.add_column("customer_id", "Customer", 30) -order_heading.add_column("date", "Date", 20) -order_heading.add_column( - "total", "total", width=10, readonly=True -) # set to True to disable editing for individual columns!) -order_heading.add_column("completed", "✔", 8) +order_table.add_column(column="order_id", heading="ID", width=5) +order_table.add_column("customer_id", "Customer", 30) +order_table.add_column("date", "Date", 20) +order_table.add_column( + column="total", + heading="Total", + width=10, + readonly=True, # set to True to disable editing for individual columns! + col_justify="right", # default, "left". Available: "left", "right", "center" +) +order_table.add_column("completed", "✔", 8) # Layout layout.append( @@ -414,10 +429,7 @@ def is_valid_email(email): [ ss.selector( "orders", - sg.Table, - num_rows=5, - headings=order_heading, - row_height=25, + order_table, ) ], [ss.actions("orders")], @@ -425,14 +437,18 @@ def is_valid_email(email): ] ) -# order_details TableHeadings: -details_heading = ss.TableHeadings( - sort_enable=True, allow_cell_edits=True, add_save_heading_button=True +# order_details TableBuilder: +details_table = ss.TableBuilder( + num_rows=10, + sort_enable=True, + allow_cell_edits=True, + add_save_heading_button=True, + style=table_style, ) -details_heading.add_column("product_id", "Product", 30) -details_heading.add_column("quantity", "quantity", 10) -details_heading.add_column("price", "price/Ea", 10, readonly=True) -details_heading.add_column("subtotal", "subtotal", 10, readonly=True) +details_table.add_column("product_id", "Product", 30) +details_table.add_column("quantity", "Quantity", 10, col_justify="right") +details_table.add_column("price", "Price/Ea", 10, readonly=True, col_justify="right") +details_table.add_column("subtotal", "Subtotal", 10, readonly=True, col_justify="right") orderdetails_layout = [ [sg.Sizer(h_pixels=0, v_pixels=10)], @@ -451,10 +467,7 @@ def is_valid_email(email): [ ss.selector( "order_details", - sg.Table, - num_rows=10, - headings=details_heading, - row_height=25, + details_table, ) ], [ss.actions("order_details", default=False, save=True, insert=True, delete=True)], @@ -478,12 +491,6 @@ def is_valid_email(email): icon=ss.themepack.icon, ) -# Expand our sg.Tables so they fill the screen -win["orders:selector"].expand(True, True) -win["orders:selector"].table_frame.pack(expand=True, fill="both") -win["order_details:selector"].expand(True, True) -win["order_details:selector"].table_frame.pack(expand=True, fill="both") - # Init pysimplesql Driver and Form # -------------------------------- if database == "sqlite": diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1e13a728..8251550a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -82,6 +82,7 @@ ClassVar, Dict, List, + Literal, Optional, Tuple, Type, @@ -228,6 +229,11 @@ TK_CHECKBUTTON = "Checkbutton" TK_DATEPICKER = "Datepicker" TK_COMBOBOX_SELECTED = "35" +TK_ANCHOR_MAP = { + "l": "w", + "r": "e", + "c": "center", +} # -------------- # Misc Constants @@ -236,6 +242,9 @@ EMPTY = ["", None] DECIMAL_PRECISION = 12 DECIMAL_SCALE = 2 +TableJustify = Literal["left", "right", "center"] +ColumnJustify = Literal["left", "right", "center", "default"] +HeadingJustify = Literal["left", "right", "center", "column", "default"] # -------------------- # Date formats @@ -2647,13 +2656,13 @@ def column_likely_in_selector(self, column: str) -> bool: return False # If table headings are not used, assume the column is displayed, return True - if not any("TableHeading" in e["element"].metadata for e in self.selector): + if not any("TableBuilder" in e["element"].metadata for e in self.selector): return True # Otherwise, Return True/False if the column is in the list of table headings return any( - "TableHeading" in e["element"].metadata - and column in e["element"].metadata["TableHeading"].columns() + "TableBuilder" in e["element"].metadata + and column in e["element"].metadata["TableBuilder"].columns for e in self.selector ) @@ -2787,8 +2796,12 @@ def quick_editor( keygen.reset() data_key = self.key layout = [] - headings = TableHeadings( - sort_enable=True, allow_cell_edits=True, add_save_heading_button=True + table_builder = TableBuilder( + num_rows=10, + sort_enable=True, + allow_cell_edits=True, + add_save_heading_button=True, + style=TableStyler(row_height=25), ) for col in self.column_info.names(): @@ -2797,17 +2810,25 @@ def quick_editor( if col == self.pk_column: # make pk column either max length of contained pks, or len of name width = max(self.rows[col].astype(str).map(len).max(), len(col) + 1) - headings.add_column(col, col.capitalize(), width=width) + justify = "left" + elif self.column_info[col] and self.column_info[col].python_type in [ + int, + float, + Decimal, + ]: + justify = "right" + else: + justify = "left" + table_builder.add_column( + col, col.capitalize(), width=width, col_justify=justify + ) layout.append( [ selector( data_key, - sg.Table, + table_builder, key=f"{data_key}:quick_editor", - num_rows=10, - row_height=25, - headings=headings, ) ] ) @@ -3097,10 +3118,10 @@ def update_headings(self, column, sort_order): for e in self.selector: element = e["element"] if ( - "TableHeading" in element.metadata - and element.metadata["TableHeading"].sort_enable + "TableBuilder" in element.metadata + and element.metadata["TableBuilder"].sort_enable ): - element.metadata["TableHeading"].update_headings( + element.metadata["TableBuilder"].update_headings( element, column, sort_order ) @@ -3757,19 +3778,19 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: element, data_key, where_column, where_value ) - # Enable sorting if TableHeading is present + # Enable sorting if TableBuilder is present if ( isinstance(element, sg.Table) - and "TableHeading" in element.metadata + and "TableBuilder" in element.metadata ): - table_heading: TableHeadings = element.metadata["TableHeading"] + table_builder: TableBuilder = element.metadata["TableBuilder"] # We need a whole chain of things to happen # when a heading is clicked on: # 1 Run the ResultRow.sort_cycle() with the correct column name - # 2 Run TableHeading.update_headings() with the: + # 2 Run TableBuilder.update_headings() with the: # Table element, sort_column, sort_reverse # 3 Run update_elements() to see the changes - table_heading.enable_heading_function( + table_builder.enable_heading_function( element, _HeadingCallback(self, data_key), ) @@ -4523,9 +4544,9 @@ def update_selectors( # Populate entries apply_search_filter = False try: - columns = element.metadata["TableHeading"].columns() + columns = element.metadata["TableBuilder"].columns apply_search_filter = element.metadata[ - "TableHeading" + "TableBuilder" ].apply_search_filter except KeyError: columns = None # default to all columns @@ -5410,14 +5431,20 @@ class LazyTable(sg.Table): support the `sg.Table` `row_colors` argument. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, lazy_loading=False, **kwargs): + # remove LazyTable only + self.headings_justification = kwargs.pop("headings_justification", None) + cols_justification = kwargs.pop("cols_justification", None) + self.frame_pack_kwargs = kwargs.pop("frame_pack_kwargs", None) + super().__init__(*args, **kwargs) - self.values = [] # noqa PD011 # full set of rows - self.data = [] # lazy slice of rows - self.Values = self.data - self.insert_qty = max(self.NumRows, 100) - """Number of rows to insert during an `update(values=)` and scroll events""" + # set cols_justification after, since PySimpleGUI sets it in its init + self.cols_justification = cols_justification + + self.data = [] # lazy slice of rows + self.lazy_loading: bool = True + self.lazy_insert_qty: int = 100 self._start_index = 0 self._end_index = 0 @@ -5428,6 +5455,45 @@ def __init__(self, *args, **kwargs): self._bg = None self._fg = None + def __setattr__(self, name, value): + if name == "SelectedRows": + # Handle PySimpleGui attempts to set our SelectedRows property + return + super().__setattr__(name, value) + + @property + def insert_qty(self): + """Number of rows to insert during an `update(values=)` and scroll events""" + if self.lazy_loading: + return max(self.NumRows, self.lazy_insert_qty) + return len(self.Values) + + @property + def SelectedRows(self): # noqa N802 + """ + Returns the selected row(s) in the LazyTable. + + :returns: + - If the LazyTable has data: + - Retrieves the index of the selected row by matching the primary key + (pk) value with the first selected item in the widget. + - Returns the corresponding row from the data list based on the index. + - If the LazyTable has no data: + - Returns None. + + :note: + This property assumes that the LazyTable is using a primary key (pk) value + to uniquely identify rows in the data list. + """ + if self.data and self.widget.selection(): + index = [ + [v.pk for v in self.data].index( + [int(x) for x in self.widget.selection()][0] + ) + ][0] + return self.data[index] + return None + def update( self, values=None, @@ -5438,12 +5504,10 @@ def update( ): # check if we shouldn't be doing this update # PySimpleGUI version support (PyPi version doesn't support quick_check) - if sg.__version__.split(".")[0] == "5" or ( - sg.__version__.split(".")[0] == "4" and sg.__version__.split(".")[1] == "61" - ): + kwargs = {} + is_closed_sig = inspect.signature(self.ParentForm.is_closed) + if "quick_check" in is_closed_sig.parameters: kwargs = {"quick_check": True} - else: - kwargs = {} if not self._widget_was_created() or ( self.ParentForm is not None and self.ParentForm.is_closed(**kwargs) @@ -5458,6 +5522,7 @@ def update( # needed, since PySimpleGUI doesn't create tk widgets during class init if not self._finalized: self.widget.configure(yscrollcommand=self._handle_scroll) + self._handle_extra_kwargs() self._finalized = True # delete all current @@ -5607,37 +5672,15 @@ def _set_colors(self, iid, toggle_color): self.widget.tag_configure(iid, background=self._bg, foreground=self._fg) return toggle_color - @property - def SelectedRows(self): # noqa N802 - """ - Returns the selected row(s) in the LazyTable. - - :returns: - - If the LazyTable has data: - - Retrieves the index of the selected row by matching the primary key - (pk) value with the first selected item in the widget. - - Returns the corresponding row from the data list based on the index. - - If the LazyTable has no data: - - Returns None. - - :note: - This property assumes that the LazyTable is using a primary key (pk) value - to uniquely identify rows in the data list. - """ - if self.data and self.widget.selection(): - index = [ - [v.pk for v in self.data].index( - [int(x) for x in self.widget.selection()][0] - ) - ][0] - return self.data[index] - return None - - def __setattr__(self, name, value): - if name == "SelectedRows": - # Handle PySimpleGui attempts to set our SelectedRows property - return - super().__setattr__(name, value) + def _handle_extra_kwargs(self): + if self.headings_justification: + for i, heading_id in enumerate(self.Widget["columns"]): + self.Widget.heading(heading_id, anchor=self.headings_justification[i]) + if self.cols_justification: + for i, column_id in enumerate(self.Widget["columns"]): + self.Widget.column(column_id, anchor=self.cols_justification[i]) + if self.frame_pack_kwargs: + self.table_frame.pack(**self.frame_pack_kwargs) class _StrictInput: @@ -5939,10 +5982,8 @@ def _on_search_string_change(self, *args): class _AutoCompleteLogic: - _completion_list: List[Union[str, ElementRow]] = dc.field( - default_factory=lambda: list - ) - _hits: List[int] = dc.field(default_factory=lambda: list) + _completion_list: List[Union[str, ElementRow]] = dc.field(default_factory=list) + _hits: List[int] = dc.field(default_factory=list) _hit_index: int = 0 position: int = 0 finalized: bool = False @@ -6239,7 +6280,7 @@ class Convenience: building PySimpleGUI layouts that conform to pysimplesql standards so that your database application is up and running quickly, and with all the great automatic functionality pysimplesql has to offer. See the documentation for the following - convenience functions: `field()`, `selector()`, `actions()`, `TableHeadings`. + convenience functions: `field()`, `selector()`, `actions()`, `TableBuilder`. Note: This is a dummy class that exists purely to enhance documentation and has no use to the end user. @@ -6248,7 +6289,12 @@ class Convenience: def field( field: str, - element: Type[sg.Element] = _EnhancedInput, + element: Union[ + Type[sg.Checkbox], + Type[sg.Combo], + Type[sg.Input], + Type[sg.Multiline], + ] = _EnhancedInput, size: Tuple[int, int] = None, label: str = "", no_label: bool = False, @@ -6848,7 +6894,14 @@ def actions( def selector( table: str, - element: Type[sg.Element] = sg.LBox, + element: Union[ + Type[sg.Combo], + Type[LazyTable], + Type[sg.Listbox], + Type[sg.Slider], + Type[sg.Table], + TableBuilder, + ] = sg.Listbox, size: Tuple[int, int] = None, filter: str = None, key: str = None, @@ -6861,18 +6914,18 @@ def selector( convenience function makes creating selectors very quick and as easy as using a normal PySimpleGUI element. - :param table: The table name in the database that this selector will act on + :param table: The table name that this selector will act on. :param element: The type of element you would like to use as a selector (defaults to a Listbox) :param size: The desired size of this selector element :param filter: Can be used to reference different `Form`s in the same layout. Use a matching filter when creating the `Form` with the filter parameter. :param key: (optional) The key to give to this selector. If no key is provided, it - will default to table:selector using the table specified in the table parameter. + will default to table:selector using the name specified in the table parameter. This is also passed through the keygen, so if selectors all use the default name, they will be made unique. ie: Journal:selector!1, Journal:selector!2, etc. :param kwargs: Any additional arguments supplied will be passed on to the - PySimpleGUI element. + PySimpleGUI element. Note: TableBuilder objects bring their own kwargs. """ element = _AutocompleteCombo if element == sg.Combo else element @@ -6909,22 +6962,18 @@ def selector( metadata=meta, ) elif element in [sg.Table, LazyTable]: - # Check if the headings arg is a Table heading... - if isinstance(kwargs["headings"], TableHeadings): - # Overwrite the kwargs from the TableHeading info - kwargs["visible_column_map"] = kwargs["headings"].visible_map() - kwargs["col_widths"] = kwargs["headings"].width_map() - kwargs["auto_size_columns"] = False # let the col_widths handle it - # Store the TableHeadings object in metadata - # to complete setup on auto_add_elements() - meta["TableHeading"] = kwargs["headings"] - else: - required_kwargs = ["headings", "visible_column_map", "num_rows"] - for kwarg in required_kwargs: - if kwarg not in kwargs: - raise RuntimeError( - f"DataSet selectors must use the {kwarg} keyword argument." - ) + required_kwargs = ["headings", "visible_column_map", "num_rows"] + for kwarg in required_kwargs: + if kwarg not in kwargs: + raise RuntimeError( + f"DataSet selectors must use the {kwarg} keyword argument." + ) + # Create a narrow column for displaying a * character for virtual rows. + # This will be the 1st column + kwargs["headings"].insert(0, "") + kwargs["visible_column_map"].insert(0, 1) + if "col_widths" in kwargs: + kwargs["col_widths"].insert(0, themepack.unsaved_column_width) # Create other kwargs that are required kwargs["enable_events"] = True @@ -6933,105 +6982,201 @@ def selector( # Make an empty list of values vals = [[""] * len(kwargs["headings"])] - - # Create a narrow column for displaying a * character for virtual rows. - # This will be the 1st column - kwargs["visible_column_map"].insert(0, 1) - if "col_widths" in kwargs: - kwargs["col_widths"].insert(0, themepack.unsaved_column_width) - - # Change the headings parameter to be a list so - # the heading doesn't display dicts when it first loads - # The TableHeadings instance is already stored in metadata - if isinstance(kwargs["headings"], TableHeadings): - if kwargs["headings"].add_save_heading_button: - kwargs["headings"].insert(0, themepack.unsaved_column_header) - else: - kwargs["headings"].insert(0, "") - kwargs["headings"] = kwargs["headings"].heading_names() - else: - kwargs["headings"].insert(0, "") - layout = element(values=vals, key=key, metadata=meta, **kwargs) + elif isinstance(element, TableBuilder): + table_builder = element + element = table_builder.element + lazy = table_builder.lazy_loading + kwargs = table_builder.get_table_kwargs() + + meta["TableBuilder"] = table_builder + # Make an empty list of values + vals = [[""] * len(kwargs["headings"])] + layout = element( + vals, + lazy_loading=lazy, + key=key, + metadata=meta, + **kwargs, + ) else: raise RuntimeError(f'Element type "{element}" not supported as a selector.') return layout -class TableHeadings(list): +@dc.dataclass +class TableStyler: + # pysimplesql specific + frame_pack_kwargs: Dict[str] = dc.field(default_factory=dict) + + # PySimpleGUI Table kwargs that are compatible with pysimplesql + justification: TableJustify = "left" + row_height: int = None + font: str or Tuple[str, int] or None = None + text_color: str = None + background_color: str = None + alternating_row_color: str = None + selected_row_colors: Tuple[str, str] = (None, None) + header_text_color: str = None + header_background_color: str = None + header_font: str or Tuple[str, int] or None = None + header_border_width: int = None + header_relief: str = None + vertical_scroll_only: bool = True + hide_vertical_scroll: bool = False + border_width: int = None + sbar_trough_color: str = None + sbar_background_color: str = None + sbar_arrow_color: str = None + sbar_width: int = None + sbar_arrow_width: int = None + sbar_frame_color: str = None + sbar_relief: str = None + pad: Union[int, Tuple[int, int], Tuple[Tuple[int, int], Tuple[int, int]]] = None + tooltip: str = None + right_click_menu: List[Union[List[str], str]] = None + expand_x: bool = False + expand_y: bool = False + visible: bool = True + + def __repr__(self): + attrs = self.get_table_kwargs() + return f"TableStyler({attrs})" + + def get_table_kwargs(self): + non_default_attributes = {} + for field in dc.fields(self): + if ( + getattr(self, field.name) != field.default + and getattr(self, field.name) + and field.name not in [] + ): + non_default_attributes[field.name] = getattr(self, field.name) + return non_default_attributes + + +@dc.dataclass +class TableBuilder(list): """ This is a convenience class used to build table headings for PySimpleGUI. - In addition, `TableHeading` objects can sort columns in ascending or descending + In addition, `TableBuilder` objects can sort columns in ascending or descending order by clicking on the column in the heading in the PySimpleGUI Table element if the sort_enable parameter is set to True. + + :param sort_enable: True to enable sorting by heading column. + :param allow_cell_edits: Double-click to edit a cell value if True. Accepted + edits update both `sg.Table` and associated `field` element. Note: primary + key, generated, or `readonly` columns don't allow cell edits. + :param lazy_loading: For larger DataSets (see `LazyTable`). + :param add_save_heading_button: Adds a save button to the left-most heading + column if True. + :param apply_search_filter: Filter rows to only those columns in + `DataSet.search_order` that contain `Dataself.search_string`. + :returns: None """ # store our instances - instances: ClassVar[List[TableHeadings]] = [] - - def __init__( - self, - sort_enable: bool = True, - allow_cell_edits: bool = False, - add_save_heading_button: bool = False, - apply_search_filter: bool = False, - ) -> None: - """ - Create a new TableHeadings object. - - :param sort_enable: True to enable sorting by heading column. - :param allow_cell_edits: Double-click to edit a cell value if True. Accepted - edits update both `sg.Table` and associated `field` element. Note: primary - key, generated, or `readonly` columns don't allow cell edits. - :param add_save_heading_button: Adds a save button to the left-most heading - column if True. - :param apply_search_filter: Filter rows to only those columns in - `DataSet.search_order` that contain `Dataself.search_string`. - :returns: None - """ - self.sort_enable = sort_enable - self.allow_cell_edits = allow_cell_edits - self.add_save_heading_button = add_save_heading_button - self.apply_search_filter = apply_search_filter - self._width_map = [] - self._visible_map = [] - self.readonly_columns = [] + instances: ClassVar[List[TableBuilder]] = [] + + num_rows: int + sort_enable: bool = True + allow_cell_edits: bool = False + apply_search_filter: bool = False + lazy_loading: bool = False + add_save_heading_button: bool = False + style: TableStyler = dc.field(default_factory=TableStyler) + + _width_map: List[int] = dc.field(default_factory=list, init=False) + _col_justify_map: List[int] = dc.field(default_factory=list, init=False) + _heading_justify_map: List[int] = dc.field(default_factory=list, init=False) + _visible_map: List[bool] = dc.field(default_factory=list, init=False) + readonly_columns: List[str] = dc.field(default_factory=list, init=False) + def __post_init__(self): # Store this instance in the master list of instances - TableHeadings.instances.append(self) + TableBuilder.instances.append(self) + + if self.add_save_heading_button: + self.insert(0, themepack.unsaved_column_header) + else: + self.insert(0, "") def add_column( self, column: str, - heading_column: str, + heading: str, width: int, + col_justify: ColumnJustify = "default", + heading_justify: HeadingJustify = "column", visible: bool = True, readonly: bool = False, ) -> None: """ - Add a new heading column to this TableHeading object. Columns are added in the + Add a new heading column to this TableBuilder object. Columns are added in the order that this method is called. Note that the primary key column does not need to be included, as primary keys are stored internally in the `TableRow` class. - :param heading_column: The name of this columns heading (title) - :param column: The name of the column in the database the heading column is for + :param column: The name of the column in the database + :param heading: The name of this columns heading (title) :param width: The width for this column to display within the Table element + :param col_justify: Default 'left'. Available options: 'left', 'right', + 'center', 'default'. + :param heading_justify: Defaults to 'column' to match col_justify. Available + options: 'left', 'right', 'center', 'column', 'default'. :param visible: True if the column is visible. Typically, the only hidden column would be the primary key column if any. This is also useful if the `DataSet.rows` DataFrame has information that you don't want to display. :param readonly: Indicates if the column is read-only when - `TableHeading.allow_cell_edits` is True. + `TableBuilder.allow_cell_edits` is True. :returns: None """ - self.append({"heading": heading_column, "column": column}) + self.append({"heading": heading, "column": column}) self._width_map.append(width) + + # column justify + if col_justify == "default": + col_justify = self.style.justification + self._col_justify_map.append(col_justify) + + # heading justify + if heading_justify == "column": + heading_justify = col_justify + if heading_justify == "default": + heading_justify = self.style.justification + self._heading_justify_map.append(heading_justify) + self._visible_map.append(visible) if readonly: self.readonly_columns.append(column) + def get_table_kwargs(self) -> Dict[str]: + kwargs = {} + + kwargs["num_rows"] = self.num_rows + kwargs["headings_justification"] = self.heading_justify_map + kwargs["cols_justification"] = self.col_justify_map + kwargs["headings"] = self.heading_names + kwargs["visible_column_map"] = self.visible_map + kwargs["col_widths"] = self.width_map + kwargs["auto_size_columns"] = False + kwargs["enable_events"] = True + kwargs["select_mode"] = sg.TABLE_SELECT_MODE_BROWSE + + # Create a narrow column for displaying a * character for virtual rows. + # This will be the 1st column + kwargs["visible_column_map"].insert(0, 1) + kwargs["col_widths"].insert(0, themepack.unsaved_column_width) + + return kwargs | self.style.get_table_kwargs() + + @property + def element(self) -> Type[LazyTable]: + return LazyTable + + @property def heading_names(self) -> List[str]: """ Return a list of heading_names for use with the headings parameter of @@ -7039,8 +7184,14 @@ def heading_names(self) -> List[str]: :returns: a list of heading names """ + headings = [c["heading"] for c in self] + if self.add_save_heading_button: + headings.insert(0, themepack.unsaved_column_header) + else: + headings.insert(0, "") return [c["heading"] for c in self] + @property def columns(self): """ Return a list of column names. @@ -7049,6 +7200,35 @@ def columns(self): """ return [c["column"] for c in self if c["column"] is not None] + @property + def col_justify_map(self) -> List[str]: + """ + Convenience method for creating PySimpleGUI tables. + + :returns: a list column justifications for use with PySimpleGUI Table + cols_justification parameter + """ + justify = [ + TK_ANCHOR_MAP[justify[0].lower()] for justify in self._col_justify_map + ] + justify.insert(0, "w") + return justify + + @property + def heading_justify_map(self) -> List[str]: + """ + Convenience method for creating PySimpleGUI tables. + + :returns: a list column justifications for use with PySimpleGUI Table + cols_justification parameter + """ + justify = [ + TK_ANCHOR_MAP[justify[0].lower()] for justify in self._heading_justify_map + ] + justify.insert(0, "w") + return justify + + @property def visible_map(self) -> List[Union[bool, int]]: """ Convenience method for creating PySimpleGUI tables. @@ -7058,6 +7238,7 @@ def visible_map(self) -> List[Union[bool, int]]: """ return list(self._visible_map) + @property def width_map(self) -> List[int]: """ Convenience method for creating PySimpleGUI tables. @@ -7092,6 +7273,7 @@ def update_headings( desc = "\u25B2" for i, x in zip(range(len(self)), self): + anchor = self.heading_justify_map[i] # Clear the direction markers x["heading"] = x["heading"].replace(asc, "").replace(desc, "") if ( @@ -7099,8 +7281,14 @@ def update_headings( and sort_column is not None and sort_order != SORT_NONE ): - x["heading"] += asc if sort_order == SORT_ASC else desc - element.Widget.heading(i, text=x["heading"], anchor="w") + marker = asc if sort_order == SORT_ASC else desc + if anchor == "e": + x["heading"] = marker + x["heading"] + else: + x["heading"] += marker + element.Widget.heading( + i, text=x["heading"], anchor=self.heading_justify_map[i] + ) def enable_heading_function(self, element: sg.Table, fn: callable) -> None: """ @@ -7108,7 +7296,7 @@ def enable_heading_function(self, element: sg.Table, fn: callable) -> None: unsaved changes column Note: Not typically used by the end user. Called from `Form.auto_map_elements()` - :param element: The PySimpleGUI Table element associated with this TableHeading + :param element: The PySimpleGUI Table element associated with this TableBuilder :param fn: A callback functions to run when a heading is clicked. The callback should take one column parameter. :returns: None @@ -7123,8 +7311,8 @@ def enable_heading_function(self, element: sg.Table, fn: callable) -> None: if self.add_save_heading_button: element.widget.heading(0, command=functools.partial(fn, None, save=True)) - def insert(self, idx, heading_column: str, column: str = None, *args, **kwargs): - super().insert(idx, {"heading": heading_column, "column": column}) + def insert(self, idx, heading: str, column: str = None, *args, **kwargs): + super().insert(idx, {"heading": heading, "column": column}) class _HeadingCallback: @@ -7192,11 +7380,11 @@ def edit(self, event): if not element: return - # get table_headings - table_heading = element.metadata["TableHeading"] + # get table_builders + table_builder = element.metadata["TableBuilder"] # get column name - columns = table_heading.columns() + columns = table_builder.columns column = columns[col_idx - 1] # use table_element to distinguish @@ -7211,7 +7399,7 @@ def edit(self, event): if col_idx == 0: return - if column in table_heading.readonly_columns: + if column in table_builder.readonly_columns: logger.debug(f"{column} is readonly") return @@ -7223,7 +7411,7 @@ def edit(self, event): logger.debug(f"{column} is a generated column") return - if not table_heading.allow_cell_edits: + if not table_builder.allow_cell_edits: logger.debug("This Table element does not allow editing") return @@ -7500,7 +7688,7 @@ def get_datakey_and_sgtable(self, treeview, frm): ]: for e in frm[data_key].selector: element = e["element"] - if element.widget == treeview and "TableHeading" in element.metadata: + if element.widget == treeview and "TableBuilder" in element.metadata: return data_key, element return None From b629b112936c2ed244034a10e3e7f2808991265d Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:53:51 -0400 Subject: [PATCH 075/121] Nit: col/headings_justification / anchor cleanup Pass 'justify' as "l", "r", "c", like PySimpleGUI does its cols_justification Pass 'anchor' for tk heading() function as "w", "e", "center" for tkinter. --- pysimplesql/pysimplesql.py | 41 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8251550a..0a4aec5d 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -5675,10 +5675,14 @@ def _set_colors(self, iid, toggle_color): def _handle_extra_kwargs(self): if self.headings_justification: for i, heading_id in enumerate(self.Widget["columns"]): - self.Widget.heading(heading_id, anchor=self.headings_justification[i]) + self.Widget.heading( + heading_id, anchor=TK_ANCHOR_MAP[self.headings_justification[i]] + ) if self.cols_justification: for i, column_id in enumerate(self.Widget["columns"]): - self.Widget.column(column_id, anchor=self.cols_justification[i]) + self.Widget.column( + column_id, anchor=TK_ANCHOR_MAP[self.cols_justification[i]] + ) if self.frame_pack_kwargs: self.table_frame.pack(**self.frame_pack_kwargs) @@ -7111,8 +7115,8 @@ def add_column( width: int, col_justify: ColumnJustify = "default", heading_justify: HeadingJustify = "column", - visible: bool = True, readonly: bool = False, + visible: bool = True, ) -> None: """ Add a new heading column to this TableBuilder object. Columns are added in the @@ -7124,13 +7128,13 @@ def add_column( :param width: The width for this column to display within the Table element :param col_justify: Default 'left'. Available options: 'left', 'right', 'center', 'default'. - :param heading_justify: Defaults to 'column' to match col_justify. Available + :param heading_justify: Defaults to 'column' inherity `col_justify`. Available options: 'left', 'right', 'center', 'column', 'default'. + :param readonly: Indicates if the column is read-only when + `TableBuilder.allow_cell_edits` is True. :param visible: True if the column is visible. Typically, the only hidden column would be the primary key column if any. This is also useful if the `DataSet.rows` DataFrame has information that you don't want to display. - :param readonly: Indicates if the column is read-only when - `TableBuilder.allow_cell_edits` is True. :returns: None """ self.append({"heading": heading, "column": column}) @@ -7208,10 +7212,8 @@ def col_justify_map(self) -> List[str]: :returns: a list column justifications for use with PySimpleGUI Table cols_justification parameter """ - justify = [ - TK_ANCHOR_MAP[justify[0].lower()] for justify in self._col_justify_map - ] - justify.insert(0, "w") + justify = [justify[0].lower() for justify in self._col_justify_map] + justify.insert(0, "l") return justify @property @@ -7219,8 +7221,19 @@ def heading_justify_map(self) -> List[str]: """ Convenience method for creating PySimpleGUI tables. - :returns: a list column justifications for use with PySimpleGUI Table - cols_justification parameter + :returns: a list heading justifications for use with LazyTable + `headings_justification` + """ + justify = [justify[0].lower() for justify in self._heading_justify_map] + justify.insert(0, "l") + return justify + + @property + def heading_anchor_map(self) -> List[str]: + """ + Internal method for passing directly to treeview heading() function. + + :returns: a list heading anchors for use with treeview heading() function. """ justify = [ TK_ANCHOR_MAP[justify[0].lower()] for justify in self._heading_justify_map @@ -7273,7 +7286,7 @@ def update_headings( desc = "\u25B2" for i, x in zip(range(len(self)), self): - anchor = self.heading_justify_map[i] + anchor = self.heading_anchor_map[i] # Clear the direction markers x["heading"] = x["heading"].replace(asc, "").replace(desc, "") if ( @@ -7287,7 +7300,7 @@ def update_headings( else: x["heading"] += marker element.Widget.heading( - i, text=x["heading"], anchor=self.heading_justify_map[i] + i, text=x["heading"], anchor=self.heading_anchor_map[i] ) def enable_heading_function(self, element: sg.Table, fn: callable) -> None: From c9e286dd53ec54f132a85b1ced788342c35c4620 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:31:56 -0400 Subject: [PATCH 076/121] Update sqlite-only orders.py to match multiple --- examples/SQLite_examples/orders.py | 47 ++++++++++++++++++++------- examples/orders_multiple_databases.py | 2 +- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 82389daf..4b8a1c31 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -1,6 +1,6 @@ import logging - import platform +import re import PySimpleGUI as sg import pysimplesql as ss @@ -13,7 +13,6 @@ # ----------------------------- logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) - # Set up the appropriate theme depending on the OS # ----------------------------- if platform.system() == "Windows": @@ -35,6 +34,26 @@ custom = custom | os_tp ss.themepack(custom) + +# create your own validator to be passed to a +# frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn +# used below in the quick_editor arguments +def is_valid_email(email): + valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None + if not valid_email: + return ss.ValidateResponse( + ss.ValidateRule.CUSTOM, email, " is not a valid email" + ) + return ss.ValidateResponse() + + +quick_editor_kwargs = { + "column_attributes": { + "email": {"custom_validate_fn": lambda value: is_valid_email(value)} + } +} + + # SQL Statement # ====================================================================================== @@ -49,7 +68,7 @@ order_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, customer_id INTEGER NOT NULL, date DATE NOT NULL DEFAULT (date('now')), - total REAL, + total DECTEXT(10,2), completed BOOLEAN NOT NULL, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); @@ -57,7 +76,7 @@ CREATE TABLE products ( product_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT 'New Product', - price REAL NOT NULL, + price DECTEXT(10,2) NOT NULL, inventory INTEGER DEFAULT 0 ); @@ -65,9 +84,9 @@ order_detail_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, order_id INTEGER, product_id INTEGER NOT NULL, - quantity INTEGER, - price REAL, - subtotal REAL GENERATED ALWAYS AS (price * quantity) STORED, + quantity INTEGER NOT NULL, + price DECTEXT(10,2), + subtotal DECTEXT(10,2) GENERATED ALWAYS AS (price * quantity) STORED, FOREIGN KEY (order_id) REFERENCES orders(order_id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(product_id) ); @@ -225,7 +244,14 @@ orderdetails_layout = [ [sg.Sizer(h_pixels=0, v_pixels=10)], - [ss.field("orders.customer_id", sg.Combo, label="Customer")], + [ + ss.field( + "orders.customer_id", + sg.Combo, + label="Customer", + quick_editor_kwargs=quick_editor_kwargs, + ) + ], [ ss.field("orders.date", label="Date"), ], @@ -320,7 +346,6 @@ def update_orders(frm_reference, window, data_key): # <=== let PySimpleSQL process its own events! Simple! elif ss.process_events(event, values): logger.info(f"PySimpleDB event handler handled the event {event}!") - # Code to automatically save and refresh order_details: # ---------------------------------------------------- elif ( @@ -333,7 +358,7 @@ def update_orders(frm_reference, window, data_key): if ( dataset.row_count and current_row["product_id"] not in [None, ss.PK_PLACEHOLDER] - and current_row["quantity"] + and current_row["quantity"] not in ss.EMPTY ): # get product_id product_id = current_row["product_id"] @@ -352,7 +377,7 @@ def update_orders(frm_reference, window, data_key): elif "Edit Products" in event: frm["products"].quick_editor() elif "Edit Customers" in event: - frm["customers"].quick_editor() + frm["customers"].quick_editor(**quick_editor_kwargs) # call a Form-level save elif "Save" in event: frm.save_records() diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 0451cb60..137c6fd2 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -301,7 +301,7 @@ def is_valid_email(email): "boolean_type": "BOOLEAN", "default_string": "'New Product'", "default_boolean": "0", - "generated_column": "NUMERIC(10,2) GENERATED ALWAYS AS (price * quantity) STORED", + "generated_column": "DECTEXT(10,2) GENERATED ALWAYS AS (price * quantity) STORED", "autoincrement": "AUTOINCREMENT", "false_bool": 0, "true_bool": 1, From 1c86e8ffe85ed4b8fdaba276fa2ef8c4353e2289 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 14 Jul 2023 01:49:28 -0400 Subject: [PATCH 077/121] Refactor DataSet as dataclass, add _LastSearch class --- pysimplesql/pysimplesql.py | 201 +++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 98 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 0a4aec5d..85842347 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -185,6 +185,7 @@ # --------------------------- PROMPT_MODE: int = 1 AUTOSAVE_MODE: int = 2 +PROMPT_SAVE_MODES = Literal[PROMPT_MODE, AUTOSAVE_MODE] # --------------------------- # RECORD SAVE RETURN BITMASKS @@ -303,6 +304,13 @@ class ValidateResponse: rule: str = None +@dc.dataclass +class _LastSearch: + search_string: str = None + column: str = None + pks: List[int] = dc.field(default_factory=list) + + class CellFormatFn: @staticmethod def bool_to_checkbox(val): @@ -614,6 +622,7 @@ def __contains__(self, item): return item in self.__dict__ +@dc.dataclass class DataSet: """ @@ -625,101 +634,101 @@ class DataSet: `DataSet` object having its own sorting, where clause, etc.). Note: While users will interact with DataSet objects often in pysimplesql, they typically aren't created manually by the user. + + :param data_key: The name you are assigning to this `DataSet` object (I.e. + 'people') Accessible via `DataSet.key`. + :param frm_reference: This is a reference to the @ Form object, for convenience. + Accessible via `DataSet.frm` + :param table: Name of the table + :param pk_column: The name of the column containing the primary key for this + table. + :param description_column: The name of the column used for display to users + (normally in a combobox or listbox). + :param query: You can optionally set an initial query here. If none is provided, + it will default to "SELECT * FROM {table}" + :param order_clause: The sort order of the returned query. If none is provided + it will default to "ORDER BY {description_column} ASC" + :param filtered: (optional) If True, the relationships will be considered and an + appropriate WHERE clause will be generated. False will display all records + in the table. + :param prompt_save: (optional) Default: Mode set in `Form`. Prompt to save + changes when dirty records are present. There are two modes available, + (if pysimplesql is imported as `ss`) use: + `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. + `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. + :param save_quiet: (optional) Default: Set in `Form`. True to skip info popup on + save. Error popups will still be shown. + :param duplicate_children: (optional) Default: Set in `Form`. If record has + children, prompt user to choose to duplicate current record, or both. + :param validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from + being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures + validation occurs before saving to the database. """ instances: ClassVar[List[DataSet]] = [] # Track our own instances - def __init__( - self, - data_key: str, - frm_reference: Form, - table: str, - pk_column: str, - description_column: str, - query: Optional[str] = "", - order_clause: Optional[str] = "", - filtered: bool = True, - prompt_save: int = None, - save_quiet: bool = None, - duplicate_children: bool = None, - validate_mode: ValidateMode = None, - ) -> None: - """ - Initialize a new `DataSet` instance. - - :param data_key: The name you are assigning to this `DataSet` object (I.e. - 'people'). - :param frm_reference: This is a reference to the @ Form object, for convenience - :param table: Name of the table - :param pk_column: The name of the column containing the primary key for this - table. - :param description_column: The name of the column used for display to users - (normally in a combobox or listbox). - :param query: You can optionally set an initial query here. If none is provided, - it will default to "SELECT * FROM {table}" - :param order_clause: The sort order of the returned query. If none is provided - it will default to "ORDER BY {description_column} ASC" - :param filtered: (optional) If True, the relationships will be considered and an - appropriate WHERE clause will be generated. False will display all records - in the table. - :param prompt_save: (optional) Default: Mode set in `Form`. Prompt to save - changes when dirty records are present. There are two modes available, - (if pysimplesql is imported as `ss`) use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. - :param save_quiet: (optional) Default: Set in `Form`. True to skip info popup on - save. Error popups will still be shown. - :param duplicate_children: (optional) Default: Set in `Form`. If record has - children, prompt user to choose to duplicate current record, or both. - :param validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from - being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures - validation occurs before saving to the database. - :returns: None - """ - DataSet.instances.append(self) - self.driver = frm_reference.driver - # No query was passed in, so we will generate a generic one - if not query: - query = self.driver.default_query(table) - # No order was passed in, so we will generate generic one - if not order_clause: - order_clause = self.driver.default_order(description_column) + data_key: dc.InitVar[str] + frm_reference: dc.InitVar[Form] + table: str + pk_column: str + description_column: str + query: Optional[str] = "" + order_clause: Optional[str] = "" + filtered: bool = True + prompt_save: dc.InitVar[PROMPT_SAVE_MODES] = None + save_quiet: bool = None + duplicate_children: bool = None + validate_mode: ValidateMode = None + + # setup during init, but not included in args/kwargs + # -------------------------------------------------- + rows: Union[pd.DataFrame, None] = dc.field(default=None, init=False) + _current_index: int = dc.field(default=0, init=False) + column_info: ColumnInfo = dc.field(default=None, init=False) + selector: List[str] = dc.field(default_factory=list, init=False) + + # initally empty clauses + join_clause: str = dc.field(default="", init=False) + where_clause: str = dc.field( + default="", init=False + ) # In addition to generated where clause! + + search_order: List[str] = dc.field(default_factory=list, init=False) + _last_search: _LastSearch = dc.field(default_factory=_LastSearch, init=False) + _search_string: tk.StringVar = dc.field(default=None, init=False) + + callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) + transform: Optional[Callable[[pd.DataFrame, int], None]] = dc.field( + default=None, init=False + ) + _simple_transform: SimpleTransformsDict = dc.field(default_factory=dict, init=False) + # Todo: do we need dependents? + dependents: list = dc.field(default_factory=list, init=False) + + def __post_init__(self, data_key, frm_reference, prompt_save): + DataSet.instances.append(self) self.key: str = data_key - self.frm: Form = frm_reference - self._current_index: int = 0 - self.table: str = table - self.pk_column: str = pk_column - self.description_column: str = description_column - self.query: str = query - self.order_clause: str = order_clause - self.join_clause: str = "" - self.where_clause: str = "" # In addition to the generated where clause! - self.dependents: list = [] - self.column_info: ColumnInfo # ColumnInfo collection - self.rows: Union[pd.DataFrame, None] = None - self.search_order: List[str] = [] - self._search_string: tk.StringVar = None - self._last_search: dict = {"search_string": None, "column": None, "pks": []} - self.selector: List[str] = [] - self.callbacks: CallbacksDict = {} - self.transform: Optional[Callable[[pd.DataFrame, int], None]] = None - self.filtered: bool = filtered + self.frm = frm_reference + self.driver = self.frm.driver + + # handle where values are not passed in: + # --------------------------------------- self._prompt_save = ( - self.frm._prompt_save if prompt_save is None else int(prompt_save) - ) - self.save_quiet = ( - self.frm.save_quiet if save_quiet is None else bool(save_quiet) + self.frm._prompt_save if prompt_save is None else prompt_save ) - self.duplicate_children = ( - self.frm.duplicate_children - if duplicate_children is None - else bool(duplicate_children) - ) - self.validate_mode = ( - self.frm.validate_mode if validate_mode is None else validate_mode - ) - self._simple_transform: SimpleTransformsDict = {} + if self.duplicate_children is None: + self.duplicate_children = self.frm.duplicate_children + if self.save_quiet is None: + self.save_quiet = self.frm.save_quiet + if self.validate_mode is None: + self.validate_mode = self.frm.validate_mode + + # generate generic clauses if none passed in + if not self.query: + self.query = self.driver.default_query(self.table) + if not self.order_clause: + self.order_clause = self.driver.default_order(self.description_column) # Override the [] operator to retrieve current columns by key def __getitem__(self, column: str) -> Union[str, int]: @@ -1529,17 +1538,13 @@ def search( return SEARCH_ABORTED # Reset _last_search if search_string is different - if search_string != self._last_search.get("search_string"): - self._last_search = { - "search_string": search_string, - "column": None, - "pks": [], - } + if search_string != self._last_search.search_string: + self._last_search = _LastSearch(search_string) # Reorder search_columns to start with the column in _last_search search_columns = self.search_order.copy() - if self._last_search["column"] in search_columns: - idx = search_columns.index(self._last_search["column"]) + if self._last_search.column in search_columns: + idx = search_columns.index(self._last_search.column) search_columns = search_columns[idx:] + search_columns[:idx] # reorder rows to be idx + 1, and wrap around back to the beginning @@ -1553,7 +1558,7 @@ def search( pk = None for column in search_columns: # update _last_search column - self._last_search["column"] = column + self._last_search.column = column # search through processed rows, looking for search_string result = rows[ @@ -1567,7 +1572,7 @@ def search( pk = result.iloc[0][self.pk_column] # search next column if the same pk is found again - if pk in self._last_search["pks"]: + if pk in self._last_search.pks: continue # if pk is same as one we are on, we can just updated_elements @@ -1583,7 +1588,7 @@ def search( if pk: # Update _last_search with the pk - self._last_search["pks"].append(pk) + self._last_search.pks.append(pk) # jump to the pk self.set_by_pk( From f0165a967caca12683bff2fdea98ee071e724488 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:23:14 -0400 Subject: [PATCH 078/121] Cleanup constants --- pysimplesql/pysimplesql.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 85842347..3dbcb8a6 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -247,32 +247,14 @@ ColumnJustify = Literal["left", "right", "center", "default"] HeadingJustify = Literal["left", "right", "center", "column", "default"] -# -------------------- -# Date formats -# -------------------- -# Format for date only -DATE_FORMAT = "%Y-%m-%d" - # -------------------- # DateTime formats # -------------------- -# Format for date and time without fraction +DATE_FORMAT = "%Y-%m-%d" DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" -# Format for date and time with microsecond precision DATETIME_FORMAT_MICROSECOND = "%Y-%m-%d %H:%M:%S.%f" - -# -------------------- -# Timestamp formats -# -------------------- -# Format for timestamp without fraction TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S" -# Format for timestamp with microsecond precision TIMESTAMP_FORMAT_MICROSECOND = "%Y-%m-%dT%H:%M:%S.%f" - -# -------------------- -# Time format -# -------------------- -# Format for time only TIME_FORMAT = "%H:%M:%S" @@ -281,12 +263,12 @@ class Boolean(enum.Flag): FALSE = False -class ValidateMode(str, enum.Enum): +class ValidateMode(enum.Enum): STRICT = "strict" RELAXED = "relaxed" -class ValidateRule(str, enum.Enum): +class ValidateRule(enum.Enum): REQUIRED = "required" PYTHON_TYPE = "python_type" PRECISION = "precision" From feaaa2284473f6dc3a3ab69b7ff8679bd656456e Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:23:25 -0400 Subject: [PATCH 079/121] convert Form to a dataclass --- pysimplesql/pysimplesql.py | 190 ++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 97 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 3dbcb8a6..f08d0c2a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3181,6 +3181,7 @@ def validate_field( return None +@dc.dataclass class Form: """ @@ -3188,112 +3189,107 @@ class Form: Maintains an internal version of the actual database `DataSet` objects can be accessed by key, I.e. frm['data_key']. + + :param driver: Supported `SQLDriver`. See `Sqlite()`, `Mysql()`, `Postgres()` + :param bind_window: Bind this window to the `Form` + :param prefix_data_keys: (optional) prefix auto generated data_key names with + this value. Example 'data_' + :param parent: (optional)Parent `Form` to base dataset off of + :param filter: (optional) Only import elements with the same filter set. + Typically set with `field()`, but can also be set manually as a dict with + the key 'filter' set in the element's metadata + :param select_first: (optional) Default:True. For each top-level parent, selects + first row, populating children as well. + :param prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when + dirty records are present. + Two modes available, (if pysimplesql is imported as `ss`) use: + - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. + - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. + :param save_quiet: (optional) Default:False. True to skip info popup on save. + Error popups will still be shown. + :param update_cascade: (optional) Default:True. Requery and filter child table + on selected parent primary key. (ON UPDATE CASCADE in SQL) + :param delete_cascade: (optional) Default:True. Delete the dependent child + records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + :param duplicate_children: (optional) Default:True. If record has children, + prompt user to choose to duplicate current record, or both. + :param description_column_names: (optional) A list of names to use for the + DataSet object's description column, displayed in Listboxes, Comboboxes, and + Tables instead of the primary key. The first matching column of the table is + given priority. If no match is found, the second column is used. Default + list: ['description', 'name', 'title']. + :param live_update: (optional) Default value is False. If True, changes made in + a field will be immediately pushed to associated selectors. If False, + changes will be pushed only after a save action. + :param auto_add_relationships: (optional) Controls the invocation of + auto_add_relationships. Default is True. Set it to False when creating a new + `Form` with pre-existing `Relationship` instances. + :param validate_mode: Passed to `DataSet` init to set validate mode. + `ss.ValidateMode.STRICT` to prevent invalid values from being entered. + `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation + occurs before saving to the database. + :returns: None """ instances: ClassVar[List[Form]] = [] # Track our instances relationships: ClassVar[List[Relationship]] = [] # Track our relationships - def __init__( + driver: SQLDriver + bind_window: dc.InitVar[sg.Window] = None + prefix_data_keys: dc.InitVar[str] = "" + parent: Form = None # TODO: This doesn't seem to really be used + filter: str = None + select_first: dc.InitVar[bool] = True + prompt_save: dc.InitVar[PROMPT_SAVE_MODES] = PROMPT_MODE + save_quiet: bool = False + update_cascade: bool = True + delete_cascade: bool = True + duplicate_children: bool = True + description_column_names: List[str] = dc.field( + default_factory=lambda: ["description", "name", "title"] + ) + live_update: bool = False + auto_add_relationships: dc.InitVar[bool] = True + validate_mode: ValidateMode = ValidateMode.RELAXED + + # init=False attributes + window: Optional[sg.Window] = dc.field(default=0, init=False) + datasets: Dict[str, DataSet] = dc.field(default_factory=dict, init=False) + element_map: List[ElementMap] = dc.field(default_factory=list, init=False) + """ + The element map dict is set up as below: + + .. literalinclude:: ../doc_examples/element_map.1.py + :language: python + :caption: Example code + """ + event_map: List = dc.field( + default_factory=list, init=False + ) # Array of dicts, {'event':, 'function':, 'table':} + _edit_protect: bool = dc.field(default=False, init=False) + relationships: List[Relationship] = dc.field(default_factory=list, init=False) + callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) + force_save: bool = dc.field(default=False, init=False) + # empty variables, just in-case bind() never called + popup: Popup = dc.field(default=None, init=False) + _celledit: _CellEdit = dc.field(default=None, init=False) + _liveupdate: _LiveUpdate = dc.field(default=None, init=False) + _liveupdate_binds: dict = dc.field(default_factory=dict, init=False) + + def __post_init__( self, - driver: SQLDriver, - bind_window: sg.Window = None, - prefix_data_keys: str = "", - parent: Form = None, - filter: str = None, - select_first: bool = True, - prompt_save: int = PROMPT_MODE, - save_quiet: bool = False, - update_cascade: bool = True, - delete_cascade: bool = True, - duplicate_children: bool = True, - description_column_names: List[str] = None, - live_update: bool = False, - auto_add_relationships: bool = True, - validate_mode: ValidateMode = ValidateMode.RELAXED, - ) -> None: - """ - Initialize a new `Form` instance. - - :param driver: Supported `SQLDriver`. See `Sqlite()`, `Mysql()`, `Postgres()` - :param bind_window: Bind this window to the `Form` - :param prefix_data_keys: (optional) prefix auto generated data_key names with - this value. Example 'data_' - :param parent: (optional)Parent `Form` to base dataset off of - :param filter: (optional) Only import elements with the same filter set. - Typically set with `field()`, but can also be set manually as a dict with - the key 'filter' set in the element's metadata - :param select_first: (optional) Default:True. For each top-level parent, selects - first row, populating children as well. - :param prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when - dirty records are present. - Two modes available, (if pysimplesql is imported as `ss`) use: - - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. - :param save_quiet: (optional) Default:False. True to skip info popup on save. - Error popups will still be shown. - :param update_cascade: (optional) Default:True. Requery and filter child table - on selected parent primary key. (ON UPDATE CASCADE in SQL) - :param delete_cascade: (optional) Default:True. Delete the dependent child - records if the parent table record is deleted. (ON UPDATE DELETE in SQL) - :param duplicate_children: (optional) Default:True. If record has children, - prompt user to choose to duplicate current record, or both. - :param description_column_names: (optional) A list of names to use for the - DataSet object's description column, displayed in Listboxes, Comboboxes, and - Tables instead of the primary key. The first matching column of the table is - given priority. If no match is found, the second column is used. Default - list: ['description', 'name', 'title']. - :param live_update: (optional) Default value is False. If True, changes made in - a field will be immediately pushed to associated selectors. If False, - changes will be pushed only after a save action. - :param auto_add_relationships: (optional) Controls the invocation of - auto_add_relationships. Default is True. Set it to False when creating a new - `Form` with pre-existing `Relationship` instances. - :param validate_mode: Passed to `DataSet` init to set validate mode. - `ss.ValidateMode.STRICT` to prevent invalid values from being entered. - `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation - occurs before saving to the database. - :returns: None - """ - win_pb = ProgressBar(lang.startup_form) - win_pb.update(lang.startup_init, 0) + bind_window, + prefix_data_keys, + select_first, + prompt_save, + auto_add_relationships, + ): Form.instances.append(self) - self.driver: SQLDriver = driver - self.filter: str = filter - self.parent: Form = parent # TODO: This doesn't seem to really be used yet - self.window: Optional[sg.Window] = None - self._edit_protect: bool = False - self.datasets: Dict[str, DataSet] = {} - self.element_map: List[ElementMap] = [] - """ - The element map dict is set up as below: - - .. literalinclude:: ../doc_examples/element_map.1.py - :language: python - :caption: Example code - """ - self.event_map = [] # Array of dicts, {'event':, 'function':, 'table':} - self.relationships: List[Relationship] = [] - self.callbacks: CallbacksDict = {} - self._prompt_save: int = prompt_save - self.save_quiet: bool = save_quiet - self.force_save: bool = False - self.update_cascade: bool = update_cascade - self.delete_cascade: bool = delete_cascade - self.duplicate_children: int = duplicate_children - if description_column_names is None: - self.description_column_names = ["description", "name", "title"] - else: - self.description_column_names = description_column_names - self.live_update: bool = live_update - self.validate_mode: ValidateMode = validate_mode - - # empty variables, just in-case bind() never called - self.popup = None - self._celledit = None - self._liveupdate = None - self._liveupdate_binds = {} + self._prompt_save: PROMPT_SAVE_MODES = prompt_save + win_pb = ProgressBar(lang.startup_form) + win_pb.update(lang.startup_init, 0) # Add our default datasets and relationships win_pb.update(lang.startup_datasets, 25) self.auto_add_datasets(prefix_data_keys) From 80fe73ee8db07e1496059f8c64ebcfcb566b07b0 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 14 Jul 2023 02:27:27 -0400 Subject: [PATCH 080/121] ruff fix --- pysimplesql/pysimplesql.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index f08d0c2a..89a92d18 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3267,7 +3267,9 @@ class Form: default_factory=list, init=False ) # Array of dicts, {'event':, 'function':, 'table':} _edit_protect: bool = dc.field(default=False, init=False) - relationships: List[Relationship] = dc.field(default_factory=list, init=False) + relationships: List[Relationship] = dc.field( # noqa: PIE794 + default_factory=list, init=False + ) callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) force_save: bool = dc.field(default=False, init=False) # empty variables, just in-case bind() never called From 646f6cb7d3f6940f144d9c9d0955aed92d91f82c Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:15:59 -0400 Subject: [PATCH 081/121] Fix: Don't generate __eq__ for DataSet --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 89a92d18..a66b8d85 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -604,7 +604,7 @@ def __contains__(self, item): return item in self.__dict__ -@dc.dataclass +@dc.dataclass(eq=False) class DataSet: """ From 8204108470278edcfdb17692988b590a0d1da0be Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 17 Jul 2023 16:33:24 -0400 Subject: [PATCH 082/121] Fix: Don't add __eq__ to Form either --- .idea/workspace.xml | 42 ++++++++++++++++++++++++++++++++++++++ pysimplesql/pysimplesql.py | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..2411ec96 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + 1689261470574 + + + + \ No newline at end of file diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index a66b8d85..3cb8e1ce 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3181,7 +3181,7 @@ def validate_field( return None -@dc.dataclass +@dc.dataclass(eq=False) class Form: """ From 53a8dec1692c2284e54c4c96cf2c6a786d4d0226 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:00:47 -0400 Subject: [PATCH 083/121] Fix: Don't close quick_editor until frm has refreshed Fixes bug where you can close quick_editor, and close frm window too quickly causing a tkinter error --- pysimplesql/pysimplesql.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 3cb8e1ce..8fdf28bb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2870,12 +2870,12 @@ def quick_editor( finalize=True, ttk_theme=themepack.ttk_theme, # Must, otherwise will redraw window icon=themepack.icon, + enable_close_attempted_event=True, ) quick_frm = Form( self.frm.driver, bind_window=quick_win, live_update=True, - auto_add_relationships=False, ) # Select the current entry to start with @@ -2898,15 +2898,14 @@ def quick_editor( logger.debug( f"PySimpleSQL Quick Editor event handler handled the event {event}!" ) - if event in [sg.WIN_CLOSED, "Exit"]: + if event == "-WINDOW CLOSE ATTEMPTED-": + if quick_frm.popup.popup_info: + quick_frm.popup.popup_info.close() + self.requery() + self.frm.update_elements() + quick_win.close() break - logger.debug(f"This event ({event}) is not yet handled.") - if quick_frm.popup.popup_info: - quick_frm.popup.popup_info.close() - quick_win.close() - self.requery() - self.frm.update_elements() def add_simple_transform(self, transforms: SimpleTransformsDict) -> None: """ From 732eb0005c0e20513d0f47745504dc31058cd789 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 17 Jul 2023 17:02:41 -0400 Subject: [PATCH 084/121] Work on Relationship --- pysimplesql/pysimplesql.py | 63 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8fdf28bb..541a6380 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -440,6 +440,16 @@ def on_update_cascade(self): def on_delete_cascade(self): return bool(self.delete_cascade and self.frm.delete_cascade) + @classmethod + def get(cls, driver_reference) -> List[Relationship]: + """ + Return the relationships for the passed-in `Driver` object. + + :param driver_reference: `Driver` to get relationships for. + :returns: A list of @Relationship objects + """ + return [r for r in cls.instances if r.driver == driver_reference] + @classmethod def get_relationships(cls, table: str) -> List[Relationship]: """ @@ -564,6 +574,10 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str and not r.on_update_cascade } + @classmethod + def purge(cls, driver_reference) -> None: + cls.instances = [rel for rel in cls.instances if rel.driver != driver_reference] + @dc.dataclass class ElementMap: @@ -1069,7 +1083,7 @@ def records_changed(self, column: str = None, recursive=True) -> bool: # handle recursive checking next if recursive: - for rel in self.frm.relationships: + for rel in Relationship.get(self.frm.driver): if rel.parent_table == self.table and rel.on_update_cascade: dirty = self.frm[rel.child_table].records_changed() if dirty: @@ -1298,7 +1312,7 @@ def requery_dependents( # dependents=False: no recursive dependent requery self.requery(update_elements=update_elements, requery_dependents=False) - for rel in self.frm.relationships: + for rel in Relationship.get(self.frm.driver): if rel.parent_table == self.table and rel.on_update_cascade: logger.debug( f"Requerying dependent table {self.frm[rel.child_table].table}" @@ -1871,7 +1885,7 @@ def insert_record( new_values[k] = v # Make sure we take into account the foreign key relationships... - for r in self.frm.relationships: + for r in Relationship.get(self.frm.driver): if self.table == r.child_table and r.on_update_cascade: new_values[r.fk_column] = self.frm[r.parent_table].get_current_pk() @@ -2204,7 +2218,7 @@ def save_record_recursive( elements without saving if individual `DataSet._prompt_save()` is False. :returns: dict of {table : results} """ - for rel in self.frm.relationships: + for rel in Relationship.get(self.frm.driver): if rel.parent_table == self.table and rel.on_update_cascade: self.frm[rel.child_table].save_record_recursive( results=results, @@ -3220,9 +3234,6 @@ class Form: :param live_update: (optional) Default value is False. If True, changes made in a field will be immediately pushed to associated selectors. If False, changes will be pushed only after a save action. - :param auto_add_relationships: (optional) Controls the invocation of - auto_add_relationships. Default is True. Set it to False when creating a new - `Form` with pre-existing `Relationship` instances. :param validate_mode: Passed to `DataSet` init to set validate mode. `ss.ValidateMode.STRICT` to prevent invalid values from being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation @@ -3231,7 +3242,6 @@ class Form: """ instances: ClassVar[List[Form]] = [] # Track our instances - relationships: ClassVar[List[Relationship]] = [] # Track our relationships driver: SQLDriver bind_window: dc.InitVar[sg.Window] = None @@ -3266,9 +3276,6 @@ class Form: default_factory=list, init=False ) # Array of dicts, {'event':, 'function':, 'table':} _edit_protect: bool = dc.field(default=False, init=False) - relationships: List[Relationship] = dc.field( # noqa: PIE794 - default_factory=list, init=False - ) callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) force_save: bool = dc.field(default=False, init=False) # empty variables, just in-case bind() never called @@ -3283,7 +3290,6 @@ def __post_init__( prefix_data_keys, select_first, prompt_save, - auto_add_relationships, ): Form.instances.append(self) @@ -3295,8 +3301,7 @@ def __post_init__( win_pb.update(lang.startup_datasets, 25) self.auto_add_datasets(prefix_data_keys) win_pb.update(lang.startup_relationships, 50) - if auto_add_relationships: - self.auto_add_relationships() + self.auto_add_relationships() self.requery_all( select_first=select_first, update_elements=False, requery_dependents=True ) @@ -3486,18 +3491,16 @@ def add_relationship( record is deleted (ON UPDATE DELETE in SQL) :returns: None """ - self.relationships.append( - Relationship( - join, - child_table, - fk_column, - parent_table, - pk_column, - update_cascade, - delete_cascade, - self.driver, - self, - ) + Relationship( + join, + child_table, + fk_column, + parent_table, + pk_column, + update_cascade, + delete_cascade, + self.driver, + self, ) def set_fk_column_cascade( @@ -3585,7 +3588,7 @@ def auto_add_relationships(self) -> None: """ logger.info("Automatically adding foreign key relationships") # Clear any current rels so that successive calls will not double the entries - self.relationships = [] # clear any relationships already stored + Relationship.purge(self.driver) # clear any relationships already stored relationships = self.driver.relationships() for r in relationships: logger.debug( @@ -9103,7 +9106,7 @@ def generate_join_clause(self, dataset: DataSet) -> str: :rtype: str """ join = "" - for r in dataset.frm.relationships: + for r in Relationship.get(dataset.frm.driver): if dataset.table == r.child_table: join += f" {self.relationship_to_join_clause(r)}" return join if not dataset.join_clause else dataset.join_clause @@ -9120,7 +9123,7 @@ def generate_where_clause(dataset: DataSet) -> str: :rtype: str """ where = "" - for r in dataset.frm.relationships: + for r in Relationship.get(dataset.frm.driver): if dataset.table == r.child_table and r.on_update_cascade: table = dataset.table parent_pk = dataset.frm[r.parent_table].get_current(r.pk_column) @@ -9307,7 +9310,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: # Next, duplicate the child records! if children: for _ in dataset.frm.datasets: - for r in dataset.frm.relationships: + for r in Relationship.get(dataset.frm.driver): if ( r.parent_table == dataset.table and r.on_update_cascade From 33e53210b4d4c627e404199310f66311230565cf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:19:15 -0400 Subject: [PATCH 085/121] Revert "Work on Relationship" This reverts commit 732eb0005c0e20513d0f47745504dc31058cd789. --- pysimplesql/pysimplesql.py | 63 ++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 541a6380..8fdf28bb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -440,16 +440,6 @@ def on_update_cascade(self): def on_delete_cascade(self): return bool(self.delete_cascade and self.frm.delete_cascade) - @classmethod - def get(cls, driver_reference) -> List[Relationship]: - """ - Return the relationships for the passed-in `Driver` object. - - :param driver_reference: `Driver` to get relationships for. - :returns: A list of @Relationship objects - """ - return [r for r in cls.instances if r.driver == driver_reference] - @classmethod def get_relationships(cls, table: str) -> List[Relationship]: """ @@ -574,10 +564,6 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str and not r.on_update_cascade } - @classmethod - def purge(cls, driver_reference) -> None: - cls.instances = [rel for rel in cls.instances if rel.driver != driver_reference] - @dc.dataclass class ElementMap: @@ -1083,7 +1069,7 @@ def records_changed(self, column: str = None, recursive=True) -> bool: # handle recursive checking next if recursive: - for rel in Relationship.get(self.frm.driver): + for rel in self.frm.relationships: if rel.parent_table == self.table and rel.on_update_cascade: dirty = self.frm[rel.child_table].records_changed() if dirty: @@ -1312,7 +1298,7 @@ def requery_dependents( # dependents=False: no recursive dependent requery self.requery(update_elements=update_elements, requery_dependents=False) - for rel in Relationship.get(self.frm.driver): + for rel in self.frm.relationships: if rel.parent_table == self.table and rel.on_update_cascade: logger.debug( f"Requerying dependent table {self.frm[rel.child_table].table}" @@ -1885,7 +1871,7 @@ def insert_record( new_values[k] = v # Make sure we take into account the foreign key relationships... - for r in Relationship.get(self.frm.driver): + for r in self.frm.relationships: if self.table == r.child_table and r.on_update_cascade: new_values[r.fk_column] = self.frm[r.parent_table].get_current_pk() @@ -2218,7 +2204,7 @@ def save_record_recursive( elements without saving if individual `DataSet._prompt_save()` is False. :returns: dict of {table : results} """ - for rel in Relationship.get(self.frm.driver): + for rel in self.frm.relationships: if rel.parent_table == self.table and rel.on_update_cascade: self.frm[rel.child_table].save_record_recursive( results=results, @@ -3234,6 +3220,9 @@ class Form: :param live_update: (optional) Default value is False. If True, changes made in a field will be immediately pushed to associated selectors. If False, changes will be pushed only after a save action. + :param auto_add_relationships: (optional) Controls the invocation of + auto_add_relationships. Default is True. Set it to False when creating a new + `Form` with pre-existing `Relationship` instances. :param validate_mode: Passed to `DataSet` init to set validate mode. `ss.ValidateMode.STRICT` to prevent invalid values from being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation @@ -3242,6 +3231,7 @@ class Form: """ instances: ClassVar[List[Form]] = [] # Track our instances + relationships: ClassVar[List[Relationship]] = [] # Track our relationships driver: SQLDriver bind_window: dc.InitVar[sg.Window] = None @@ -3276,6 +3266,9 @@ class Form: default_factory=list, init=False ) # Array of dicts, {'event':, 'function':, 'table':} _edit_protect: bool = dc.field(default=False, init=False) + relationships: List[Relationship] = dc.field( # noqa: PIE794 + default_factory=list, init=False + ) callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) force_save: bool = dc.field(default=False, init=False) # empty variables, just in-case bind() never called @@ -3290,6 +3283,7 @@ def __post_init__( prefix_data_keys, select_first, prompt_save, + auto_add_relationships, ): Form.instances.append(self) @@ -3301,7 +3295,8 @@ def __post_init__( win_pb.update(lang.startup_datasets, 25) self.auto_add_datasets(prefix_data_keys) win_pb.update(lang.startup_relationships, 50) - self.auto_add_relationships() + if auto_add_relationships: + self.auto_add_relationships() self.requery_all( select_first=select_first, update_elements=False, requery_dependents=True ) @@ -3491,16 +3486,18 @@ def add_relationship( record is deleted (ON UPDATE DELETE in SQL) :returns: None """ - Relationship( - join, - child_table, - fk_column, - parent_table, - pk_column, - update_cascade, - delete_cascade, - self.driver, - self, + self.relationships.append( + Relationship( + join, + child_table, + fk_column, + parent_table, + pk_column, + update_cascade, + delete_cascade, + self.driver, + self, + ) ) def set_fk_column_cascade( @@ -3588,7 +3585,7 @@ def auto_add_relationships(self) -> None: """ logger.info("Automatically adding foreign key relationships") # Clear any current rels so that successive calls will not double the entries - Relationship.purge(self.driver) # clear any relationships already stored + self.relationships = [] # clear any relationships already stored relationships = self.driver.relationships() for r in relationships: logger.debug( @@ -9106,7 +9103,7 @@ def generate_join_clause(self, dataset: DataSet) -> str: :rtype: str """ join = "" - for r in Relationship.get(dataset.frm.driver): + for r in dataset.frm.relationships: if dataset.table == r.child_table: join += f" {self.relationship_to_join_clause(r)}" return join if not dataset.join_clause else dataset.join_clause @@ -9123,7 +9120,7 @@ def generate_where_clause(dataset: DataSet) -> str: :rtype: str """ where = "" - for r in Relationship.get(dataset.frm.driver): + for r in dataset.frm.relationships: if dataset.table == r.child_table and r.on_update_cascade: table = dataset.table parent_pk = dataset.frm[r.parent_table].get_current(r.pk_column) @@ -9310,7 +9307,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: # Next, duplicate the child records! if children: for _ in dataset.frm.datasets: - for r in Relationship.get(dataset.frm.driver): + for r in dataset.frm.relationships: if ( r.parent_table == dataset.table and r.on_update_cascade From ef6d964d669bb6da36d8cde797c7e33cd3503b62 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:35:15 -0400 Subject: [PATCH 086/121] Fix converted dataclass (DataSet/Form/TableBuilder) I didn't understand how init=False worked. So I had accidently turned these into ClassVars, not instance attributes, whoops! --- pysimplesql/pysimplesql.py | 107 +++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 58 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 8fdf28bb..bb49aa83 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -662,37 +662,33 @@ class DataSet: duplicate_children: bool = None validate_mode: ValidateMode = None - # setup during init, but not included in args/kwargs - # -------------------------------------------------- - rows: Union[pd.DataFrame, None] = dc.field(default=None, init=False) - _current_index: int = dc.field(default=0, init=False) - column_info: ColumnInfo = dc.field(default=None, init=False) - selector: List[str] = dc.field(default_factory=list, init=False) - - # initally empty clauses - join_clause: str = dc.field(default="", init=False) - where_clause: str = dc.field( - default="", init=False - ) # In addition to generated where clause! - - search_order: List[str] = dc.field(default_factory=list, init=False) - _last_search: _LastSearch = dc.field(default_factory=_LastSearch, init=False) - _search_string: tk.StringVar = dc.field(default=None, init=False) - - callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) - transform: Optional[Callable[[pd.DataFrame, int], None]] = dc.field( - default=None, init=False - ) - _simple_transform: SimpleTransformsDict = dc.field(default_factory=dict, init=False) - - # Todo: do we need dependents? - dependents: list = dc.field(default_factory=list, init=False) - def __post_init__(self, data_key, frm_reference, prompt_save): DataSet.instances.append(self) + self.key: str = data_key self.frm = frm_reference self.driver = self.frm.driver + self.relationships = self.driver.relationships + + self.rows: Union[pd.DataFrame, None] = [] + self._current_index: int = 0 + self.column_info: ColumnInfo = None + self.selector: List[str] = [] + + # initally empty clauses + self.join_clause: str = "" + self.where_clause: str = "" # In addition to generated where clause! + + self.search_order: List[str] = [] + self._last_search: _LastSearch = _LastSearch() + self._search_string: tk.StringVar = None + + self.callbacks: CallbacksDict = {} + self.transform: Optional[Callable[[pd.DataFrame, int], None]] = None + self._simple_transform: SimpleTransformsDict = {} + + # Todo: do we need dependents? + self.dependents: list = [] # handle where values are not passed in: # --------------------------------------- @@ -3251,32 +3247,6 @@ class Form: auto_add_relationships: dc.InitVar[bool] = True validate_mode: ValidateMode = ValidateMode.RELAXED - # init=False attributes - window: Optional[sg.Window] = dc.field(default=0, init=False) - datasets: Dict[str, DataSet] = dc.field(default_factory=dict, init=False) - element_map: List[ElementMap] = dc.field(default_factory=list, init=False) - """ - The element map dict is set up as below: - - .. literalinclude:: ../doc_examples/element_map.1.py - :language: python - :caption: Example code - """ - event_map: List = dc.field( - default_factory=list, init=False - ) # Array of dicts, {'event':, 'function':, 'table':} - _edit_protect: bool = dc.field(default=False, init=False) - relationships: List[Relationship] = dc.field( # noqa: PIE794 - default_factory=list, init=False - ) - callbacks: CallbacksDict = dc.field(default_factory=dict, init=False) - force_save: bool = dc.field(default=False, init=False) - # empty variables, just in-case bind() never called - popup: Popup = dc.field(default=None, init=False) - _celledit: _CellEdit = dc.field(default=None, init=False) - _liveupdate: _LiveUpdate = dc.field(default=None, init=False) - _liveupdate_binds: dict = dc.field(default_factory=dict, init=False) - def __post_init__( self, bind_window, @@ -3287,6 +3257,27 @@ def __post_init__( ): Form.instances.append(self) + self.window: Optional[sg.Window] = 0 + self.datasets: Dict[str, DataSet] = {} + self.element_map: List[ElementMap] = [] + """ + The element map dict is set up as below: + + .. literalinclude:: ../doc_examples/element_map.1.py + :language: python + :caption: Example code + """ + self.event_map: List = [] # Array of dicts, {'event':, 'function':, 'table':} + self._edit_protect: bool = False + self.relationships: RelationshipStore = self.driver.relationships + self.callbacks: CallbacksDict = {} + self.force_save: bool = False + # empty variables, just in-case bind() never called + self.popup: Popup = None + self._celledit: _CellEdit = None + self._liveupdate: _LiveUpdate = None + self._liveupdate_binds: dict = {} + self._prompt_save: PROMPT_SAVE_MODES = prompt_save win_pb = ProgressBar(lang.startup_form) @@ -7077,16 +7068,16 @@ class TableBuilder(list): add_save_heading_button: bool = False style: TableStyler = dc.field(default_factory=TableStyler) - _width_map: List[int] = dc.field(default_factory=list, init=False) - _col_justify_map: List[int] = dc.field(default_factory=list, init=False) - _heading_justify_map: List[int] = dc.field(default_factory=list, init=False) - _visible_map: List[bool] = dc.field(default_factory=list, init=False) - readonly_columns: List[str] = dc.field(default_factory=list, init=False) - def __post_init__(self): # Store this instance in the master list of instances TableBuilder.instances.append(self) + self._width_map: List[int] = [] + self._col_justify_map: List[int] = [] + self._heading_justify_map: List[int] = [] + self._visible_map: List[bool] = [] + self.readonly_columns: List[str] = [] + if self.add_save_heading_button: self.insert(0, themepack.unsaved_column_header) else: From b109077fa0e0ae7efe095afe17c0893fca29bce5 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:33:12 -0400 Subject: [PATCH 087/121] Convert SqlDrivers to Dataclasses, and Move Relationships to Driver It makes most sense to the list of relationships be bound to the driver. Multiple forms can share 1 driver, but each Form only has 1 driver. This way, there can be multiple drivers in use, but we arn't using class instances to lookup tables/columns/etc. --- pysimplesql/pysimplesql.py | 923 +++++++++++++++++++------------------ 1 file changed, 482 insertions(+), 441 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index bb49aa83..1569b7cc 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -54,7 +54,6 @@ from __future__ import annotations # docstrings -import abc import asyncio import calendar import contextlib @@ -73,10 +72,12 @@ import threading import tkinter as tk import tkinter.font as tkfont +from abc import ABC, abstractmethod from decimal import Decimal, DecimalException from time import sleep, time from tkinter import ttk from typing import ( + TYPE_CHECKING, Any, Callable, ClassVar, @@ -95,6 +96,9 @@ import pandas as pd import PySimpleGUI as sg +if TYPE_CHECKING: + from pathlib import Path + # Wrap optional imports so that pysimplesql can be imported as a single file if desired: with contextlib.suppress(ModuleNotFoundError, ImportError): from .language_pack import * # noqa F403 @@ -246,6 +250,7 @@ TableJustify = Literal["left", "right", "center"] ColumnJustify = Literal["left", "right", "center", "default"] HeadingJustify = Literal["left", "right", "center", "column", "default"] +InMemory = Literal[":memory:"] # -------------------- # DateTime formats @@ -379,30 +384,18 @@ def get_instance(self): @dc.dataclass class Relationship: - """ - Used to track primary/foreign key relationships in the database. - - See the following for more information: `Form.add_relationship` and - `Form.auto_add_relationships`. - :param join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. - :param child_table: The table name of the child table + :param child_table: The table name of the fk table :param fk_column: The child table's foreign key column :param parent_table: The table name of the parent table :param pk_column: The parent table's primary key column :param update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' :param delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' :param driver: A `SQLDriver` instance - :param frm: A Form instance :returns: None - - Note: This class is not typically used the end user """ - # store our own instances - instances: ClassVar[List[Relationship]] = [] - join_type: str child_table: str fk_column: Union[str, int] @@ -410,48 +403,44 @@ class Relationship: pk_column: Union[str, int] update_cascade: bool delete_cascade: bool - driver: SQLDriver - frm: Form + driver: Driver - def __post_init__(self): - Relationship.instances.append(self) + @property + def on_update_cascade(self): + return bool(self.update_cascade and self.driver.update_cascade) + + @property + def on_delete_cascade(self): + return bool(self.delete_cascade and self.driver.delete_cascade) def __str__(self): """Return a join clause when cast to a string.""" return self.driver.relationship_to_join_clause(self) - def __repr__(self): - """Return a more descriptive string for debugging.""" - return ( - f"Relationship (" - f"\n\tjoin={self.join_type}," - f"\n\tchild_table={self.child_table}," - f"\n\tfk_column={self.fk_column}," - f"\n\tparent_table={self.parent_table}," - f"\n\tpk_column={self.pk_column}" - f"\n)" - ) - @property - def on_update_cascade(self): - return bool(self.update_cascade and self.frm.update_cascade) +@dc.dataclass +class RelationshipStore(list): + """ + Used to track primary/foreign key relationships in the database. - @property - def on_delete_cascade(self): - return bool(self.delete_cascade and self.frm.delete_cascade) + See the following for more information: `SQLDriver.add_relationship` and + `SQLDriver.auto_add_relationships`. - @classmethod - def get_relationships(cls, table: str) -> List[Relationship]: + Note: This class is not typically used the end user + """ + + driver: SQLDriver + + def get_rels_for(self, table: str) -> List[Relationship]: """ Return the relationships for the passed-in table. :param table: The table to get relationships for :returns: A list of @Relationship objects """ - return [r for r in cls.instances if r.child_table == table] + return [r for r in self if r.child_table == table] - @classmethod - def get_update_cascade_tables(cls, table: str) -> List[str]: + def get_update_cascade_tables(self, table: str) -> List[str]: """ Return a unique list of the relationships for this table that should requery with this table. @@ -461,14 +450,13 @@ def get_update_cascade_tables(cls, table: str) -> List[str]: """ rel = [ r.child_table - for r in cls.instances + for r in self if r.parent_table == table and r.on_update_cascade ] # make unique return list(set(rel)) - @classmethod - def get_delete_cascade_tables(cls, table: str) -> List[str]: + def get_delete_cascade_tables(self, table: str) -> List[str]: """ Return a unique list of the relationships for this table that should be deleted with this table. @@ -478,27 +466,25 @@ def get_delete_cascade_tables(cls, table: str) -> List[str]: """ rel = [ r.child_table - for r in cls.instances + for r in self if r.parent_table == table and r.on_delete_cascade ] # make unique return list(set(rel)) - @classmethod - def get_parent(cls, table: str) -> Union[str, None]: + def get_parent(self, table: str) -> Union[str, None]: """ Return the parent table for the passed-in table. :param table: The table (str) to get relationships for :returns: The name of the Parent table, or None if there is none """ - for r in cls.instances: + for r in self: if r.child_table == table and r.on_update_cascade: return r.parent_table return None - @classmethod - def parent_virtual(cls, table: str, frm: Form) -> Union[bool, None]: + def parent_virtual(self, table: str, frm: Form) -> Union[bool, None]: """ Return True if current row of parent table is virtual. @@ -506,7 +492,7 @@ def parent_virtual(cls, table: str, frm: Form) -> Union[bool, None]: :param frm: Form reference :returns: True if current row of parent table is virtual """ - for r in cls.instances: + for r in self: if r.child_table == table and r.on_update_cascade: try: return frm[r.parent_table].pk_is_virtual() @@ -514,34 +500,31 @@ def parent_virtual(cls, table: str, frm: Form) -> Union[bool, None]: return False return None - @classmethod - def get_update_cascade_fk_column(cls, table: str) -> Union[str, None]: + def get_update_cascade_fk_column(self, table: str) -> Union[str, None]: """ Return the cascade fk that filters for the passed-in table. :param table: The table name of the child :returns: The name of the cascade-fk, or None """ - for r in cls.instances: + for r in self: if r.child_table == table and r.on_update_cascade: return r.fk_column return None - @classmethod - def get_delete_cascade_fk_column(cls, table: str) -> Union[str, None]: + def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: """ Return the cascade fk that filters for the passed-in table. :param table: The table name of the child :returns: The name of the cascade-fk, or None """ - for r in cls.instances: + for r in self: if r.child_table == table and r.on_delete_cascade: return r.fk_column return None - @classmethod - def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str]: + def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, str]: """ Returns a dictionary of the `DataSet.key` and column names that use the description_column text of the given parent table in their `ElementRow` objects. @@ -557,7 +540,7 @@ def get_dependent_columns(cls, frm_reference: Form, table: str) -> Dict[str, str """ return { frm_reference[dataset].key: r.fk_column - for r in cls.instances + for r in self for dataset in frm_reference.datasets if r.parent_table == table and frm_reference[dataset].table == r.child_table @@ -1217,10 +1200,10 @@ def requery( if filtered: # Stop requery short if parent has no records or current row is virtual - parent_table = Relationship.get_parent(self.table) + parent_table = self.relationships.get_parent(self.table) if parent_table and ( not len(self.frm[parent_table].rows.index) - or Relationship.parent_virtual(self.table, self.frm) + or self.relationships.parent_virtual(self.table, self.frm) ): # purge rows self.rows = Result.set(pd.DataFrame(columns=self.rows.columns)) @@ -1294,7 +1277,7 @@ def requery_dependents( # dependents=False: no recursive dependent requery self.requery(update_elements=update_elements, requery_dependents=False) - for rel in self.frm.relationships: + for rel in self.relationships: if rel.parent_table == self.table and rel.on_update_cascade: logger.debug( f"Requerying dependent table {self.frm[rel.child_table].table}" @@ -1848,11 +1831,11 @@ def insert_record( return # Don't insert if parent has no records or is virtual - parent_table = Relationship.get_parent(self.table) + parent_table = self.relationships.get_parent(self.table) if ( parent_table and not len(self.frm[parent_table].rows) - or Relationship.parent_virtual(self.table, self.frm) + or self.relationships.parent_virtual(self.table, self.frm) ): logger.debug(f"{parent_table=} is empty or current row is virtual") return @@ -1867,7 +1850,7 @@ def insert_record( new_values[k] = v # Make sure we take into account the foreign key relationships... - for r in self.frm.relationships: + for r in self.relationships: if self.table == r.child_table and r.on_update_cascade: new_values[r.fk_column] = self.frm[r.parent_table].get_current_pk() @@ -2059,7 +2042,7 @@ def save_record( # check to see if cascading-fk has changed before we update database cascade_fk_changed = False - cascade_fk_column = Relationship.get_update_cascade_fk_column(self.table) + cascade_fk_column = self.relationships.get_update_cascade_fk_column(self.table) if cascade_fk_column: # check if fk for mapped in self.frm.element_map: @@ -2171,7 +2154,9 @@ def save_record( # that may depend on it, that otherwise wouldn't be requeried because they are # not setup as on_update_cascade. if self.description_column in changed_row_dict: - dependent_columns = Relationship.get_dependent_columns(self.frm, self.table) + dependent_columns = self.relationships.get_dependent_columns( + self.frm, self.table + ) for key, col in dependent_columns.items(): self.frm.update_fields(key, columns=[col], combo_values_only=True) if self.frm[key].column_likely_in_selector(col): @@ -2200,7 +2185,7 @@ def save_record_recursive( elements without saving if individual `DataSet._prompt_save()` is False. :returns: dict of {table : results} """ - for rel in self.frm.relationships: + for rel in self.relationships: if rel.parent_table == self.table and rel.on_update_cascade: self.frm[rel.child_table].save_record_recursive( results=results, @@ -2246,7 +2231,7 @@ def delete_record( children = [] if cascade: - children = Relationship.get_delete_cascade_tables(self.table) + children = self.relationships.get_delete_cascade_tables(self.table) msg_children = ", ".join(children) if len(children): @@ -2336,7 +2321,7 @@ def duplicate_record( child_list = [] if children: - child_list = Relationship.get_update_cascade_tables(self.table) + child_list = self.relationships.get_update_cascade_tables(self.table) msg_children = ", ".join(child_list) msg = lang.duplicate_child.format_map( @@ -2662,7 +2647,7 @@ def combobox_values( if not self.row_count: return None - rels = Relationship.get_relationships(self.table) + rels = self.relationships.get_rels_for(self.table) rel = next((r for r in rels if r.fk_column == column_name), None) if rel is None: return None @@ -2692,7 +2677,7 @@ def get_related_table_for_column(self, column: str) -> str: :param column: The column name to get related table information for :returns: The name of the related table, or the current table if none are found """ - rels = Relationship.get_relationships(self.table) + rels = self.relationships.get_rels_for(self.table) for rel in rels: if column == rel.fk_column: return rel.parent_table @@ -2716,7 +2701,7 @@ def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): columns = rows.columns # get fk descriptions - rels = Relationship.get_relationships(self.table) + rels = self.relationships.get_rels_for(self.table) for col in columns: for rel in rels: if col == rel.fk_column: @@ -2821,7 +2806,7 @@ def quick_editor( fields_layout = [[sg.Sizer(h_pixels=0, v_pixels=y_pad)]] - rels = Relationship.get_relationships(self.table) + rels = self.relationships.get_rels_for(self.table) for col in self.column_info.names(): found = False column = f"{data_key}.{col}" @@ -2951,7 +2936,7 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: # We don't want to sort by foreign keys directly - we want to sort by the # description column of the foreign table that the foreign key references tmp_column = None - rels = Relationship.get_relationships(table) + rels = self.relationships.get_rels_for(table) transformed = False for rel in rels: @@ -3202,10 +3187,6 @@ class Form: - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. :param save_quiet: (optional) Default:False. True to skip info popup on save. Error popups will still be shown. - :param update_cascade: (optional) Default:True. Requery and filter child table - on selected parent primary key. (ON UPDATE CASCADE in SQL) - :param delete_cascade: (optional) Default:True. Delete the dependent child - records if the parent table record is deleted. (ON UPDATE DELETE in SQL) :param duplicate_children: (optional) Default:True. If record has children, prompt user to choose to duplicate current record, or both. :param description_column_names: (optional) A list of names to use for the @@ -3216,9 +3197,6 @@ class Form: :param live_update: (optional) Default value is False. If True, changes made in a field will be immediately pushed to associated selectors. If False, changes will be pushed only after a save action. - :param auto_add_relationships: (optional) Controls the invocation of - auto_add_relationships. Default is True. Set it to False when creating a new - `Form` with pre-existing `Relationship` instances. :param validate_mode: Passed to `DataSet` init to set validate mode. `ss.ValidateMode.STRICT` to prevent invalid values from being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation @@ -3227,7 +3205,6 @@ class Form: """ instances: ClassVar[List[Form]] = [] # Track our instances - relationships: ClassVar[List[Relationship]] = [] # Track our relationships driver: SQLDriver bind_window: dc.InitVar[sg.Window] = None @@ -3237,14 +3214,11 @@ class Form: select_first: dc.InitVar[bool] = True prompt_save: dc.InitVar[PROMPT_SAVE_MODES] = PROMPT_MODE save_quiet: bool = False - update_cascade: bool = True - delete_cascade: bool = True duplicate_children: bool = True description_column_names: List[str] = dc.field( default_factory=lambda: ["description", "name", "title"] ) live_update: bool = False - auto_add_relationships: dc.InitVar[bool] = True validate_mode: ValidateMode = ValidateMode.RELAXED def __post_init__( @@ -3253,7 +3227,6 @@ def __post_init__( prefix_data_keys, select_first, prompt_save, - auto_add_relationships, ): Form.instances.append(self) @@ -3286,8 +3259,6 @@ def __post_init__( win_pb.update(lang.startup_datasets, 25) self.auto_add_datasets(prefix_data_keys) win_pb.update(lang.startup_relationships, 50) - if auto_add_relationships: - self.auto_add_relationships() self.requery_all( select_first=select_first, update_elements=False, requery_dependents=True ) @@ -3329,7 +3300,7 @@ def bind(self, win: sg.Window) -> None: and relationship mapping. This can happen automatically on `Form` creation with the bind parameter and is not typically called by the end user. This function literally just groups all the auto_* methods. See `Form.auto_add_tables()`, - `Form.auto_add_relationships()`, `Form.auto_map_elements()`, + `SQLDriver.auto_add_relationships()`, `Form.auto_map_elements()`, `Form.auto_map_events()`. :param win: The PySimpleGUI window @@ -3447,50 +3418,6 @@ def add_dataset( # set a default sort order self[data_key].set_search_order([description_column]) - def add_relationship( - self, - join: str, - child_table: str, - fk_column: str, - parent_table: str, - pk_column: str, - update_cascade: bool, - delete_cascade: bool, - ) -> None: - """ - Add a foreign key relationship between two dataset of the database When you - attach a database, PySimpleSQL isn't aware of the relationships contained until - dataset are added via `Form.add_data`, and the relationship of various tables is - set with this function. Note that `Form.auto_add_relationships()` will do this - automatically from the schema of the database, which also happens automatically - when a `Form` is created. - - :param join: The join type of the relationship ('LEFT JOIN', 'INNER JOIN', - 'RIGHT JOIN') - :param child_table: The child table containing the foreign key - :param fk_column: The foreign key column of the child table - :param parent_table: The parent table containing the primary key - :param pk_column: The primary key column of the parent table - :param update_cascade: Requery and filter child table results on selected parent - primary key (ON UPDATE CASCADE in SQL) - :param delete_cascade: Delete the dependent child records if the parent table - record is deleted (ON UPDATE DELETE in SQL) - :returns: None - """ - self.relationships.append( - Relationship( - join, - child_table, - fk_column, - parent_table, - pk_column, - update_cascade, - delete_cascade, - self.driver, - self, - ) - ) - def set_fk_column_cascade( self, child_table: str, @@ -3501,7 +3428,7 @@ def set_fk_column_cascade( """ Set a foreign key's update_cascade and delete_cascade behavior. - `Form.auto_add_relationships()` does this automatically from the database + `SQLDriver.auto_add_relationships()` does this automatically from the database schema. :param child_table: Child table with the foreign key. @@ -3514,7 +3441,7 @@ def set_fk_column_cascade( """ for rel in self.relationships: if rel.child_table == child_table and rel.fk_column == fk_column: - logger.info(f"Updating {fk_column=} relationship.") + logger.info(f"Updating {fk_column=} self.relationships.") if update_cascade is not None: rel.update_cascade = update_cascade if delete_cascade is not None: @@ -3560,39 +3487,6 @@ def auto_add_datasets(self, prefix_data_keys: str = "") -> None: self.add_dataset(data_key, table, pk_column, description_column) self.datasets[data_key].column_info = column_info - # Make sure to send a list of table names to requery if you want - # dependent dataset to requery automatically - def auto_add_relationships(self) -> None: - """ - Automatically add a foreign key relationship between tables of the database. - This is done by foreign key constraints within the database. Automatically - requery the child table if the parent table changes (ON UPDATE CASCADE in sql is - set) When you attach a database, PySimpleSQL isn't aware of the relationships - contained until tables are added and the relationship of various tables is set. - This happens automatically during `Form` creation. Note that - `Form.add_relationship()` can do this manually. - - :returns: None - """ - logger.info("Automatically adding foreign key relationships") - # Clear any current rels so that successive calls will not double the entries - self.relationships = [] # clear any relationships already stored - relationships = self.driver.relationships() - for r in relationships: - logger.debug( - f'Adding relationship {r["from_table"]}.{r["from_column"]} = ' - f'{r["to_table"]}.{r["to_column"]}' - ) - self.add_relationship( - "LEFT JOIN", - r["from_table"], - r["from_column"], - r["to_table"], - r["to_column"], - r["update_cascade"], - r["delete_cascade"], - ) - # Map an element to a DataSet. # Optionally a where_column and a where_value. This is useful for key,value pairs! def map_element( @@ -4088,15 +3982,15 @@ def save_records( tables = [ dataset.table for dataset in self.datasets.values() - if len(Relationship.get_update_cascade_tables(dataset.table)) - and Relationship.get_parent(dataset.table) is None + if len(self.relationships.get_update_cascade_tables(dataset.table)) + and self.relationships.get_parent(dataset.table) is None ] # default behavior, build list of top-level dataset (ones without a parent) else: tables = [ dataset.table for dataset in self.datasets.values() - if Relationship.get_parent(dataset.table) is None + if self.relationships.get_parent(dataset.table) is None ] # call save_record_recursive on tables, which saves from last to first. @@ -4235,12 +4129,12 @@ def update_actions(self, target_data_key: str = None) -> None: # Disable insert on children with no parent/virtual parent records or # edit protect mode elif ":table_insert" in m["event"]: - parent = Relationship.get_parent(data_key) + parent = self.relationships.get_parent(data_key) if parent is not None: disable = bool( not self[parent].row_count or self._edit_protect - or Relationship.parent_virtual(data_key, self) + or self.relationships.parent_virtual(data_key, self) ) else: disable = self._edit_protect @@ -4373,7 +4267,7 @@ def update_fields( mapped.element.update(values=combo_vals) elif isinstance(mapped.element, sg.Text): - rels = Relationship.get_relationships(mapped.dataset.table) + rels = self.relationships.get_rels_for(mapped.dataset.table) found = False # try to get description of linked if foreign-key for rel in rels: @@ -4585,7 +4479,7 @@ def requery_all( # then select_first/update/dependents logger.info("Requerying all datasets") for data_key in self.datasets: - if Relationship.get_parent(data_key) is None: + if self.relationships.get_parent(data_key) is None: self[data_key].requery( select_first=select_first, filtered=filtered, @@ -5693,7 +5587,7 @@ class _TtkStrictInput(ttk.Entry, _StrictInput): """Internal Ttk Entry with validate commands""" -class _PlaceholderText(abc.ABC): +class _PlaceholderText(ABC): """ An abstract class for PySimpleGUI text-entry elements that allows for the display of a placeholder text when the input is empty. @@ -5745,7 +5639,7 @@ def add_placeholder(self, placeholder: str, color: str = None, font: str = None) self.placeholder_feature_enabled = True self._add_binds() - @abc.abstractmethod + @abstractmethod def _add_binds(self): pass @@ -5797,11 +5691,11 @@ def get(self) -> str: return "" return super().get() - @abc.abstractmethod + @abstractmethod def insert_placeholder(self): pass - @abc.abstractmethod + @abstractmethod def delete_placeholder(self): pass @@ -7991,6 +7885,8 @@ class LanguagePack: "sqldriver_init": "{name} connection", "sqldriver_connecting": "Connecting to database", "sqldriver_execute": "Executing SQL commands", + "sqldriver_file_not_found_title": "Trouble finding db file", + "sqldriver_file_not_found": "Could not find file\n{file}", # ------------------------------------------------------------------------------ # Default ProgressAnimate Phrases # ------------------------------------------------------------------------------ @@ -8458,7 +8354,7 @@ def __post_init__(self): ) except ValueError: logger.debug( - f"Unable to set {self.name} column decimal precision to " + f"Unable to set {self.NAME} column decimal precision to " f"{self.domain_args[0]}" ) if len(self.domain_args) >= 2: @@ -8470,7 +8366,7 @@ def __post_init__(self): ) except ValueError: logger.debug( - f"Unable to set {self.name} column decimal scale to " + f"Unable to set {self.NAME} column decimal scale to " f"{self.domain_args[1]}" ) self.cell_format_fn: callable = lambda x: CellFormatFn.decimal_places( @@ -8672,7 +8568,7 @@ def default_row_dict(self, dataset: DataSet) -> dict: # First, check to see if the default might be a database function if self._looks_like_function(default): table = self.driver.quote_table(self.table) - # TODO: may need AS column to support all databases? + q = f"SELECT {default} AS val FROM {table};" rows = self.driver.execute(q) @@ -8687,7 +8583,7 @@ def default_row_dict(self, dataset: DataSet) -> dict: d[c.name] = default continue logger.warning( - f"There was an exception getting the default: {rows.exception}" + f"There was an exception getting the default: {rows.attrs['exception']}" ) # The stored default is a literal value, lets try to use it: @@ -8698,9 +8594,9 @@ def default_row_dict(self, dataset: DataSet) -> dict: # Perhaps our default dict does not yet support this datatype null_default = None - # return PK_PLACEHOLDER if this is a fk_relationship. + # return PK_PLACEHOLDER if this is a fk_relationships. # trick used in Combo for the pk to display placeholder - rels = Relationship.get_relationships(dataset.table) + rels = self.driver.relationships.get_rels_for(dataset.table) rel = next((r for r in rels if r.fk_column == c.name), None) if rel: null_default = PK_PLACEHOLDER @@ -8856,7 +8752,29 @@ class ReservedKeywordError(Exception): pass -class SQLDriver: +@dc.dataclass +class SqlChar: + # Each database type expects their SQL prepared in a certain way. Below are + # defaults for how various elements in the SQL string should be quoted and + # represented as placeholders. Override these in the derived class as needed to + # satisfy SQL requirements + + # The placeholder for values in the query string. This is typically '?' or'%s' + placeholder: str = "%s" # override this in derived subclass SqlChar + + # These are the quote characters for tables, columns and values. + # It varies between different databases + + # override this in derived subclass SqlChar (defaults to no quotes) + table_quote: str = "" + # override this in derived subclass SqlChar (defaults to no quotes) + column_quote: str = "" + # override this in derived subclass SqlChar (defaults to single quotes) + value_quote: str = "'" + + +@dc.dataclass +class SQLDriver(ABC): """ Abstract SQLDriver class. Derive from this class to create drivers that conform to @@ -8876,68 +8794,70 @@ class SQLDriver: pysimplesql convention, the attrs["lastrowid"] should always be None unless and INSERT query is executed with SQLDriver.execute() or a record is inserted with SQLDriver.insert_record() + + :param update_cascade: (optional) Default:True. Requery and filter child table + on selected parent primary key. (ON UPDATE CASCADE in SQL) + :param delete_cascade: (optional) Default:True. Delete the dependent child + records if the parent table record is deleted. (ON UPDATE DELETE in SQL) """ - SQL_CONSTANTS: ClassVar[List[str]] = [] + host: str = None + user: str = None + password: str = None + database: str = None + + sql_commands: str = None + sql_script: str = None + sql_script_encoding: str = "utf-8" + + update_cascade: bool = True + delete_cascade: bool = True + + sql_char: dc.InitVar[SqlChar] = SqlChar() # noqa RUF009 # --------------------------------------------------------------------- # MUST implement # in order to function # --------------------------------------------------------------------- - def __init__( - self, - name: str, - requires: List[str], - placeholder="%s", - table_quote="", - column_quote="", - value_quote="'", - ): - """ - Create a new SQLDriver instance This must be overridden in the derived class, - which must call super().__init__(), and when finished call self.win_pb.close() - to close the database. - """ - # Be sure to call super().__init__() in derived class! - self.con = None - self.name = name - self.requires = requires - self._check_reserved_keywords = True - self.win_pb = ProgressBar( - lang.sqldriver_init.format_map(LangFormat(name=name)), 100 - ) - self.win_pb.update(lang.sqldriver_connecting, 0) - # Each database type expects their SQL prepared in a certain way. Below are - # defaults for how various elements in the SQL string should be quoted and - # represented as placeholders. Override these in the derived class as needed to - # satisfy SQL requirements + # --------------------------------------------------------------------- + # ClassVars, replace in derived subclass with your own + # --------------------------------------------------------------------- + NAME: ClassVar[str] = "SQLDriver" + REQUIRES: ClassVar[List[str]] = None - # The placeholder for values in the query string. This is typically '?' or'%s' - self.placeholder = placeholder # override this in derived __init__() + # TODO: Document these + COLUMN_CLASS_MAP: ClassVar[Dict[str, ColumnClass]] = {} + SQL_CONSTANTS: ClassVar[List[str]] = [] + _CHECK_RESERVED_KEYWORDS: ClassVar[bool] = True - # These are the quote characters for tables, columns and values. - # It varies between different databases + def __post_init__(self, sql_char): + # if derived subclass implements __init__, call `super()__post_init__()` + # unpack quoting + self.placeholder = sql_char.placeholder + self.quote_table_char = sql_char.table_quote + self.quote_column_char = sql_char.column_quote + self.quote_value_char = sql_char.value_quote - # override this in derived __init__() (defaults to no quotes) - self.quote_table_char = table_quote - # override this in derived __init__() (defaults to no quotes) - self.quote_column_char = column_quote - # override this in derived __init__() (defaults to single quotes) - self.quote_value_char = value_quote + self.win_pb = ProgressBar( + lang.sqldriver_init.format_map(LangFormat(name=self.NAME)), 100 + ) + self.win_pb.update(lang.sqldriver_connecting, 0) + self._import_required_modules() + self._init_db() + self.relationships = RelationshipStore(self) + self.auto_add_relationships() + self.win_pb.close() - def check_reserved_keywords(self, value: bool) -> None: - """ - SQLDrivers can check to make sure that field names respect their own reserved - keywords. By default, all SQLDrivers will check for their respective keywords. - You can choose to disable this feature with this method. + @abstractmethod + def _import_required_modules(self) -> None: + pass - :param value: True to check for reserved keywords in field names, false to skip - this check - :return: None - """ - self._check_reserved_keywords = value + @abstractmethod + def _init_db(self) -> None: + pass + @abstractmethod def connect(self, *args, **kwargs): """ Connect to a database. @@ -8948,8 +8868,8 @@ def connect(self, *args, **kwargs): Implementation varies by database, you may need only one parameter, or several depending on how a connection is established with the target database. """ - raise NotImplementedError + @abstractmethod def execute( self, query, @@ -8968,22 +8888,26 @@ def execute( have exceptions and commit/rollbacks happen automatically :return: """ - raise NotImplementedError + @abstractmethod def execute_script(self, script: str, encoding: str): - raise NotImplementedError + pass + @abstractmethod def get_tables(self): - raise NotImplementedError + pass + @abstractmethod def column_info(self, table): - raise NotImplementedError + pass + @abstractmethod def pk_column(self, table): - raise NotImplementedError + pass - def relationships(self): - raise NotImplementedError + @abstractmethod + def get_relationships(self): + pass # --------------------------------------------------------------------- # SHOULD implement @@ -9017,7 +8941,7 @@ def check_keyword(self, keyword: str, key: str = None) -> None: if key is None: # First try using the name of the driver - key = self.name.lower() if self.name.lower() in RESERVED else "common" + key = self.NAME.lower() if self.NAME.lower() in RESERVED else "common" if keyword.upper() in RESERVED[key] or keyword.upper in RESERVED["common"]: raise ReservedKeywordError( @@ -9094,13 +9018,12 @@ def generate_join_clause(self, dataset: DataSet) -> str: :rtype: str """ join = "" - for r in dataset.frm.relationships: + for r in self.relationships: if dataset.table == r.child_table: join += f" {self.relationship_to_join_clause(r)}" return join if not dataset.join_clause else dataset.join_clause - @staticmethod - def generate_where_clause(dataset: DataSet) -> str: + def generate_where_clause(self, dataset: DataSet) -> str: """ Generates a where clause from the Relationships that have been set, as well as the DataSet's where clause. @@ -9111,7 +9034,7 @@ def generate_where_clause(dataset: DataSet) -> str: :rtype: str """ where = "" - for r in dataset.frm.relationships: + for r in self.relationships: if dataset.table == r.child_table and r.on_update_cascade: table = dataset.table parent_pk = dataset.frm[r.parent_table].get_current(r.pk_column) @@ -9171,7 +9094,7 @@ def delete_record(self, dataset: DataSet, cascade=True): # Delete child records first! if cascade: recursion = 0 - result = self.delete_record_recursive( + result = self._delete_record_recursive( dataset, "", where_clause, table, pk_column, recursion ) @@ -9181,10 +9104,10 @@ def delete_record(self, dataset: DataSet, cascade=True): q = delete_clause + where_clause + ";" return self.execute(q) - def delete_record_recursive( + def _delete_record_recursive( self, dataset: DataSet, inner_join, where_clause, parent, pk_column, recursion ): - for child in Relationship.get_delete_cascade_tables(dataset.table): + for child in self.relationships.get_delete_cascade_tables(dataset.table): # Check to make sure we arn't at recursion limit recursion += 1 # Increment, since this is a child if recursion >= DELETE_CASCADE_RECURSION_LIMIT: @@ -9192,7 +9115,7 @@ def delete_record_recursive( # Get data for query fk_column = self.quote_column( - Relationship.get_delete_cascade_fk_column(child) + self.relationships.get_delete_cascade_fk_column(child) ) pk_column = self.quote_column(dataset.frm[child].pk_column) child_table = self.quote_table(child) @@ -9206,7 +9129,7 @@ def delete_record_recursive( ) # Call function again to create recursion - result = self.delete_record_recursive( + result = self._delete_record_recursive( dataset.frm[child], inner_join_clause, where_clause, @@ -9298,7 +9221,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: # Next, duplicate the child records! if children: for _ in dataset.frm.datasets: - for r in dataset.frm.relationships: + for r in self.relationships: if ( r.parent_table == dataset.table and r.on_update_cascade @@ -9422,18 +9345,108 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # Probably won't need to implement the following functions # --------------------------------------------------------------------- - def import_failed(self, exception) -> None: + def add_relationship( + self, + join: str, + child_table: str, + fk_column: str, + parent_table: str, + pk_column: str, + update_cascade: bool, + delete_cascade: bool, + ) -> None: + """ + Add a foreign key relationship between two dataset of the database When you + attach a database, PySimpleSQL isn't aware of the relationships contained until + dataset are added via `Form.add_data`, and the relationship of various tables is + set with this function. Note that `SQLDriver.auto_add_relationships()` will do + this automatically from the schema of the database, which also happens + automatically when a `SQLDriver` is created. + + :param join: The join type of the relationship ('LEFT JOIN', 'INNER JOIN', + 'RIGHT JOIN') + :param child_table: The child table containing the foreign key + :param fk_column: The foreign key column of the child table + :param parent_table: The parent table containing the primary key + :param pk_column: The primary key column of the parent table + :param update_cascade: Requery and filter child table results on selected parent + primary key (ON UPDATE CASCADE in SQL) + :param delete_cascade: Delete the dependent child records if the parent table + record is deleted (ON UPDATE DELETE in SQL) + :returns: None + """ + self.relationships.append( + Relationship( + join, + child_table, + fk_column, + parent_table, + pk_column, + update_cascade, + delete_cascade, + self, + ) + ) + + # Make sure to send a list of table names to requery if you want + # dependent dataset to requery automatically + def auto_add_relationships(self) -> None: + """ + Automatically add a foreign key relationship between tables of the database. + This is done by foreign key constraints within the database. Automatically + requery the child table if the parent table changes (ON UPDATE CASCADE in sql is + set) When you attach a database, PySimpleSQL isn't aware of the relationships + contained until tables are added and the relationship of various tables is set. + This happens automatically during `Form` creation. Note that + `Form.add_relationship()` can do this manually. + + :returns: None + """ + logger.info("Automatically adding foreign key relationships") + # Clear any current rels so that successive calls will not double the entries + self.relationships = RelationshipStore( + self + ) # clear any relationships already stored + relationships = self.get_relationships() + for r in relationships: + logger.debug( + f'Adding relationship {r["from_table"]}.{r["from_column"]} = ' + f'{r["to_table"]}.{r["to_column"]}' + ) + self.add_relationship( + "LEFT JOIN", + r["from_table"], + r["from_column"], + r["to_table"], + r["to_column"], + r["update_cascade"], + r["delete_cascade"], + ) + + def check_reserved_keywords(self, value: bool) -> None: + """ + SQLDrivers can check to make sure that field names respect their own reserved + keywords. By default, all SQLDrivers will check for their respective keywords. + You can choose to disable this feature with this method. + + :param value: True to check for reserved keywords in field names, false to skip + this check + :return: None + """ + self._CHECK_RESERVED_KEYWORDS = value + + def _import_failed(self, exception) -> None: popup = Popup() - requires = ", ".join(self.requires) + requires = ", ".join(self.REQUIRES) popup.ok( lang.import_module_failed_title, lang.import_module_failed.format_map( - LangFormat(name=self.name, requires=requires, exception=exception) + LangFormat(name=self.NAME, requires=requires, exception=exception) ), ) exit(0) - def parse_domain(self, domain): + def _parse_domain(self, domain): domain_parts = domain.split("(") domain_name = domain_parts[0].strip().upper() @@ -9445,7 +9458,7 @@ def parse_domain(self, domain): return domain_name, domain_args - def get_column_class(self, domain) -> Union[ColumnClass, None]: + def _get_column_class(self, domain) -> Union[ColumnClass, None]: if domain in self.COLUMN_CLASS_MAP: return self.COLUMN_CLASS_MAP[domain] logger.info(f"Mapping {domain} to generic Column class") @@ -9455,11 +9468,22 @@ def get_column_class(self, domain) -> Union[ColumnClass, None]: # -------------------------------------------------------------------------------------- # SQLITE3 DRIVER # -------------------------------------------------------------------------------------- +@dc.dataclass class Sqlite(SQLDriver): """ The SQLite driver supports SQLite3 databases. """ + global sqlite3 # noqa PLW0603 + import sqlite3 + + sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + placeholder="?", table_quote='"', column_quote='"' + ) + + NAME: ClassVar[str] = "SQLite" + REQUIRES: ClassVar[str] = ["sqlite3"] + DECIMAL_DOMAINS: ClassVar[List[str]] = ["DECIMAL", "DECTEXT", "MONEY", "NUMERIC"] COLUMN_CLASS_MAP: ClassVar[List[str]] = {} @@ -9473,59 +9497,93 @@ class Sqlite(SQLDriver): def __init__( self, - db_path=None, + database: Union[ + str, + Path, + InMemory, + sqlite3.Connection, + ] = None, + *, + sql_commands=None, sql_script=None, sql_script_encoding: str = "utf-8", - sqlite3_database=None, - sql_commands=None, + update_cascade: bool = True, + delete_cascade: bool = True, + sql_char: SqlChar = sql_char, + create_file: bool = True, + skip_sql_if_db_exists: bool = True, ): - super().__init__( - name="SQLite", - requires=["sqlite3"], - placeholder="?", - table_quote='"', - column_quote='"', - ) + """ + :param update_cascade: (optional) Default:True. Requery and filter child table + on selected parent primary key. (ON UPDATE CASCADE in SQL) + :param delete_cascade: (optional) Default:True. Delete the dependent child + records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + """ + self._database = str(database) + self.sql_commands = sql_commands + self.sql_script = sql_script + self.sql_script_encoding = sql_script_encoding + self.update_cascade = update_cascade + self.delete_cascade = delete_cascade + self.create_file = create_file + self.skip_sql_if_db_exists = skip_sql_if_db_exists + + super().__post_init__(sql_char) - self.import_required_modules() + def _import_required_modules(self): + # Sqlite needs Sqlite3.Connection for a type-hint, so we already imported + pass + def _init_db(self) -> None: # register adapters and converters self._register_type_callables() - new_database = False - if db_path is not None: - logger.info(f"Opening database: {db_path}") - new_database = not os.path.isfile(db_path) - self.connect(db_path) # Open our database + # if str, try opening + if isinstance(self._database, str): + logger.info(f"Opening database: {self._database}") + new_database = not os.path.isfile(self._database) + if self._database != ":memory:" and new_database and not self.create_file: + popup = Popup() + popup.ok( + lang.sqldriver_file_not_found_title, + lang.sqldriver_file_not_found.format_map( + LangFormat(file=self._database) + ), + ) + exit(0) + self.connect(self._database) # Open our database - self.imported_database = False - if sqlite3_database is not None: - self.con = sqlite3_database + # or use passed preexisting connection + elif isinstance(self._database, sqlite3.Connection): + self.con = self._database new_database = False - self.imported_database = True self.win_pb.update(lang.sqldriver_execute, 50) self.con.row_factory = sqlite3.Row - if sql_commands is not None and new_database: + + # execute sql + if ( + not self.skip_sql_if_db_exists + or self.sql_commands is not None + and new_database + ): # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") - logger.debug(sql_commands) - self.con.executescript(sql_commands) + logger.debug(self.sql_commands) + self.con.executescript(self.sql_commands) self.con.commit() - if sql_script is not None and new_database: + if ( + not self.skip_sql_if_db_exists + or self.sql_script is not None + and new_database + ): # run SQL script from the file if the database does not yet exist logger.info("Executing sql script from file passed in") - self.execute_script(sql_script, sql_script_encoding) + self.execute_script(self.sql_script, self.sql_script_encoding) - self.db_path = db_path - self.win_pb.close() - - def import_required_modules(self): - global sqlite3 # noqa PLW0603 - try: - import sqlite3 - except ModuleNotFoundError as e: - self.import_failed(e) + @property + def _imported_database(self): + return isinstance(self._database, sqlite3.Connection) def connect(self, database): self.con = sqlite3.connect( @@ -9576,9 +9634,9 @@ def execute_script(self, script, encoding): def close(self): # Only do cleanup if this is not an imported database - if not self.imported_database: + if not self._imported_database: # optimize the database for long-term benefits - if self.db_path != ":memory:": + if self._database != ":memory:": q = "PRAGMA optimize;" self.con.execute(q) # Close the connection @@ -9599,8 +9657,8 @@ def column_info(self, table): names = [] col_info = ColumnInfo(self, table) for _, row in rows.iterrows(): - domain, domain_args = self.parse_domain(row["type"]) - col_class = self.get_column_class(domain) or Column + domain, domain_args = self._parse_domain(row["type"]) + col_class = self._get_column_class(domain) or Column # TODO: should we exclude hidden columns? # if row["hidden"] == 1: # continue @@ -9630,7 +9688,7 @@ def pk_column(self, table): result = self.execute(q, silent=True) return result.loc[result["pk"] == 1, "name"].iloc[0] - def relationships(self): + def get_relationships(self): # Return a list of dicts {from_table,to_table,from_column,to_column,requery} relationships = [] tables = self.get_tables() @@ -9657,9 +9715,9 @@ def relationships(self): relationships.append(dic) return relationships - def get_column_class(self, domain) -> Union[ColumnClass, None]: + def _get_column_class(self, domain) -> Union[ColumnClass, None]: if self.COLUMN_CLASS_MAP: - col_class = super().get_column_class(domain) + col_class = super()._get_column_class(domain) if col_class is not None: return col_class if "DATETIME" in domain or "TIMESTAMP" in domain: @@ -9700,6 +9758,7 @@ def _register_type_callables(self): # -------------------------------------------------------------------------------------- # The CSV driver uses SQlite3 in the background # to use pysimplesql directly with CSV files +@dc.dataclass class Flatfile(Sqlite): """ @@ -9744,17 +9803,6 @@ def __init__( virtual primary key column that was created will not be written to the flatfile. """ - # First up the SQLite driver that we derived from - super().__init__(":memory:") # use an in-memory database - - # Change Sqlite Sqldriver init set values to Flatfile-specific - self.name = "Flatfile" - self.requires = ["csv,sqlite3"] - self.placeholder = "?" # update - - self.import_required_modules() - - self.connect(":memory:") self.file_path = file_path self.delimiter = delimiter self.quotechar = quotechar @@ -9762,13 +9810,25 @@ def __init__( self.pk_col = pk_col if pk_col is not None else "pk" self.pk_col_is_virtual = False self.table = table if table is not None else "Flatfile" + + # First up the SQLite driver that we derived from + super().__init__(":memory:") # use an in-memory database + + # Change Sqlite Sqldriver init set values to Flatfile-specific + self.NAME = "Flatfile" + self.REQUIRES = ["csv,sqlite3"] + self.placeholder = "?" # update + + def _init_db(self): + self.connect(":memory:") + self.con.row_factory = sqlite3.Row # Store any text up to the header line, so they can be restored self.pre_header = [] # Open the CSV file and read the header row to get column names - with open(file_path, "r") as f: + with open(self.file_path, "r") as f: reader = csv.reader(f, delimiter=self.delimiter, quotechar=self.quotechar) # skip lines as determined by header_row_num for _i in range(self.header_row_num): @@ -9816,16 +9876,15 @@ def __init__( self.execute(query, row) self.commit() # commit them all at the end - self.win_pb.close() - def import_required_modules(self): + def _import_required_modules(self): global csv # noqa PLW0603 global sqlite3 # noqa PLW0603 try: import csv import sqlite3 except ModuleNotFoundError as e: - self.import_failed(e) + self._import_failed(e) def save_record( self, dataset: DataSet, changed_row: dict, where_clause: str = None @@ -9870,11 +9929,17 @@ def save_record( # -------------------------------------------------------------------------------------- # MYSQL DRIVER # -------------------------------------------------------------------------------------- +@dc.dataclass class Mysql(SQLDriver): """ The Mysql driver supports MySQL databases. """ + tinyint1_is_boolean: bool = True + + NAME: ClassVar[str] = "MySQL" + REQUIRES: ClassVar[List[str]] = ["mysql-connector-python"] + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIT": BoolCol, "BIGINT": IntCol, @@ -9908,36 +9973,16 @@ class Mysql(SQLDriver): "CURRENT_TIMESTAMP", ] - def __init__( - self, - host, - user, - password, - database, - sql_script=None, - sql_script_encoding: str = "utf-8", - sql_commands=None, - tinyint1_is_boolean=True, - ): - super().__init__(name="MySQL", requires=["mysql-connector-python"]) - - self.import_required_modules() - - self.name = "MySQL" # is this redundant? - self.host = host - self.user = user - self.password = password - self.database = database - self.tinyint1_is_boolean = tinyint1_is_boolean + def _init_db(self): self.con = self.connect() self.win_pb.update(lang.sqldriver_execute, 50) - if sql_commands is not None: + if self.sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") - logger.debug(sql_commands) + logger.debug(self.sql_commands) cursor = self.con.cursor() - for result in cursor.execute(sql_commands, multi=True): + for result in cursor.execute(self.sql_commands, multi=True): if result.with_rows: print("Rows produced by statement '{}':".format(result.statement)) print(result.fetchall()) @@ -9949,19 +9994,17 @@ def __init__( ) self.con.commit() cursor.close() - if sql_script is not None: + if self.sql_script is not None: # run SQL script from the file if the database does not yet exist logger.info("Executing sql script from file passed in") - self.execute_script(sql_script, sql_script_encoding) - - self.win_pb.close() + self.execute_script(self.sql_script, self.sql_script_encoding) - def import_required_modules(self): + def _import_required_modules(self): global mysql try: import mysql.connector except ModuleNotFoundError as e: - self.import_failed(e) + self._import_failed(e) def connect(self, retries=3): attempt = 0 @@ -10048,7 +10091,7 @@ def column_info(self, table): ) # Capitalize and get rid of the extra information of the row type # I.e. varchar(255) becomes VARCHAR - domain, domain_args = self.parse_domain(type_value) + domain, domain_args = self._parse_domain(type_value) # TODO, think about an Enum or SetCol # # domain_args for enum/set are actually a list @@ -10063,7 +10106,7 @@ def column_info(self, table): col_class = BoolCol else: - col_class = self.get_column_class(domain) or Column + col_class = self._get_column_class(domain) or Column if col_class == DecimalCol: domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] elif col_class in [FloatCol, IntCol]: @@ -10094,7 +10137,7 @@ def pk_column(self, table): rows = self.execute(query, silent=True) return rows.iloc[0]["Column_name"] - def relationships(self): + def get_relationships(self): # Return a list of dicts {from_table,to_table,from_column,to_column,requery} tables = self.get_tables() relationships = [] @@ -10180,26 +10223,30 @@ def _insert_duplicate_record( # MariaDB is a fork of MySQL and backward compatible. It technically does not need its # own driver, but that could change in the future, plus having its own named class makes # it more clear for the end user. +@dc.dataclass class Mariadb(Mysql): """ The Mariadb driver supports MariaDB databases. """ - def __init__( - self, host, user, password, database, sql_script=None, sql_commands=None - ): - super().__init__(host, user, password, database, sql_script, sql_commands) - self.name = "MariaDB" + NAME: ClassVar[str] = "MariaDB" # -------------------------------------------------------------------------------------- # POSTGRES DRIVER # -------------------------------------------------------------------------------------- +@dc.dataclass class Postgres(SQLDriver): """ The Postgres driver supports PostgreSQL databases. """ + sql_char: dc.InitVar[SqlChar] = SqlChar(table_quote='"') # noqa RUF009 + sync_sequences: bool = False + + NAME: ClassVar[str] = "Postgres" + REQUIRES: ClassVar[List[str]] = ["psycopg2", "psycopg2.extras"] + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIGINT": IntCol, "BIGSERIAL": IntCol, @@ -10233,34 +10280,14 @@ class Postgres(SQLDriver): "USER", ] - def __init__( - self, - host, - user, - password, - database, - sql_script=None, - sql_script_encoding: str = "utf-8", - sql_commands=None, - sync_sequences=False, - ): - super().__init__( - name="Postgres", requires=["psycopg2", "psycopg2.extras"], table_quote='"' - ) - - self.import_required_modules() - - self.host = host - self.user = user - self.password = password - self.database = database + def _init_db(self): self.con = self.connect() # experiment to see if I can make a nocase collation # query ="CREATE COLLATION NOCASE (provider = icu, locale = 'und-u-ks-level2');" # self.execute(query) - if sync_sequences: + if self.sync_sequences: # synchronize the sequences with the max pk for each table. This is useful # if manual records were inserted without calling nextval() to update the # sequencer @@ -10291,27 +10318,28 @@ def __init__( self.execute(q, silent=True, auto_commit_rollback=True) self.win_pb.update(lang.sqldriver_execute, 50) - if sql_commands is not None: + + if self.sql_script is not None: + # run SQL script from the file if the database does not yet exist + logger.info("Executing sql script from file passed in") + self.execute_script(self.sql_script, self.sql_script_encoding) + + if self.sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") - logger.debug(sql_commands) + logger.debug(self.sql_commands) cursor = self.con.cursor() - cursor.execute(sql_commands) + cursor.execute(self.sql_commands) self.con.commit() cursor.close() - if sql_script is not None: - # run SQL script from the file if the database does not yet exist - logger.info("Executing sql script from file passed in") - self.execute_script(sql_script, sql_script_encoding) - self.win_pb.close() - def import_required_modules(self): + def _import_required_modules(self): global psycopg2 # noqa PLW0603 try: import psycopg2 import psycopg2.extras except ModuleNotFoundError as e: - self.import_failed(e) + self._import_failed(e) def connect(self, retries=3): attempt = 0 @@ -10394,7 +10422,7 @@ def column_info(self, table: str) -> ColumnInfo: for _, row in rows.iterrows(): name = row["column_name"] domain = row["data_type"].upper() - col_class = self.get_column_class(domain) or Column + col_class = self._get_column_class(domain) or Column domain_args = [] if col_class == DecimalCol: domain_args = [row["numeric_precision"], row["numeric_scale"]] @@ -10433,7 +10461,7 @@ def pk_column(self, table): rows = self.execute(query, silent=True) return rows.iloc[0]["column_name"] - def relationships(self): + def get_relationships(self): # Return a list of dicts {from_table,to_table,from_column,to_column,requery} tables = self.get_tables() relationships = [] @@ -10521,11 +10549,19 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- # MS SQLSERVER DRIVER # -------------------------------------------------------------------------------------- +@dc.dataclass class Sqlserver(SQLDriver): """ The Sqlserver driver supports Microsoft SQL Server databases. """ + sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + placeholder="?", table_quote="[]" + ) + + NAME: ClassVar[str] = "Sqlserver" + REQUIRES: ClassVar[List[str]] = ["pyodbc"] + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIGINT": IntCol, "BIT": BoolCol, @@ -10562,48 +10598,29 @@ class Sqlserver(SQLDriver): "USER", ] - def __init__( - self, - host, - user, - password, - database, - sql_script=None, - sql_script_encoding: str = "utf-8", - sql_commands=None, - ): - super().__init__( - name="Sqlserver", requires=["pyodbc"], table_quote="[]", placeholder="?" - ) - - self.import_required_modules() - self.name = "Sqlserver" # is this redundant? - self.host = host - self.user = user - self.password = password - self.database = database + def _init_db(self): self.con = self.connect() - if sql_commands is not None: + if self.sql_script is not None: + # run SQL script from the file if the database does not yet exist + logger.info("Executing sql script from file passed in") + self.execute_script(self.sql_script, self.sql_script_encoding) + + if self.sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") - logger.debug(sql_commands) + logger.debug(self.sql_commands) cursor = self.con.cursor() - cursor.execute(sql_commands) + cursor.execute(self.sql_commands) self.con.commit() cursor.close() - if sql_script is not None: - # run SQL script from the file if the database does not yet exist - logger.info("Executing sql script from file passed in") - self.execute_script(sql_script, sql_script_encoding) - self.win_pb.close() - def import_required_modules(self): + def _import_required_modules(self): global pyodbc # noqa PLW0603 try: import pyodbc except ModuleNotFoundError as e: - self.import_failed(e) + self._import_failed(e) def connect(self, retries=3, timeout=3): attempt = 0 @@ -10713,7 +10730,7 @@ def column_info(self, table): for _, row in rows.iterrows(): name = row["COLUMN_NAME"] domain = row["DATA_TYPE"].upper() - col_class = self.get_column_class(domain) or Column + col_class = self._get_column_class(domain) or Column domain_args = [] if col_class == DecimalCol: domain_args = [row["NUMERIC_PRECISION"], row["NUMERIC_SCALE"]] @@ -10749,7 +10766,7 @@ def column_info(self, table): return col_info - def relationships(self): + def get_relationships(self): # Return a list of dicts {from_table,to_table,from_column,to_column,requery} tables = self.get_tables() relationships = [] @@ -10840,6 +10857,7 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- # MS ACCESS DRIVER # -------------------------------------------------------------------------------------- +@dc.dataclass class MSAccess(SQLDriver): """ The MSAccess driver supports Microsoft Access databases. @@ -10852,6 +10870,13 @@ class MSAccess(SQLDriver): frm[DATASET KEY].column_info[COLUMN NAME].scale = 2 """ + sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + placeholder="?", table_quote="[]" + ) + + NAME: ClassVar[str] = "MSAccess" + REQUIRES: ClassVar[List[str]] = ["Jype1"] + COLUMN_CLASS_MAP: ClassVar[List[str]] = { "BIG_INT": IntCol, "BOOLEAN": BoolCol, @@ -10863,11 +10888,15 @@ class MSAccess(SQLDriver): def __init__( self, - database_file, + database_file: Union[str, Path], + *, overwrite_file: bool = False, - sql_commands: str = None, - sql_script=None, + sql_script: str = None, sql_script_encoding: str = "utf-8", + sql_commands: str = None, + update_cascade: bool = True, + delete_cascade: bool = True, + sql_char: SqlChar = sql_char, infer_datetype_from_default_function: bool = True, use_newer_jackcess: bool = False, ): @@ -10884,6 +10913,10 @@ def __init__( database. :param sql_script_encoding: The encoding of the SQL script file. Defaults to 'utf-8'. + :param update_cascade: (optional) Default:True. Requery and filter child table + on selected parent primary key. (ON UPDATE CASCADE in SQL) + :param delete_cascade: (optional) Default:True. Delete the dependent child + records if the parent table record is deleted. (ON UPDATE DELETE in SQL) :param infer_datetype_from_default_function: If True, specializes a DateTime column by examining the column's default function. A DateTime column with '=Date()' will be treated as a 'DateCol', and '=Time()' will be treated as a @@ -10893,14 +10926,19 @@ def __init__( columns. Defaults to False. """ - super().__init__( - name="MSAccess", requires=["Jype1"], table_quote="[]", placeholder="?" - ) - self.import_required_modules() - self.database_file = database_file + self.database_file = str(database_file) + self.overwrite_file = overwrite_file + self.sql_script = sql_script + self.sql_script_encoding = sql_script_encoding + self.sql_commands = sql_commands + self.update_cascade = update_cascade + self.delete_cascade = delete_cascade self.infer_datetype_from_default_function = infer_datetype_from_default_function self.use_newer_jackcess = use_newer_jackcess + super().__post_init__(sql_char) + + def _init_db(self): if not self.start_jvm(): logger.debug("Failed to start jvm") exit() @@ -10909,13 +10947,13 @@ def __init__( create_access_file = False if not os.path.exists(self.database_file): create_access_file = True - elif os.path.exists(self.database_file) and overwrite_file: + elif os.path.exists(self.database_file) and self.overwrite_file: text = sg.popup_get_text(lang.overwrite, title=lang.overwrite_title) if text == lang.overwrite_prompt: create_access_file = True else: - sql_script = None - sql_commands = None + self.sql_script = None + self.sql_commands = None if create_access_file: self._create_access_file() @@ -10924,28 +10962,31 @@ def __init__( self.con = self.connect() self.win_pb.update(lang.sqldriver_execute, 50) - if sql_commands is not None: + + if self.sql_script is not None: + # run SQL script from the file if the database does not yet exist + logger.info("Executing sql script from file passed in") + self.execute_script(self.sql_script, self.sql_script_encoding) + if self.sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") - logger.debug(sql_commands) - queries = sql_commands.split(";") # Split the query string by semicolons + logger.debug(self.sql_commands) + queries = self.sql_commands.split( + ";" + ) # Split the query string by semicolons for query in queries: self.execute(query) - if sql_script is not None: - # run SQL script from the file if the database does not yet exist - logger.info("Executing sql script from file passed in") - self.execute_script(sql_script, sql_script_encoding) - self.win_pb.close() import os import sys - def import_required_modules(self): + def _import_required_modules(self): global jpype # noqa PLW0603 try: import jpype # pip install JPype1 + import jpype.imports except ModuleNotFoundError as e: - self.import_failed(e) + self._import_failed(e) def start_jvm(self): # Get the path to the 'lib' folder @@ -11150,7 +11191,7 @@ def get_tables(self): return tables - def relationships(self): + def get_relationships(self): # Get the mapping of uppercase table and column names to their original case table_mapping = {table.upper(): table for table in self.get_tables()} column_mappings = { From 4ad29a474dbfa72026a7bfe175e6e44d29fd49a6 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:34:00 -0400 Subject: [PATCH 088/121] Fixes for MsAccess Don't return numpy types for rows Fix function handling Register some adapters for date/datetime/tmie --- pysimplesql/pysimplesql.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1569b7cc..b1dce249 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -11013,6 +11013,8 @@ def start_jvm(self): jpype.startJVM( jpype.getDefaultJVMPath(), "-ea", f"-Djava.class.path={classpath}" ) + global java + import java return True return True @@ -11038,6 +11040,7 @@ def execute( if values: stmt = self.con.prepareStatement(query) for index, value in enumerate(values, start=1): + value = self.adapt(value) stmt.setObject(index, value) has_result_set = stmt.execute() else: @@ -11099,7 +11102,7 @@ def execute( res = self.execute("SELECT @@IDENTITY AS ID") lastrowid = res.iloc[0]["ID"] - return Result.set(rows, lastrowid, exception, column_info) + return Result.set([dict(row) for row in rows], lastrowid, exception, column_info) stmt.getUpdateCount() return Result.set([], None, exception, column_info) @@ -11143,17 +11146,17 @@ def column_info(self, table): name = str(rs.getString("column_name")) domain = str(rs.getString("TYPE_NAME")).upper() notnull = str(rs.getString("IS_NULLABLE")) == "NO" - default = str(rs.getString("COLUMN_DEF")) + default = str(rs.getString("COLUMN_DEF")).lstrip("=") pk = name in pk_columns generated = str(rs.getString("IS_GENERATEDCOLUMN")) == "YES" - col_class = self.get_column_class(domain) or Column + col_class = self._get_column_class(domain) or Column domain_args = [] # handling Date/Time columns, since they are all reported as DateTime if self.infer_datetype_from_default_function and col_class == DateTimeCol: - if default == "=Date()": + if default == "DATE()": col_class = DateCol - elif default == "=Time()": + elif default == "TIME()": col_class = TimeCol if col_class in [DecimalCol, FloatCol, IntCol, StrCol]: domain_args = [str(rs.getString("COLUMN_SIZE"))] @@ -11309,6 +11312,15 @@ def _create_access_file(self): print("Error creating access file:", e) return False return True + + def adapt(self, value): + if isinstance(value, dt.date): + return java.sql.Date@value + if isinstance(value, dt.datetime): + return java.sql.Timestamp@value + if isinstance(value, dt.time): + return java.sql.Time@value + return value # -------------------------- From 02494a0c686935aaa0e74d5cd644a8317c411a63 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:37:15 -0400 Subject: [PATCH 089/121] Ruff/Black fixes --- pysimplesql/pysimplesql.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index b1dce249..84512f33 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -8583,7 +8583,8 @@ def default_row_dict(self, dataset: DataSet) -> dict: d[c.name] = default continue logger.warning( - f"There was an exception getting the default: {rows.attrs['exception']}" + "There was an exception getting the default: " + f"{rows.attrs['exception']}" ) # The stored default is a literal value, lets try to use it: @@ -11013,8 +11014,9 @@ def start_jvm(self): jpype.startJVM( jpype.getDefaultJVMPath(), "-ea", f"-Djava.class.path={classpath}" ) - global java + global java # noqa PLW0603 import java + return True return True @@ -11040,8 +11042,8 @@ def execute( if values: stmt = self.con.prepareStatement(query) for index, value in enumerate(values, start=1): - value = self.adapt(value) - stmt.setObject(index, value) + adapted_value = self.adapt(value) + stmt.setObject(index, adapted_value) has_result_set = stmt.execute() else: stmt = self.con.createStatement() @@ -11102,7 +11104,9 @@ def execute( res = self.execute("SELECT @@IDENTITY AS ID") lastrowid = res.iloc[0]["ID"] - return Result.set([dict(row) for row in rows], lastrowid, exception, column_info) + return Result.set( + [dict(row) for row in rows], lastrowid, exception, column_info + ) stmt.getUpdateCount() return Result.set([], None, exception, column_info) @@ -11312,14 +11316,14 @@ def _create_access_file(self): print("Error creating access file:", e) return False return True - + def adapt(self, value): if isinstance(value, dt.date): - return java.sql.Date@value + return java.sql.Date @ value if isinstance(value, dt.datetime): - return java.sql.Timestamp@value + return java.sql.Timestamp @ value if isinstance(value, dt.time): - return java.sql.Time@value + return java.sql.Time @ value return value From 90f313085420f457a24b59ee585011eed75f7bf7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:27:50 -0400 Subject: [PATCH 090/121] Move MsAccess adapters/converters to an extendable framework --- pysimplesql/pysimplesql.py | 77 ++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 84512f33..d1cadec4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -11017,6 +11017,9 @@ def start_jvm(self): global java # noqa PLW0603 import java + self._register_default_adapters() + self._register_default_converters() + return True return True @@ -11069,33 +11072,7 @@ def execute( for i in range(1, column_count + 1): column_name = str(metadata.getColumnName(i)) value = rs.getObject(i) - - if isinstance(value, jpype.JPackage("java").lang.String): - value = str(value) - elif isinstance(value, jpype.JPackage("java").lang.Integer): - value = int(value) - elif isinstance(value, jpype.JPackage("java").math.BigDecimal): - value = float(value.doubleValue()) - elif isinstance(value, jpype.JPackage("java").lang.Double): - value = float(value) - if isinstance(value, jpype.JPackage("java").sql.Timestamp): - timestamp_str = value.toInstant().toString()[:-1] - if "." in timestamp_str: - timestamp_format = TIMESTAMP_FORMAT_MICROSECOND - else: - timestamp_format = TIMESTAMP_FORMAT - dt_value = dt.datetime.strptime(timestamp_str, timestamp_format) - value = dt_value.strftime(DATE_FORMAT) - elif isinstance(value, jpype.JPackage("java").sql.Date): - date_str = value.toString() - value = dt.datetime.strptime(date_str, DATE_FORMAT).date() - elif isinstance(value, jpype.JPackage("java").sql.Time): - time_str = value.toString() - value = dt.datetime.strptime(time_str, TIME_FORMAT).time() - elif value is not None: - value = value - # TODO: More conversions? - + value = self.convert(value) row[column_name] = value rows.append(row) @@ -11318,14 +11295,48 @@ def _create_access_file(self): return True def adapt(self, value): - if isinstance(value, dt.date): - return java.sql.Date @ value - if isinstance(value, dt.datetime): - return java.sql.Timestamp @ value - if isinstance(value, dt.time): - return java.sql.Time @ value + for py_type, java_type in self.adapters.items(): + if isinstance(value, py_type): + return java_type @ value + return value + + def convert(self, value): + for java_type, converter_fn in self.converters.items(): + if isinstance(value, java_type): + return converter_fn(value) return value + def _register_default_adapters(self): + self.adapters = { + dt.date: java.sql.Date, + dt.datetime: java.sql.Timestamp, + dt.time: java.sql.Time, + } + + def _register_default_converters(self): + self.converters = { + jpype.JPackage("java").lang.String: lambda value: str(value), + jpype.JPackage("java").lang.Integer: lambda value: int(value), + jpype.JPackage("java").math.BigDecimal: lambda value: float( + value.doubleValue() + ), + jpype.JPackage("java").lang.Double: lambda value: float(value), + jpype.JPackage("java") + .sql.Timestamp: lambda value: dt.datetime.strptime( + value.toInstant().toString()[:-1], + TIMESTAMP_FORMAT_MICROSECOND + if "." in value.toInstant().toString()[:-1] + else TIMESTAMP_FORMAT, + ) + .strftime(DATE_FORMAT), + jpype.JPackage("java") + .sql.Date: lambda value: dt.datetime.strptime(value.toString(), DATE_FORMAT) + .date(), + jpype.JPackage("java") + .sql.Time: lambda value: dt.datetime.strptime(value.toString(), TIME_FORMAT) + .time(), + } + # -------------------------- # TYPEDDICTS AND TYPEALIASES From 163f9fd7bd5dcdea759e54fb88f597b39201ced7 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:46:05 -0400 Subject: [PATCH 091/121] Fix for Postgres, use pk/pk_column --- pysimplesql/pysimplesql.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index d1cadec4..313e6efa 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -10533,9 +10533,10 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # insert_record() for Postgres is a little different from the rest. Instead of # relying on an autoincrement, we first already "reserved" a primary key # earlier, so we will use it directly quote appropriately + row[pk_column] = pk + table = self.quote_table(table) - # Remove the primary key column to ensure autoincrement is used! query = ( f"INSERT INTO {table} ({', '.join(key for key in row)}) VALUES " f"({','.join('%s' for _ in range(len(row)))}); " From f11d2298a90c14cfd43fc1bdb3783874b9a5c9f1 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:55:45 -0400 Subject: [PATCH 092/121] Remove redundant `insert_record` in msaccess It was duplicate of SQLServer's version --- pysimplesql/pysimplesql.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 313e6efa..31e0eb7e 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9334,7 +9334,6 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # quote appropriately table = self.quote_table(table) - # Remove the primary key column to ensure autoincrement is used! query = ( f"INSERT INTO {table} ({', '.join(key for key in row)}) VALUES " f"({','.join(self.placeholder for _ in range(len(row)))}); " @@ -10533,8 +10532,12 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # insert_record() for Postgres is a little different from the rest. Instead of # relying on an autoincrement, we first already "reserved" a primary key # earlier, so we will use it directly quote appropriately + + # add pk back in if its missing row[pk_column] = pk + # quote + row = {self.quote_column(k): v for k, v in row.items()} table = self.quote_table(table) query = ( @@ -10841,7 +10844,6 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # quote appropriately table = self.quote_table(table) - # Remove the primary key column to ensure autoincrement is used! query = ( f"INSERT INTO {table} ({', '.join(key for key in row)}) " f"OUTPUT inserted.{self.quote_column(pk_column)} " @@ -11253,21 +11255,6 @@ def _insert_duplicate_record( res.attrs["lastrowid"] = res.iloc[0]["ID"].tolist() return res - def insert_record(self, table: str, pk: int, pk_column: str, row: dict): - # Remove the pk column - row = {self.quote_column(k): v for k, v in row.items() if k != pk_column} - - # quote appropriately - table = self.quote_table(table) - - # Remove the primary key column to ensure autoincrement is used! - query = ( - f"INSERT INTO {table} ({', '.join(key for key in row)}) VALUES " - f"({','.join(self.placeholder for _ in range(len(row)))}); " - ) - values = [value for key, value in row.items()] - return self.execute(query, tuple(values)) - def _create_access_file(self): try: db_builder = jpype.JClass( From cd3f5433a5f27edf5d8bec6fe32aaf9a1d8abfdf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:06:48 -0400 Subject: [PATCH 093/121] Fix: Small fix for pk column --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 31e0eb7e..e3fa3446 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -11228,7 +11228,7 @@ def get_relationships(self): def max_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MAX({pk_column}) as max_pk FROM {table}") - return rows.iloc[0]["MAX_PK"] # returned as upper case + return rows.iloc[0]["MAX_PK"].tolist() # returned as upper case def _get_column_definitions(self, table_name): # Creates a comma separated list of column names and types to be used in a From 0237aa4eb7f6a0e2cfa6c53ae0144d2b75746b58 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:50:28 -0400 Subject: [PATCH 094/121] Fix: Remove quick_editor Form from Form Instances --- pysimplesql/pysimplesql.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index e3fa3446..aa7b9451 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -1101,10 +1101,12 @@ def value_changed( # Make the comparison # Temporary debug output - # print( - # f"element: {element_val}({type(element_val)}), - # db: {table_val}({type(table_val)})" - # ) + debug = False + if debug: + print( + f"element: {element_val}({type(element_val)})" + f"db: {table_val}({type(table_val)})" + ) if element_val != table_val: return new_value if new_value is not None else "" return Boolean.FALSE @@ -2885,6 +2887,7 @@ def quick_editor( self.requery() self.frm.update_elements() quick_win.close() + Form.purge_instance(quick_frm) break logger.debug(f"This event ({event}) is not yet handled.") @@ -3292,6 +3295,7 @@ def close(self, reset_keygen: bool = True): DataSet.purge_form(self, reset_keygen) if self.popup.popup_info: self.popup.popup_info.close() + Form.purge_instance(self) self.driver.close() def bind(self, win: sg.Window) -> None: @@ -4572,7 +4576,16 @@ def update_element_states( element.update(disabled=disable) if visible is not None: element.update(visible=visible) + + @classmethod + def purge_instance(cls, frm: Form) -> None: + """ + Remove self from Form.instances + :param frm: the `Form` to purge + :returns: None + """ + cls.instances = [i for i in cls.instances if i != frm] # ===================================================================================== # MAIN PYSIMPLESQL UTILITY FUNCTIONS From 909a1f6b65af222a4815098b5edf06fcf2480b96 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:50:50 -0400 Subject: [PATCH 095/121] Fix: Convert numpy.int64 to int correctly --- pysimplesql/pysimplesql.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index aa7b9451..402fc20c 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -335,6 +335,8 @@ def __str__(self): return str(self[:]) def __int__(self): + if isinstance(self.pk, np.int64): + return self.pk.tolist() return self.pk def __repr__(self): @@ -362,6 +364,8 @@ def __str__(self): return str(self.val) def __int__(self): + if isinstance(self.pk, np.int64): + return self.pk.tolist() return self.pk def get_pk(self): From 21a156b81d2a884b0320441588a7d1e2b3ddf4cf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:51:22 -0400 Subject: [PATCH 096/121] Ruff/Black fixes --- pysimplesql/pysimplesql.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 402fc20c..de9b448a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -1108,8 +1108,8 @@ def value_changed( debug = False if debug: print( - f"element: {element_val}({type(element_val)})" - f"db: {table_val}({type(table_val)})" + f"element: {element_val}({type(element_val)})" + f"db: {table_val}({type(table_val)})" ) if element_val != table_val: return new_value if new_value is not None else "" @@ -4580,7 +4580,7 @@ def update_element_states( element.update(disabled=disable) if visible is not None: element.update(visible=visible) - + @classmethod def purge_instance(cls, frm: Form) -> None: """ @@ -4591,6 +4591,7 @@ def purge_instance(cls, frm: Form) -> None: """ cls.instances = [i for i in cls.instances if i != frm] + # ===================================================================================== # MAIN PYSIMPLESQL UTILITY FUNCTIONS # ===================================================================================== From 47cb650789da8a162c0288f1088dba9915605bad Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 23:40:39 -0400 Subject: [PATCH 097/121] Fixes for opening a database without any records --- pysimplesql/pysimplesql.py | 70 +++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index de9b448a..74591d9a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -657,7 +657,7 @@ def __post_init__(self, data_key, frm_reference, prompt_save): self.driver = self.frm.driver self.relationships = self.driver.relationships - self.rows: Union[pd.DataFrame, None] = [] + self.rows: Union[pd.DataFrame, None] = Result.set() self._current_index: int = 0 self.column_info: ColumnInfo = None self.selector: List[str] = [] @@ -1212,7 +1212,7 @@ def requery( or self.relationships.parent_virtual(self.table, self.frm) ): # purge rows - self.rows = Result.set(pd.DataFrame(columns=self.rows.columns)) + self.rows = Result.set(pd.DataFrame(columns=self.column_info.names())) if update_elements: self.frm.update_elements(self.key) @@ -1257,6 +1257,10 @@ def requery( lambda x: x.rstrip() if isinstance(x, str) else x ) + # fill in columns if empty + if self.rows.columns.empty: + self.rows = Result.set(pd.DataFrame(columns=self.column_info.names())) + # reset search string self.search_string = "" @@ -2579,6 +2583,10 @@ def table_values( # get fk descriptions rows = self.map_fk_descriptions(rows, columns) + # return early if empty + if rows.empty: + return [] + # filter rows to only contain search, or virtual/unsaved row if apply_search_filter and self.search_string not in EMPTY: masks = [ @@ -2658,6 +2666,9 @@ def combobox_values( if rel is None: return None + if not self.frm[rel.parent_table].row_count: + return None + rows = self.frm[rel.parent_table].rows.copy() pk_column = self.frm[rel.parent_table].pk_column description = self.frm[rel.parent_table].description_column @@ -2711,6 +2722,10 @@ def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): for col in columns: for rel in rels: if col == rel.fk_column: + # return early if parent is empty + if not self.frm[rel.parent_table].row_count: + return rows + parent_df = self.frm[rel.parent_table].rows parent_pk_column = self.frm[rel.parent_table].pk_column @@ -2783,7 +2798,9 @@ def quick_editor( width = int(55 / (len(self.column_info.names()) - 1)) if col == self.pk_column: # make pk column either max length of contained pks, or len of name - width = max(self.rows[col].astype(str).map(len).max(), len(col) + 1) + width = int( + np.nanmax([self.rows[col].astype(str).map(len).max(), len(col) + 1]) + ) justify = "left" elif self.column_info[col] and self.column_info[col].python_type in [ int, @@ -4483,9 +4500,10 @@ def requery_all( must = True to use this parameter. :returns: None """ - # TODO: It would make sense to reorder these, and put filtered first - # then select_first/update/dependents + logger.info("Requerying all datasets") + + # first let datasets requery through cascade for data_key in self.datasets: if self.relationships.get_parent(data_key) is None: self[data_key].requery( @@ -4495,6 +4513,13 @@ def requery_all( requery_dependents=requery_dependents, ) + # fill in any datasets that are empty + for data_key in self.datasets: + if self[data_key].rows.columns.empty: + self[data_key].rows = Result.set( + pd.DataFrame(columns=self[data_key].column_info.names()) + ) + def process_events(self, event: str, values: list) -> bool: """ Process mapped events for this specific `Form` instance. @@ -7231,15 +7256,14 @@ def __init__(self, frm_reference: Form, data_key: str): self.data_key = data_key def __call__(self, column, save): + dataset = self.frm[self.data_key] if save: - self.frm[self.data_key].save_record() + dataset.save_record() # force a timeout, without this # info popup creation broke pysimplegui events, weird! self.frm.window.read(timeout=1) - else: - self.frm[self.data_key].sort_cycle( - column, self.data_key, update_elements=True - ) + elif dataset.row_count: # len(dataset.rows.index) - len(dataset.virtual_pks): + dataset.sort_cycle(column, self.data_key, update_elements=True) class _CellEdit: @@ -8405,6 +8429,10 @@ def validate(self, value): return response value = self.cast(value) + + if isinstance(value, str) and value in EMPTY: + return ValidateResponse() + value_precision = len(value.as_tuple().digits) if self.precision is not None and value_precision > self.precision: return ValidateResponse(ValidateRule.PRECISION, value, self.precision) @@ -8523,9 +8551,9 @@ def __init__(self, driver: SQLDriver, table: str): "float": 0.0, "Decimal": Decimal(0), "bool": 0, - "time": lambda x: dt.datetime.now().strftime(TIME_FORMAT), - "date": lambda x: dt.date.today().strftime(DATE_FORMAT), - "datetime": lambda x: dt.datetime.now().strftime(DATETIME_FORMAT), + "time": lambda: dt.datetime.now().strftime(TIME_FORMAT), + "date": lambda: dt.date.today().strftime(DATE_FORMAT), + "datetime": lambda: dt.datetime.now().strftime(DATETIME_FORMAT), } super().__init__() @@ -8593,13 +8621,14 @@ def default_row_dict(self, dataset: DataSet) -> dict: if rows.attrs["exception"] is None: try: default = rows.iloc[0]["val"] - except KeyError: + except IndexError: try: default = rows.iloc[0]["VAL"] - except KeyError: - default = "" - d[c.name] = default - continue + except IndexError: + default = None + if default is not None: + d[c.name] = default + continue logger.warning( "There was an exception getting the default: " f"{rows.attrs['exception']}" @@ -8764,6 +8793,7 @@ def set( rows.attrs["column_info"] = column_info rows.attrs["row_backup"] = row_backup rows.attrs["virtual"] = [] + rows.attrs["sort_column"] = None return rows @@ -10766,7 +10796,9 @@ def column_info(self, table): if row["COLUMN_DEFAULT"]: col_default = row["COLUMN_DEFAULT"] if (col_default.startswith("('") and col_default.endswith("')")) or ( - col_default.startswith('("') and col_default.endswith('")') + col_default.startswith('("') + and col_default.endswith('")') + or (col_default.startswith("((") and col_default.endswith("))")) ): default = col_default[2:-2] else: From eca1d302da7a8c4390ac383c1af387c6659eb78d Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Thu, 20 Jul 2023 23:41:04 -0400 Subject: [PATCH 098/121] Fix Postgres, get columns in order of creation (like other drivers) --- pysimplesql/pysimplesql.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 74591d9a..6c886c8f 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -10463,7 +10463,10 @@ def get_tables(self): def column_info(self, table: str) -> ColumnInfo: # Return a list of column names - query = f"SELECT * FROM information_schema.columns WHERE table_name = '{table}'" + query = ( + f"SELECT * FROM information_schema.columns WHERE table_name = '{table}' " + "ORDER BY ordinal_position" + ) rows = self.execute(query, silent=True) col_info = ColumnInfo(self, table) pk_column = self.pk_column(table) From 39f598b18f2ca14a1f90145f186e567a15b2cc67 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 21 Jul 2023 12:58:19 -0400 Subject: [PATCH 099/121] Remove prefix_data_keys Form init option This is currently broken, so removing for now until there is a viable strategy for implementing. --- pysimplesql/pysimplesql.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 6c886c8f..c1f2410a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3196,8 +3196,6 @@ class Form: :param driver: Supported `SQLDriver`. See `Sqlite()`, `Mysql()`, `Postgres()` :param bind_window: Bind this window to the `Form` - :param prefix_data_keys: (optional) prefix auto generated data_key names with - this value. Example 'data_' :param parent: (optional)Parent `Form` to base dataset off of :param filter: (optional) Only import elements with the same filter set. Typically set with `field()`, but can also be set manually as a dict with @@ -3232,7 +3230,6 @@ class Form: driver: SQLDriver bind_window: dc.InitVar[sg.Window] = None - prefix_data_keys: dc.InitVar[str] = "" parent: Form = None # TODO: This doesn't seem to really be used filter: str = None select_first: dc.InitVar[bool] = True @@ -3248,7 +3245,6 @@ class Form: def __post_init__( self, bind_window, - prefix_data_keys, select_first, prompt_save, ): @@ -3281,7 +3277,7 @@ def __post_init__( win_pb.update(lang.startup_init, 0) # Add our default datasets and relationships win_pb.update(lang.startup_datasets, 25) - self.auto_add_datasets(prefix_data_keys) + self.auto_add_datasets() win_pb.update(lang.startup_relationships, 50) self.requery_all( select_first=select_first, update_elements=False, requery_dependents=True @@ -3472,7 +3468,7 @@ def set_fk_column_cascade( if delete_cascade is not None: rel.delete_cascade = delete_cascade - def auto_add_datasets(self, prefix_data_keys: str = "") -> None: + def auto_add_datasets(self) -> None: """ Automatically add `DataSet` objects from the database by looping through the tables available and creating a `DataSet` object for each. Each dataset key is @@ -3481,7 +3477,6 @@ def auto_add_datasets(self, prefix_data_keys: str = "") -> None: This is called automatically when a `Form ` is created. Note that `Form.add_table()` can do this manually on a per-table basis. - :param prefix_data_keys: Adds a prefix to the auto-generated `DataSet` keys :returns: None """ logger.info( @@ -3504,7 +3499,7 @@ def auto_add_datasets(self, prefix_data_keys: str = "") -> None: # Get our pk column pk_column = self.driver.pk_column(table) - data_key = prefix_data_keys + table + data_key = table logger.debug( f'Adding DataSet "{data_key}" on table {table} to Form with primary ' f"key {pk_column} and description of {description_column}" From 6dd1d566fe9e412560d42f799cd1e9ef8ed2b3f3 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 21 Jul 2023 12:58:58 -0400 Subject: [PATCH 100/121] Rows will always have (at least) an empty dataframe Too many bugs generated if it is None --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index c1f2410a..c7409a72 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -657,7 +657,7 @@ def __post_init__(self, data_key, frm_reference, prompt_save): self.driver = self.frm.driver self.relationships = self.driver.relationships - self.rows: Union[pd.DataFrame, None] = Result.set() + self.rows: pd.DataFrame = Result.set() self._current_index: int = 0 self.column_info: ColumnInfo = None self.selector: List[str] = [] From d34ed787ee12cf3760872444eb20afb9f1b33413 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:02:42 -0400 Subject: [PATCH 101/121] Feat: add `close_driver` arg to Form.close() Allows quick_editor (or others) that share driver to close without closing driver. --- pysimplesql/pysimplesql.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index c7409a72..d6abfa3a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -2908,7 +2908,7 @@ def quick_editor( self.requery() self.frm.update_elements() quick_win.close() - Form.purge_instance(quick_frm) + quick_frm.close(close_driver=False) break logger.debug(f"This event ({event}) is not yet handled.") @@ -3302,7 +3302,7 @@ def __getitem__(self, key: str) -> DataSet: f"proper permissions set, or any number of db configuration issues." ) from e - def close(self, reset_keygen: bool = True): + def close(self, reset_keygen: bool = True, close_driver: bool = True): """ Safely close out the `Form`. @@ -3313,7 +3313,8 @@ def close(self, reset_keygen: bool = True): if self.popup.popup_info: self.popup.popup_info.close() Form.purge_instance(self) - self.driver.close() + if close_driver: + self.driver.close() def bind(self, win: sg.Window) -> None: """ From 9660376a3c900def9a8f15a1ebf2db260f20ea45 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:23:00 -0400 Subject: [PATCH 102/121] Update ColumnInfo example --- doc_examples/ColumnInfo.1.py | 25 ++++++++++--------------- pysimplesql/pysimplesql.py | 7 ------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/doc_examples/ColumnInfo.1.py b/doc_examples/ColumnInfo.1.py index 1e70e451..bea143db 100644 --- a/doc_examples/ColumnInfo.1.py +++ b/doc_examples/ColumnInfo.1.py @@ -1,22 +1,17 @@ -# Set the null value default for INTEGERS to 10; +# Set the null value default for 'int' to 10; # When reading from the database, if an INTEGER is Null, this value will be set -frm["Journal"].column_info.set_null_default("INTEGER", 10) +frm["Journal"].column_info.set_null_default("int", 10) # Provide a complete custom set of null defaults: # note: All supported keys must be included null_defaults = { - "TEXT": "New Record", - "VARCHAR": "New Record", - "CHAR": "New Record", - "INTEGER": 10, - "REAL": 100.0, - "DOUBLE": 90.0, - "FLOAT": 80.0, - "DECIMAL": 70.0, - "BOOLEAN": 1, - "TIME": lambda x: datetime.now().strftime("%H:%M:%S"), - "DATE": lambda x: date.today().strftime("%Y-%m-%d"), - "TIMESTAMP": lambda x: datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - "DATETIME": lambda x: datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "str": lang.description_column_str_null_default, + "int": 10, + "float": 90.0, + "Decimal": Decimal("70.0"), + "bool": 1, + "time": lambda: dt.datetime.now().strftime(TIME_FORMAT), + "date": lambda: dt.date.today().strftime(DATE_FORMAT), + "datetime": lambda: dt.datetime.now().strftime(DATETIME_FORMAT), } frm["Journal"].column_info.set_null_defaults(null_defaults) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index d6abfa3a..1e4ca3b4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -3253,13 +3253,6 @@ def __post_init__( self.window: Optional[sg.Window] = 0 self.datasets: Dict[str, DataSet] = {} self.element_map: List[ElementMap] = [] - """ - The element map dict is set up as below: - - .. literalinclude:: ../doc_examples/element_map.1.py - :language: python - :caption: Example code - """ self.event_map: List = [] # Array of dicts, {'event':, 'function':, 'table':} self._edit_protect: bool = False self.relationships: RelationshipStore = self.driver.relationships From a62404a5345ff4f4d125f361387443bd123f36e0 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:17:16 -0400 Subject: [PATCH 103/121] Make a few more classes Private --- pysimplesql/pysimplesql.py | 89 ++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 1e4ca3b4..744e86d0 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -318,8 +318,8 @@ def decimal_places(val, decimal_places): # ------- # CLASSES # ------- -# TODO: Combine TableRow and ElementRow into one class for simplicity -class TableRow(list): +# TODO: Combine _TableRow and _ElementRow into one class for simplicity +class _TableRow(list): """ Convenience class used by Tables to associate a primary key with a row of data. @@ -341,10 +341,10 @@ def __int__(self): def __repr__(self): # Add some extra information that could be useful for debugging - return f"TableRow(pk={self.pk}): {super().__repr__()}" + return f"_TableRow(pk={self.pk}): {super().__repr__()}" -class ElementRow: +class _ElementRow: """ Convenience class used by listboxes and comboboxes to associate a primary key with @@ -531,7 +531,7 @@ def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, str]: """ Returns a dictionary of the `DataSet.key` and column names that use the - description_column text of the given parent table in their `ElementRow` objects. + description_column text of the given parent table in their `_ElementRow` objects. This method is used to determine which GUI field and selector elements to update when a new `DataSet.description_column` value is saved. The returned dictionary @@ -1212,7 +1212,7 @@ def requery( or self.relationships.parent_virtual(self.table, self.frm) ): # purge rows - self.rows = Result.set(pd.DataFrame(columns=self.column_info.names())) + self.rows = Result.set(pd.DataFrame(columns=self.column_info.names)) if update_elements: self.frm.update_elements(self.key) @@ -1259,7 +1259,7 @@ def requery( # fill in columns if empty if self.rows.columns.empty: - self.rows = Result.set(pd.DataFrame(columns=self.column_info.names())) + self.rows = Result.set(pd.DataFrame(columns=self.column_info.names)) # reset search string self.search_string = "" @@ -1949,7 +1949,7 @@ def save_record( # convert the data into the correct type using the domain in ColumnInfo if isinstance(mapped.element, sg.Combo): - # try to get ElementRow pk + # try to get _ElementRow pk try: element_val = self.column_info[mapped.column].cast( mapped.element.get().get_pk_ignore_placeholder() @@ -2532,9 +2532,9 @@ def table_values( mark_unsaved: bool = False, apply_search_filter: bool = False, apply_cell_format_fn: bool = True, - ) -> List[TableRow]: + ) -> List[_TableRow]: """ - Create a values list of `TableRows`s for use in a PySimpleGUI Table element. + Create a values list of `_TableRows`s for use in a PySimpleGUI Table element. :param columns: A list of column names to create table values for. Defaults to getting them from the `DataSet.rows` DataFrame. @@ -2544,7 +2544,7 @@ def table_values( `DataSet.search_order` that contain `DataSet.search_string`. :param apply_cell_format_fn: If set, apply() `DataSet.column_info[col].cell_format_fn` to rows column - :returns: A list of `TableRow`s suitable for using with PySimpleGUI Table + :returns: A list of `_TableRow`s suitable for using with PySimpleGUI Table element values. """ if not self.row_count: @@ -2615,9 +2615,9 @@ def table_values( # resort rows with requested columns rows = rows[columns] - # fastest way yet to generate list of TableRows + # fastest way yet to generate list of _TableRows return [ - TableRow(pk, values.tolist()) + _TableRow(pk, values.tolist()) for pk, values in zip( rows.index, np.vstack((rows.fillna("").astype("O").to_numpy().T, rows.index)).T, @@ -2650,12 +2650,12 @@ def column_likely_in_selector(self, column: str) -> bool: def combobox_values( self, column_name, insert_placeholder: bool = True - ) -> List[ElementRow] or None: + ) -> List[_ElementRow] or None: """ - Returns the values to use in a sg.Combobox as a list of ElementRow objects. + Returns the values to use in a sg.Combobox as a list of _ElementRow objects. :param column_name: The name of the table column for which to get the values. - :returns: A list of ElementRow objects representing the possible values for the + :returns: A list of _ElementRow objects representing the possible values for the combobox column, or None if no matching relationship is found. """ if not self.row_count: @@ -2677,14 +2677,14 @@ def combobox_values( parent_current_row = self.frm[rel.parent_table].get_original_current_row() rows.iloc[self.frm[rel.parent_table].current_index] = parent_current_row - # fastest way yet to generate this list of ElementRow + # fastest way yet to generate this list of _ElementRow combobox_values = [ - ElementRow(*values) + _ElementRow(*values) for values in np.column_stack((rows[pk_column], rows[description])) ] if insert_placeholder: - combobox_values.insert(0, ElementRow("Null", lang.combo_placeholder)) + combobox_values.insert(0, _ElementRow("Null", lang.combo_placeholder)) return combobox_values def get_related_table_for_column(self, column: str) -> str: @@ -2793,9 +2793,9 @@ def quick_editor( style=TableStyler(row_height=25), ) - for col in self.column_info.names(): + for col in self.column_info.names: # set widths - width = int(55 / (len(self.column_info.names()) - 1)) + width = int(55 / (len(self.column_info.names) - 1)) if col == self.pk_column: # make pk column either max length of contained pks, or len of name width = int( @@ -2830,7 +2830,7 @@ def quick_editor( fields_layout = [[sg.Sizer(h_pixels=0, v_pixels=y_pad)]] rels = self.relationships.get_rels_for(self.table) - for col in self.column_info.names(): + for col in self.column_info.names: found = False column = f"{data_key}.{col}" # make sure isn't pk @@ -3078,7 +3078,7 @@ def sort(self, table: str, update_elements: bool = True, sort_order=None) -> Non if update_elements and self.row_count: self.frm.update_selectors(self.key) self.frm.update_actions(self.key) - self.update_headings(self.rows.attrs["sort_column"], sort_order) + self._update_headings(self.rows.attrs["sort_column"], sort_order) def sort_cycle(self, column: str, table: str, update_elements: bool = True) -> int: """ @@ -3105,14 +3105,14 @@ def sort_cycle(self, column: str, table: str, update_elements: bool = True) -> i self.sort(table, update_elements=update_elements, sort_order=SORT_NONE) return SORT_NONE - def update_headings(self, column, sort_order): + def _update_headings(self, column, sort_order): for e in self.selector: element = e["element"] if ( "TableBuilder" in element.metadata and element.metadata["TableBuilder"].sort_enable ): - element.metadata["TableBuilder"].update_headings( + element.metadata["TableBuilder"]._update_headings( element, column, sort_order ) @@ -3485,7 +3485,7 @@ def auto_add_datasets(self) -> None: # auto generate description column. Default it to the 2nd column, # but can be overwritten below description_column = column_info.col_name(1) - for col in column_info.names(): + for col in column_info.names: if col in self.description_column_names: description_column = col break @@ -3628,7 +3628,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: element, self[table], col, where_column, where_value ) if isinstance(element, (_EnhancedInput, _EnhancedMultiline)) and ( - col in self[table].column_info.names() + col in self[table].column_info.names and self[table].column_info[col].notnull ): element.add_placeholder( @@ -3637,7 +3637,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: ) if ( isinstance(element, _EnhancedInput) - and col in self[table].column_info.names() + and col in self[table].column_info.names ): element.add_validate(self[table], col) @@ -3670,7 +3670,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: # We need a whole chain of things to happen # when a heading is clicked on: # 1 Run the ResultRow.sort_cycle() with the correct column name - # 2 Run TableBuilder.update_headings() with the: + # 2 Run TableBuilder._update_headings() with the: # Table element, sort_column, sort_reverse # 3 Run update_elements() to see the changes table_builder.enable_heading_function( @@ -4218,7 +4218,7 @@ def update_fields( col = mapped.column # get notnull from the column info if ( - col in mapped.dataset.column_info.names() + col in mapped.dataset.column_info.names and mapped.dataset.column_info[col].notnull ): self.window[marker_key].update( @@ -4393,13 +4393,13 @@ def update_selectors( # TODO: Kind of a hackish way to check for equality. if str(r[e["where_column"]]) == str(e["where_value"]): lst.append( - ElementRow(r[pk_column], r[description_column]) + _ElementRow(r[pk_column], r[description_column]) ) else: pass else: lst.append( - ElementRow(r[pk_column], r[description_column]) + _ElementRow(r[pk_column], r[description_column]) ) element.update( @@ -4506,7 +4506,7 @@ def requery_all( for data_key in self.datasets: if self[data_key].rows.columns.empty: self[data_key].rows = Result.set( - pd.DataFrame(columns=self[data_key].column_info.names()) + pd.DataFrame(columns=self[data_key].column_info.names) ) def process_events(self, event: str, values: list) -> bool: @@ -4691,7 +4691,7 @@ def simple_transform(dataset: DataSet, row, encode): def update_table_element( window: sg.Window, element: Type[sg.Table], - values: List[TableRow], + values: List[_TableRow], select_rows: List[int], ) -> None: """ @@ -5327,7 +5327,7 @@ class LazyTable(sg.Table): To use, simply replace `sg.Table` with `ss.LazyTable` as the `element` argument in a selector() function call in your layout. - Expects values in the form of [TableRow(pk, values)], and only becomes active after + Expects values in the form of [_TableRow(pk, values)], and only becomes active after a update(values=, selected_rows=[int]) call. Please note that LazyTable does not support the `sg.Table` `row_colors` argument. """ @@ -5526,7 +5526,7 @@ def _handle_start_scroll(self): self._start_index = new_start_index # Insert new_rows to beginning - # don't use data.insert(0, new_rows), it breaks TableRow + # don't use data.insert(0, new_rows), it breaks _TableRow self.data[:0] = new_rows # to avoid an infinite scroll, move scroll a little after 0.0 @@ -5887,7 +5887,7 @@ def _on_search_string_change(self, *args): class _AutoCompleteLogic: - _completion_list: List[Union[str, ElementRow]] = dc.field(default_factory=list) + _completion_list: List[Union[str, _ElementRow]] = dc.field(default_factory=list) _hits: List[int] = dc.field(default_factory=list) _hit_index: int = 0 position: int = 0 @@ -7022,7 +7022,7 @@ def add_column( """ Add a new heading column to this TableBuilder object. Columns are added in the order that this method is called. Note that the primary key column does not need - to be included, as primary keys are stored internally in the `TableRow` class. + to be included, as primary keys are stored internally in the `_TableRow` class. :param column: The name of the column in the database :param heading: The name of this columns heading (title) @@ -7162,7 +7162,7 @@ def width_map(self) -> List[int]: """ return list(self._width_map) - def update_headings( + def _update_headings( self, element: sg.Table, sort_column=None, sort_order: int = None ) -> None: """ @@ -7221,7 +7221,7 @@ def enable_heading_function(self, element: sg.Table, fn: callable) -> None: element.widget.heading( i, command=functools.partial(fn, self[i]["column"], False) ) - self.update_headings(element) + self._update_headings(element) if self.add_save_heading_button: element.widget.heading(0, command=functools.partial(fn, None, save=True)) @@ -7484,7 +7484,7 @@ def accept( row, column, col_idx, - combobox_values: ElementRow, + combobox_values: _ElementRow, widget_type, field_var, ): @@ -7661,7 +7661,7 @@ def sync(self, widget, widget_type): data_key = e["table"] column = e["column"] element = e["element"] - if widget_type == TK_COMBOBOX and isinstance(element.get(), ElementRow): + if widget_type == TK_COMBOBOX and isinstance(element.get(), _ElementRow): new_value = element.get().get_pk_ignore_placeholder() else: new_value = element.get() @@ -8455,7 +8455,7 @@ def cast(self, value, truncate_decimals: bool = None): value_backup = value if isinstance(value, int): return value - if isinstance(value, ElementRow): + if isinstance(value, _ElementRow): return int(value) try: value = self.strip_locale(value) @@ -8556,6 +8556,7 @@ def __getitem__(self, item): return next((i for i in self if i.name == item), None) return super().__getitem__(item) + @property def pk_column(self) -> Union[str, None]: """ Get the pk_column for this colection of column_info. @@ -8568,6 +8569,7 @@ def pk_column(self) -> Union[str, None]: return c.name return None + @property def names(self) -> List[str]: """ Return a List of column names from the `Column`s in this collection. @@ -8783,6 +8785,7 @@ def set( rows.attrs["row_backup"] = row_backup rows.attrs["virtual"] = [] rows.attrs["sort_column"] = None + rows.attrs["sort_reverse"] = None return rows From 2f5f436d4cb2e5c41108dab6d3f03348409836ca Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:19:30 -0400 Subject: [PATCH 104/121] Ruff/Black --- pysimplesql/pysimplesql.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 744e86d0..651590e3 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -531,7 +531,8 @@ def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, str]: """ Returns a dictionary of the `DataSet.key` and column names that use the - description_column text of the given parent table in their `_ElementRow` objects. + description_column text of the given parent table in their `_ElementRow` + objects. This method is used to determine which GUI field and selector elements to update when a new `DataSet.description_column` value is saved. The returned dictionary @@ -7661,7 +7662,9 @@ def sync(self, widget, widget_type): data_key = e["table"] column = e["column"] element = e["element"] - if widget_type == TK_COMBOBOX and isinstance(element.get(), _ElementRow): + if widget_type == TK_COMBOBOX and isinstance( + element.get(), _ElementRow + ): new_value = element.get().get_pk_ignore_placeholder() else: new_value = element.get() From 33cb42866f75dec3af658701a5fc596987409533 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 23 Jul 2023 17:27:42 -0400 Subject: [PATCH 105/121] Ruff fixes (had to upgrade to match github action --- pysimplesql/pysimplesql.py | 5 ++--- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 651590e3..414ab36d 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -250,7 +250,6 @@ TableJustify = Literal["left", "right", "center"] ColumnJustify = Literal["left", "right", "center", "default"] HeadingJustify = Literal["left", "right", "center", "column", "default"] -InMemory = Literal[":memory:"] # -------------------- # DateTime formats @@ -5390,7 +5389,7 @@ def SelectedRows(self): # noqa N802 if self.data and self.widget.selection(): index = [ [v.pk for v in self.data].index( - [int(x) for x in self.widget.selection()][0] + next(int(x) for x in self.widget.selection()) ) ][0] return self.data[index] @@ -9543,7 +9542,7 @@ def __init__( database: Union[ str, Path, - InMemory, + Literal[":memory:"], sqlite3.Connection, ] = None, *, diff --git a/setup.py b/setup.py index 93eb7b19..460904eb 100644 --- a/setup.py +++ b/setup.py @@ -28,11 +28,11 @@ def main(): install_requires=app.__requires__, extras_require=app.__extra_requires__, classifiers=app.__classifiers__, - license=[ + license=next( c.rsplit("::", 1)[1].strip() for c in app.__classifiers__ if c.startswith("License ::") - ][0], + ), include_package_data=True, platforms=app.__platforms__, ) From e17857d4369d57961b6f75385fd1e5a1d2434869 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Tue, 25 Jul 2023 08:55:00 -0400 Subject: [PATCH 106/121] Convert to Google-style docstrings --- pysimplesql/pysimplesql.py | 2610 +++++++++++++++++++----------------- 1 file changed, 1384 insertions(+), 1226 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 414ab36d..aad6003a 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -1,5 +1,4 @@ -""" -## DISCLAIMER: While **pysimplesql** works with and was inspired by the excellent +"""## DISCLAIMER: While **pysimplesql** works with and was inspired by the excellent PySimpleGUI™ project, it has no affiliation. ## Rapidly build and deploy database applications in Python **pysimplesql** binds @@ -320,8 +319,7 @@ def decimal_places(val, decimal_places): # TODO: Combine _TableRow and _ElementRow into one class for simplicity class _TableRow(list): - """ - Convenience class used by Tables to associate a primary key with a row of data. + """Convenience class used by Tables to associate a primary key with a row of data. Note: This is typically not used by the end user. """ @@ -345,9 +343,8 @@ def __repr__(self): class _ElementRow: - """ - Convenience class used by listboxes and comboboxes to associate a primary key with - a row of data. + """Convenience class used by listboxes and comboboxes to associate a primary key + with a row of data. Note: This is typically not used by the end user. """ @@ -388,15 +385,18 @@ def get_instance(self): @dc.dataclass class Relationship: """ - :param join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. - :param child_table: The table name of the fk table - :param fk_column: The child table's foreign key column - :param parent_table: The table name of the parent table - :param pk_column: The parent table's primary key column - :param update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' - :param delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' - :param driver: A `SQLDriver` instance - :returns: None + Args: + join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. + child_table: The table name of the fk table + fk_column: The child table's foreign key column + parent_table: The table name of the parent table + pk_column: The parent table's primary key column + update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' + delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' + driver: A `SQLDriver` instance + + Returns: + None """ join_type: str @@ -423,8 +423,7 @@ def __str__(self): @dc.dataclass class RelationshipStore(list): - """ - Used to track primary/foreign key relationships in the database. + """Used to track primary/foreign key relationships in the database. See the following for more information: `SQLDriver.add_relationship` and `SQLDriver.auto_add_relationships`. @@ -435,21 +434,25 @@ class RelationshipStore(list): driver: SQLDriver def get_rels_for(self, table: str) -> List[Relationship]: - """ - Return the relationships for the passed-in table. + """Return the relationships for the passed-in table. + + Args: + table: The table to get relationships for - :param table: The table to get relationships for - :returns: A list of @Relationship objects + Returns: + A list of @Relationship objects """ return [r for r in self if r.child_table == table] def get_update_cascade_tables(self, table: str) -> List[str]: - """ - Return a unique list of the relationships for this table that should requery + """Return a unique list of the relationships for this table that should requery with this table. - :param table: The table to get cascaded children for - :returns: A unique list of table names + Args: + table: The table to get cascaded children for + + Returns: + A unique list of table names """ rel = [ r.child_table @@ -460,12 +463,14 @@ def get_update_cascade_tables(self, table: str) -> List[str]: return list(set(rel)) def get_delete_cascade_tables(self, table: str) -> List[str]: - """ - Return a unique list of the relationships for this table that should be deleted - with this table. + """Return a unique list of the relationships for this table that should be + deleted with this table. + + Args: + table: The table to get cascaded children for - :param table: The table to get cascaded children for - :returns: A unique list of table names + Returns: + A unique list of table names """ rel = [ r.child_table @@ -476,11 +481,13 @@ def get_delete_cascade_tables(self, table: str) -> List[str]: return list(set(rel)) def get_parent(self, table: str) -> Union[str, None]: - """ - Return the parent table for the passed-in table. + """Return the parent table for the passed-in table. + + Args: + table: The table (str) to get relationships for - :param table: The table (str) to get relationships for - :returns: The name of the Parent table, or None if there is none + Returns: + The name of the Parent table, or None if there is none """ for r in self: if r.child_table == table and r.on_update_cascade: @@ -488,12 +495,14 @@ def get_parent(self, table: str) -> Union[str, None]: return None def parent_virtual(self, table: str, frm: Form) -> Union[bool, None]: - """ - Return True if current row of parent table is virtual. + """Return True if current row of parent table is virtual. - :param table: The table (str) to get relationships for - :param frm: Form reference - :returns: True if current row of parent table is virtual + Args: + table: The table (str) to get relationships for + frm: Form reference + + Returns: + True if current row of parent table is virtual """ for r in self: if r.child_table == table and r.on_update_cascade: @@ -504,11 +513,13 @@ def parent_virtual(self, table: str, frm: Form) -> Union[bool, None]: return None def get_update_cascade_fk_column(self, table: str) -> Union[str, None]: - """ - Return the cascade fk that filters for the passed-in table. + """Return the cascade fk that filters for the passed-in table. - :param table: The table name of the child - :returns: The name of the cascade-fk, or None + Args: + table: The table name of the child + + Returns: + The name of the cascade-fk, or None """ for r in self: if r.child_table == table and r.on_update_cascade: @@ -516,11 +527,13 @@ def get_update_cascade_fk_column(self, table: str) -> Union[str, None]: return None def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: - """ - Return the cascade fk that filters for the passed-in table. + """Return the cascade fk that filters for the passed-in table. + + Args: + table: The table name of the child - :param table: The table name of the child - :returns: The name of the cascade-fk, or None + Returns: + The name of the cascade-fk, or None """ for r in self: if r.child_table == table and r.on_delete_cascade: @@ -528,8 +541,7 @@ def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: return None def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, str]: - """ - Returns a dictionary of the `DataSet.key` and column names that use the + """Returns a dictionary of the `DataSet.key` and column names that use the description_column text of the given parent table in their `_ElementRow` objects. @@ -538,9 +550,12 @@ def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, st contains the `DataSet.key` as the key and the corresponding column name as the value. - :param frm_reference: A `Form` object representing the parent form. - :param table: The name of the parent table. - :returns: A dictionary of `{datakey: column}` pairs. + Args: + frm_reference: A `Form` object representing the parent form. + table: The name of the parent table. + + Returns: + A dictionary of `{datakey: column}` pairs. """ return { frm_reference[dataset].key: r.fk_column @@ -555,8 +570,7 @@ def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, st @dc.dataclass class ElementMap: - """ - Map a PySimpleGUI element to a specific `DataSet` column. + """Map a PySimpleGUI element to a specific `DataSet` column. This is what makes the GUI automatically update to the contents of the database. This happens automatically when a PySimpleGUI Window is bound to a `Form` by using @@ -564,12 +578,15 @@ class ElementMap: long as the Table.column naming convention is used, This method can be used to manually map any element to any `DataSet` column regardless of naming convention. - :param element: A PySimpleGUI Element - :param dataset: A `DataSet` object - :param column: The name of the column to bind to the element - :param where_column: Used for key, value shorthand - :param where_value: Used for key, value shorthand - :returns: None + Args: + element: A PySimpleGUI Element + dataset: A `DataSet` object + column: The name of the column to bind to the element + where_column: Used for key, value shorthand + where_value: Used for key, value shorthand + + Returns: + None """ element: sg.Element @@ -594,8 +611,7 @@ def __contains__(self, item): @dc.dataclass(eq=False) class DataSet: - """ - `DataSet` objects are used for an internal representation of database tables. + """`DataSet` objects are used for an internal representation of database tables. `DataSet` instances are added by the following `Form` methods: `Form.add_table`, `Form.auto_add_tables`. A `DataSet` is synonymous for a SQL Table (though you can @@ -604,34 +620,34 @@ class DataSet: Note: While users will interact with DataSet objects often in pysimplesql, they typically aren't created manually by the user. - :param data_key: The name you are assigning to this `DataSet` object (I.e. - 'people') Accessible via `DataSet.key`. - :param frm_reference: This is a reference to the @ Form object, for convenience. - Accessible via `DataSet.frm` - :param table: Name of the table - :param pk_column: The name of the column containing the primary key for this - table. - :param description_column: The name of the column used for display to users - (normally in a combobox or listbox). - :param query: You can optionally set an initial query here. If none is provided, - it will default to "SELECT * FROM {table}" - :param order_clause: The sort order of the returned query. If none is provided - it will default to "ORDER BY {description_column} ASC" - :param filtered: (optional) If True, the relationships will be considered and an - appropriate WHERE clause will be generated. False will display all records - in the table. - :param prompt_save: (optional) Default: Mode set in `Form`. Prompt to save - changes when dirty records are present. There are two modes available, - (if pysimplesql is imported as `ss`) use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. - :param save_quiet: (optional) Default: Set in `Form`. True to skip info popup on - save. Error popups will still be shown. - :param duplicate_children: (optional) Default: Set in `Form`. If record has - children, prompt user to choose to duplicate current record, or both. - :param validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from - being entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures - validation occurs before saving to the database. + Args: + data_key: The name you are assigning to this `DataSet` object (I.e. 'people') + Accessible via `DataSet.key`. + frm_reference: This is a reference to the @ Form object, for convenience. + Accessible via `DataSet.frm` + table: Name of the table + pk_column: The name of the column containing the primary key for this table. + description_column: The name of the column used for display to users (normally + in a combobox or listbox). + query: You can optionally set an initial query here. If none is provided, it + will default to "SELECT * FROM {table}" + order_clause: The sort order of the returned query. If none is provided it will + default to "ORDER BY {description_column} ASC" + filtered: (optional) If True, the relationships will be considered and an + appropriate WHERE clause will be generated. False will display all records + in the table. + prompt_save: (optional) Default: Mode set in `Form`. Prompt to save changes when + dirty records are present. There are two modes available, (if pysimplesql is + imported as `ss`) use: `ss.PROMPT_MODE` to prompt to save when unsaved + changes are present. `ss.AUTOSAVE_MODE` to automatically save when unsaved + changes are present. + save_quiet: (optional) Default: Set in `Form`. True to skip info popup on save. + Error popups will still be shown. + duplicate_children: (optional) Default: Set in `Form`. If record has children, + prompt user to choose to duplicate current record, or both. + validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from being + entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures + validation occurs before saving to the database. """ instances: ClassVar[List[DataSet]] = [] # Track our own instances @@ -697,23 +713,26 @@ def __post_init__(self, data_key, frm_reference, prompt_save): # Override the [] operator to retrieve current columns by key def __getitem__(self, column: str) -> Union[str, int]: - """ - Retrieve the value of the specified column in the current row. + """Retrieve the value of the specified column in the current row. + + Args: + column: The key of the column to retrieve. - :param column: The key of the column to retrieve. - :returns: The current value of the specified column. + Returns: + The current value of the specified column. """ return self.get_current(column) # Override the [] operator to set current columns value def __setitem__(self, column, value: Union[str, int]) -> None: - """ - Set the value of the specified column in the current row. + """Set the value of the specified column in the current row. - :param column: The key of the column to set. - :param value: The value to set the column to. + Args: + column: The key of the column to set. + value: The value to set the column to. - :returns: None + Returns: + None """ self.set_current(column, value) @@ -745,12 +764,14 @@ def current_index(self, val: int): @classmethod def purge_form(cls, frm: Form, reset_keygen: bool) -> None: - """ - Purge the tracked instances related to frm. + """Purge the tracked instances related to frm. + + Args: + frm: the `Form` to purge `DataSet`` instances from + reset_keygen: Reset the keygen after purging? - :param frm: the `Form` to purge `DataSet`` instances from - :param reset_keygen: Reset the keygen after purging? - :returns: None + Returns: + None """ new_instances = [] selector_keys = [] @@ -777,32 +798,36 @@ def purge_form(cls, frm: Form, reset_keygen: bool) -> None: DataSet.instances = new_instances def set_prompt_save(self, mode: int) -> None: - """ - Set the prompt to save action when navigating records. + """Set the prompt to save action when navigating records. + + Args: + mode: a constant value. If pysimplesql is imported as `ss`, use: - + `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - + `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are + present. - :param mode: a constant value. If pysimplesql is imported as `ss`, use: - - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. - :returns: None + Returns: + None """ self._prompt_save = mode def set_search_order(self, order: List[str]) -> None: - """ - Set the search order when using the search box. + """Set the search order when using the search box. This is a list of column names to be searched, in order - :param order: A list of column names to search - :returns: None + Args: + order: A list of column names to search + + Returns: + None """ self.search_order = order def set_callback( self, callback: str, fctn: Callable[[Form, sg.Window, DataSet.key], bool] ) -> None: - """ - Set DataSet callbacks. A runtime error will be thrown if the callback is not + """Set DataSet callbacks. A runtime error will be thrown if the callback is not supported. The following callbacks are supported: @@ -833,11 +858,14 @@ def set_callback( after_record_edit called after the internal `DataSet` row is edited via a `sg.Table` cell-edit, or `field` live-update. - :param callback: The name of the callback, from the list above - :param fctn: The function to call. Note, the function must take at least two - parameters, a `Form` instance, and a `PySimpleGUI.Window` instance, with an - optional `DataSet.key`, and return True or False - :returns: None + Args: + callback: The name of the callback, from the list above + fctn: The function to call. Note, the function must take at least two + parameters, a `Form` instance, and a `PySimpleGUI.Window` instance, with + an optional `DataSet.key`, and return True or False + + Returns: + None """ logger.info(f"Callback {callback} being set on table {self.table}") supported = [ @@ -874,8 +902,7 @@ def _invoke_callback(self, callback, *args): raise ValueError("Unexpected number of parameters in the callback function") def set_transform(self, fn: callable) -> None: - """ - Set a transform on the data for this `DataSet`. + """Set a transform on the data for this `DataSet`. Here you can set custom a custom transform to both decode data from the database and encode data written to the database. This allows you to have dates @@ -883,49 +910,58 @@ def set_transform(self, fn: callable) -> None: the GUI and within PySimpleSQL. This transform happens only while PySimpleSQL actually reads from or writes to the database. - :param fn: A callable function to preform encode/decode. This function should + Args: + fn: A callable function to preform encode/decode. This function should take three arguments: query, row (which will be populated by a dictionary of the row data), and an encode parameter (1 to encode, 0 to decode - see constants `TFORM_ENCODE` and `TFORM_DECODE`). Note that this transform works on one row at a time. See the example `journal_with_data_manipulation.py` for a usage example. - :returns: None + + Returns: + None """ self.transform = fn def set_query(self, query: str) -> None: - """ - Set the query string for the `DataSet`. + """Set the query string for the `DataSet`. + + This is more for advanced users. It defaults to "SELECT * FROM {table};" + This can override the default - This is more for advanced users. It defaults to "SELECT * FROM {table};" This - can override the default + Args: + query: The query string you would like to associate with the table - :param query: The query string you would like to associate with the table - :returns: None + Returns: + None """ logger.debug(f"Setting {self.table} query to {query}") self.query = query def set_join_clause(self, clause: str) -> None: - """ - Set the `DataSet` object's join string. + """Set the `DataSet` object's join string. This is more for advanced users, as it will automatically generate from the database Relationships otherwise. - :param clause: The join clause, such as "LEFT JOIN That on This.pk=That.fk" - :returns: None + Args: + clause: The join clause, such as "LEFT JOIN That on This.pk=That.fk" + + Returns: + None """ logger.debug(f"Setting {self.table} join clause to {clause}") self.join_clause = clause def set_where_clause(self, clause: str) -> None: - """ - Set the `DataSet` object's where clause. + """Set the `DataSet` object's where clause. This is ADDED TO the auto-generated where clause from Relationship data - :param clause: The where clause, such as "WHERE pkThis=100" - :returns: None + Args: + clause: The where clause, such as "WHERE pkThis=100" + + Returns: + None """ logger.debug( f"Setting {self.table} where clause to {clause} for DataSet {self.key}" @@ -933,28 +969,32 @@ def set_where_clause(self, clause: str) -> None: self.where_clause = clause def set_order_clause(self, clause: str) -> None: - """ - Set the `DataSet` object's order clause. + """Set the `DataSet` object's order clause. This is more for advanced users, as it will automatically generate from the database Relationships otherwise. - :param clause: The order clause, such as "Order by name ASC" - :returns: None + Args: + clause: The order clause, such as "Order by name ASC" + + Returns: + None """ logger.debug(f"Setting {self.table} order clause to {clause}") self.order_clause = clause def update_column_info(self, column_info: ColumnInfo = None) -> None: - """ - Generate column information for the `DataSet` object. + """Generate column information for the `DataSet` object. This may need done, for example, when a manual query using joins is used. This is more for advanced users. - :param column_info: (optional) A `ColumnInfo` instance. Defaults to being - generated by the `SQLDriver`. - :returns: None + Args: + column_info: (optional) A `ColumnInfo` instance. Defaults to being generated + by the `SQLDriver`. + + Returns: + None """ # Now we need to set new column names, as the query could have changed if column_info is not None: @@ -963,30 +1003,34 @@ def update_column_info(self, column_info: ColumnInfo = None) -> None: self.column_info = self.driver.column_info(self.table) def set_description_column(self, column: str) -> None: - """ - Set the `DataSet` object's description column. + """Set the `DataSet` object's description column. - This is the column that will display in Listboxes, Comboboxes, Tables, etc. - By default, this is initialized to either the 'description','name' or 'title' + This is the column that will display in Listboxes, Comboboxes, Tables, etc. By + default, this is initialized to either the 'description','name' or 'title' column, or the 2nd column of the table if none of those columns exist. This method allows you to specify a different column to use as the description for the record. - :param column: The name of the column to use - :returns: None + Args: + column: The name of the column to use + + Returns: + None """ self.description_column = column def records_changed(self, column: str = None, recursive=True) -> bool: - """ - Checks if records have been changed. + """Checks if records have been changed. This is done by comparing PySimpleGUI control values with the stored `DataSet` values. - :param column: Limit the changed records search to just the supplied column name - :param recursive: True to check related `DataSet` instances - :returns: True or False on whether changed records were found + Args: + column: Limit the changed records search to just the supplied column name + recursive: True to check related `DataSet` instances + + Returns: + True or False on whether changed records were found """ logger.debug(f'Checking if records have changed in table "{self.table}"...') @@ -1063,17 +1107,19 @@ def records_changed(self, column: str = None, recursive=True) -> bool: def value_changed( self, column_name: str, old_value, new_value, is_checkbox: bool ) -> Union[Any, Boolean]: - """ - Verifies if a new value is different from an old value and returns the cast + """Verifies if a new value is different from an old value and returns the cast value ready to be inserted into a database. - :param column_name: The name of the column used in casting. - :param old_value: The value to check against. - :param new_value: The value being checked. - :param is_checkbox: Whether or not additional logic should be applied to handle - checkboxes. - :returns: The cast value ready to be inserted into a database if the new value - is different from the old value. Returns `Boolean.FALSE` otherwise. + Args: + column_name: The name of the column used in casting. + old_value: The value to check against. + new_value: The value being checked. + is_checkbox: Whether or not additional logic should be applied to handle + checkboxes. + + Returns: + The cast value ready to be inserted into a database if the new value is + different from the old value. Returns `Boolean.FALSE` otherwise. """ table_val = old_value # convert numpy to normal type @@ -1118,15 +1164,17 @@ def value_changed( def prompt_save( self, update_elements: bool = True ) -> Union[PROMPT_SAVE_PROCEED, PROMPT_SAVE_DISCARDED, PROMPT_SAVE_NONE]: - """ - Prompts the user, asking if they want to save when changes are detected. + """Prompts the user, asking if they want to save when changes are detected. This is called when the current record is about to change. - :param update_elements: (optional) Passed to `Form.save_records()` -> - `Form.save_records_recursive()` to update_elements. Additionally used to - discard changes if user reply's 'No' to prompt. - :returns: A prompt return value of one of the following: `PROMPT_PROCEED`, + Args: + update_elements: (optional) Passed to `Form.save_records()` -> + `Form.save_records_recursive()` to update_elements. Additionally used to + discard changes if user reply's 'No' to prompt. + + Returns: + A prompt return value of one of the following: `PROMPT_PROCEED`, `PROMPT_DISCARDED`, or `PROMPT_NONE`. """ # Return False if there is nothing to check or _prompt_save is False @@ -1178,25 +1226,27 @@ def requery( update_elements: bool = True, requery_dependents: bool = True, ) -> None: - """ - Requeries the table. + """Requeries the table. The `DataSet` object maintains an internal representation of the actual database table. The requery method will query the actual database and sync the `DataSet` object to it. - :param select_first: (optional) If True, the first record will be selected after - the requery. - :param filtered: (optional) If True, the relationships will be considered and an - appropriate WHERE clause will be generated. If False all records in the - table will be fetched. - :param update_elements: (optional) Passed to `DataSet.first()` to - update_elements. Note that the select_first parameter must equal True to use - this parameter. - :param requery_dependents: (optional) passed to `DataSet.first()` to - requery_dependents. Note that the select_first parameter must = True to use - this parameter. - :returns: None + Args: + select_first: (optional) If True, the first record will be selected after + the requery. + filtered: (optional) If True, the relationships will be considered and an + appropriate WHERE clause will be generated. If False all records in the + table will be fetched. + update_elements: (optional) Passed to `DataSet.first()` to update_elements. + Note that the select_first parameter must equal True to use this + parameter. + requery_dependents: (optional) passed to `DataSet.first()` to + requery_dependents. Note that the select_first parameter must = True to + use this parameter. + + Returns: + None """ join = "" where = "" @@ -1274,14 +1324,17 @@ def requery( def requery_dependents( self, child: bool = False, update_elements: bool = True ) -> None: - """ - Requery parent `DataSet` instances as defined by the relationships of the table. + """Requery parent `DataSet` instances as defined by the relationships of the + table. + + Args: + child: (optional) If True, will requery self. Default False; used to skip + requery when called by parent. + update_elements: (optional) passed to `DataSet.requery()` -> + `DataSet.first()` to update_elements. - :param child: (optional) If True, will requery self. Default False; used to skip - requery when called by parent. - :param update_elements: (optional) passed to `DataSet.requery()` -> - `DataSet.first()` to update_elements. - :returns: None + Returns: + None """ if child: # dependents=False: no recursive dependent requery @@ -1302,19 +1355,20 @@ def first( requery_dependents: bool = True, skip_prompt_save: bool = False, ) -> None: - """ - Move to the first record of the table. + """Move to the first record of the table. Only one entry in the table is ever considered "Selected" This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :returns: None + Args: + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + + Returns: + None """ logger.debug(f"Moving to the first record of table {self.table}") # prompt_save @@ -1340,19 +1394,20 @@ def last( requery_dependents: bool = True, skip_prompt_save: bool = False, ): - """ - Move to the last record of the table. + """Move to the last record of the table. - Only one entry in the table is ever considered "Selected" This is one of + Only one entry in the table is ever considered "Selected". This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :returns: None + Args: + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + + Returns: + None """ logger.debug(f"Moving to the last record of table {self.table}") # prompt_save @@ -1379,19 +1434,20 @@ def next( requery_dependents: bool = True, skip_prompt_save: bool = False, ): - """ - Move to the next record of the table. + """Move to the next record of the table. - Only one entry in the table is ever considered "Selected" This is one of + Only one entry in the table is ever considered "Selected". This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :returns: None + Args: + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + + Returns: + None """ if self.current_index < self.row_count - 1: logger.debug(f"Moving to the next record of table {self.table}") @@ -1418,19 +1474,20 @@ def previous( requery_dependents: bool = True, skip_prompt_save: bool = False, ): - """ - Move to the previous record of the table. + """Move to the previous record of the table. - Only one entry in the table is ever considered "Selected" This is one of + Only one entry in the table is ever considered "Selected". This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :returns: None + Args: + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + + Returns: + None """ if self.current_index > 0: logger.debug(f"Moving to the previous record of table {self.table}") @@ -1459,8 +1516,7 @@ def search( skip_prompt_save: bool = False, display_message: bool = None, ) -> Union[SEARCH_FAILED, SEARCH_RETURNED, SEARCH_ABORTED]: - """ - Move to the next record in the `DataSet` that contains `search_string`. + """Move to the next record in the `DataSet` that contains `search_string`. Successive calls will search from the current position, and wrap around back to the beginning. The search order from `DataSet.set_search_order()` will be used. @@ -1471,15 +1527,17 @@ def search( `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param search_string: The search string to look for - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :param display_message: Displays a message "Search Failed: ...", otherwise is - silent on fail. - :returns: One of the following search values: `SEARCH_FAILED`, - `SEARCH_RETURNED`, `SEARCH_ABORTED`. + Args: + search_string: The search string to look for + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + display_message: Displays a message "Search Failed: ...", otherwise is + silent on fail. + + Returns: + One of the following search values: `SEARCH_FAILED`, `SEARCH_RETURNED`, + `SEARCH_ABORTED`. """ # See if the string is an element name # TODO this is a bit of an ugly hack, but it works @@ -1600,21 +1658,22 @@ def set_by_index( skip_prompt_save: bool = False, omit_elements: List[str] = None, ) -> None: - """ - Move to the record of the table located at the specified index in DataSet. + """Move to the record of the table located at the specified index in DataSet. - Only one entry in the table is ever considered "Selected" This is one of + Only one entry in the table is ever considered "Selected". This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param index: The index of the record to move to. - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :param omit_elements: (optional) A list of elements to omit from updating - :returns: None + Args: + index: The index of the record to move to. + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + omit_elements: (optional) A list of elements to omit from updating + + Returns: + None """ # if already there if self.current_index == index: @@ -1648,24 +1707,26 @@ def set_by_pk( skip_prompt_save: bool = False, omit_elements: list[str] = None, ) -> None: - """ - Move to the record with this primary key. + """Move to the record with this primary key. This is useful when modifying a record (such as renaming). The primary key can be stored, the record re-named, and then the current record selection updated regardless of the new sort order. - Only one entry in the table is ever considered "Selected" This is one of + + Only one entry in the table is ever considered "Selected". This is one of several functions that influences which record is currently selected. See `DataSet.first()`, `DataSet.previous()`, `DataSet.next()`, `DataSet.last()`, `DataSet.search()`, `DataSet.set_by_pk()`, `DataSet.set_by_index()`. - :param pk: The record to move to containing the primary key - :param update_elements: (optional) Update the GUI elements after switching - records. - :param requery_dependents: (optional) Requery dependents after switching records - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :param omit_elements: (optional) A list of elements to omit from updating - :returns: None + Args: + pk: The record to move to containing the primary key + update_elements: (optional) Update the GUI elements after switching records. + requery_dependents: (optional) Requery dependents after switching records + skip_prompt_save: (optional) True to skip prompting to save dirty records + omit_elements: (optional) A list of elements to omit from updating + + Returns: + None """ logger.debug(f"Setting table {self.table} record by primary key {pk}") @@ -1692,15 +1753,17 @@ def set_by_pk( def get_current( self, column: str, default: Union[str, int] = "" ) -> Union[str, int]: - """ - Get the value for the supplied column in the current row. + """Get the value for the supplied column in the current row. You can also use indexing of the `Form` object to get the current value of a column I.e. frm[{DataSet}].[{column}]. - :param column: The column you want to get the value from - :param default: A value to return if the record is null - :returns: The value of the column requested + Args: + column: The column you want to get the value from + default: A value to return if the record is null + + Returns: + The value of the column requested """ logger.debug(f"Getting current record for {self.table}.{column}") if self.row_count: @@ -1712,18 +1775,20 @@ def get_current( def set_current( self, column: str, value: Union[str, int], write_event: bool = False ) -> None: - """ - Set the value for the supplied column in the current row, making a backup if + """Set the value for the supplied column in the current row, making a backup if needed. You can also use indexing of the `Form` object to set the current value of a column. I.e. frm[{DataSet}].[{column}] = 'New value'. - :param column: The column you want to set the value for - :param value: A value to set the current record's column to - :param write_event: (optional) If True, writes an event to PySimpleGui - as `after_record_edit`. - :returns: None + Args: + column: The column you want to set the value for + value: A value to set the current record's column to + write_event: (optional) If True, writes an event to PySimpleGui as + `after_record_edit`. + + Returns: + None """ logger.debug(f"Setting current record for {self.key}.{column} = {value}") self.backup_current_row() @@ -1745,15 +1810,17 @@ def set_current( def get_keyed_value( self, value_column: str, key_column: str, key_value: Union[str, int] ) -> Union[str, int, None]: - """ - Return `value_column` where` key_column`=`key_value`. + """Return `value_column` where` key_column`=`key_value`. Useful for datastores with key/value pairs. - :param value_column: The column to fetch the value from - :param key_column: The column in which to search for the value - :param key_value: The value to search for - :returns: Returns the value found in `value_column` + Args: + value_column: The column to fetch the value from + key_column: The column in which to search for the value + key_value: The value to search for + + Returns: + Returns the value found in `value_column` """ for _, row in self.rows.iterrows(): if row[key_column] == key_value: @@ -1761,18 +1828,18 @@ def get_keyed_value( return None def get_current_pk(self) -> int: - """ - Get the primary key of the currently selected record. + """Get the primary key of the currently selected record. - :returns: the primary key + Returns: + the primary key """ return self.get_current(self.pk_column) def get_current_row(self) -> Union[pd.Series, None]: - """ - Get the row for the currently selected record of this table. + """Get the row for the currently selected record of this table. - :returns: A pandas Series object + Returns: + A pandas Series object """ if not self.rows.empty: # force the current_index to be in bounds! @@ -1790,18 +1857,20 @@ def add_selector( where_column: str = None, where_value: str = None, ) -> None: - """ - Use an element such as a listbox, combobox or a table as a selector item for + """Use an element such as a listbox, combobox or a table as a selector item for this table. Note: This is not typically used by the end user, as this is called from the `selector()` convenience function. - :param element: the PySimpleGUI element used as a selector element - :param data_key: the `DataSet` item this selector will operate on - :param where_column: (optional) - :param where_value: (optional) - :returns: None + Args: + element: the PySimpleGUI element used as a selector element + data_key: the `DataSet` item this selector will operate on + where_column: (optional) + where_value: (optional) + + Returns: + None """ if not isinstance(element, (sg.Listbox, sg.Slider, sg.Combo, sg.Table)): raise RuntimeError( @@ -1820,17 +1889,19 @@ def add_selector( def insert_record( self, values: Dict[str : Union[str, int]] = None, skip_prompt_save: bool = False ) -> None: - """ - Insert a new record virtually in the `DataSet` object. + """Insert a new record virtually in the `DataSet` object. If values are passed, it will initially set those columns to the values (I.e. {'name': 'New Record', 'note': ''}), otherwise they will be fetched from the database if present. - :param values: column:value pairs - :param skip_prompt_save: Skip prompting the user to save dirty records before - the insert. - :returns: None + Args: + values: column:value pairs + skip_prompt_save: Skip prompting the user to save dirty records before the + insert. + + Returns: + None """ # prompt_save if ( @@ -1884,18 +1955,20 @@ def save_record( update_elements: bool = True, validate_fields: bool = None, ) -> int: - """ - Save the currently selected record. + """Save the currently selected record. Saves any changes made via the GUI back to the database. The before_save and after_save `DataSet.callbacks` will call your own functions for error checking if needed!. - :param display_message: Displays a message "Updates saved successfully", - otherwise is silent on success. - :param update_elements: Update the GUI elements after saving - :param validate_fields: Validate fields before saving to database. - :returns: SAVE_NONE, SAVE_FAIL or SAVE_SUCCESS masked with SHOW_MESSAGE + Args: + display_message: Displays a message "Updates saved successfully", otherwise + is silent on success. + update_elements: Update the GUI elements after saving + validate_fields: Validate fields before saving to database. + + Returns: + SAVE_NONE, SAVE_FAIL or SAVE_SUCCESS masked with SHOW_MESSAGE """ logger.debug(f"Saving records for table {self.table}...") if display_message is None: @@ -2184,16 +2257,19 @@ def save_record_recursive( check_prompt_save: bool = False, update_elements: bool = True, ) -> SaveResultsDict: - """ - Recursively save changes, taking into account the relationships of the tables. + """Recursively save changes, taking into account the relationships of the + tables. - :param results: Used in Form.save_records to collect DataSet.save_record - returns. Pass an empty dict to get list of {table : result} - :param display_message: Passed to DataSet.save_record. Displays a message - that updates were saved successfully, otherwise is silent on success. - :param check_prompt_save: Used when called from Form.prompt_save. Updates - elements without saving if individual `DataSet._prompt_save()` is False. - :returns: dict of {table : results} + Args: + results: Used in Form.save_records to collect DataSet.save_record returns. + Pass an empty dict to get list of {table : result} + display_message: Passed to DataSet.save_record. Displays a message that + updates were saved successfully, otherwise is silent on success. + check_prompt_save: Used when called from Form.prompt_save. Updates elements + without saving if individual `DataSet._prompt_save()` is False. + + Returns: + dict of {table : results} """ for rel in self.relationships: if rel.parent_table == self.table and rel.on_update_cascade: @@ -2219,15 +2295,17 @@ def save_record_recursive( def delete_record( self, cascade: bool = True ): # TODO: check return type, we return True below - """ - Delete the currently selected record. + """Delete the currently selected record. The before_delete and after_delete callbacks are run during this process to give some control over the process. - :param cascade: Delete child records (as defined by `Relationship`s that were - set up) before deleting this record. - :returns: None + Args: + cascade: Delete child records (as defined by `Relationship`s that were set + up) before deleting this record. + + Returns: + None """ # Ensure that there is actually something to delete if not self.row_count: @@ -2297,16 +2375,18 @@ def duplicate_record( children: bool = None, skip_prompt_save: bool = False, ) -> Union[bool, None]: # TODO check return type, returns True within - """ - Duplicate the currently selected record. + """Duplicate the currently selected record. The before_duplicate and after_duplicate callbacks are run during this process to give some control over the process. - :param children: Duplicate child records (as defined by `Relationship`s that - were set up) before duplicating this record. - :param skip_prompt_save: (optional) True to skip prompting to save dirty records - :returns: None + Args: + children: Duplicate child records (as defined by `Relationship`s that were + set up) before duplicating this record. + skip_prompt_save: (optional) True to skip prompting to save dirty records + + Returns: + None """ # Ensure that there is actually something to duplicate if not self.row_count or self.pk_is_virtual(): @@ -2416,14 +2496,16 @@ def duplicate_record( return None def get_description_for_pk(self, pk: int) -> Union[str, int, None]: - """ - Get the description from the `DataSet` on the matching pk. + """Get the description from the `DataSet` on the matching pk. Return the description from `DataSet.description_column` for the row where the `DataSet.pk_column` = `pk`. - :param pk: The primary key from which to find the description for - :returns: The value found in the description column, or None if nothing is found + Args: + pk: The primary key from which to find the description for + + Returns: + The value found in the description column, or None if nothing is found """ # We don't want to update other views comboboxes/tableviews until row is # actually saved. So first check their current @@ -2441,11 +2523,13 @@ def virtual_pks(self): return self.rows.attrs["virtual"] def pk_is_virtual(self, pk: int = None) -> bool: - """ - Check whether pk is virtual + """Check whether pk is virtual. - :param pk: The pk to check. If None, the pk of the current row will be checked. - :returns: True or False based on whether the row is virtual + Args: + pk: The pk to check. If None, the pk of the current row will be checked. + + Returns: + True or False based on whether the row is virtual """ if not self.row_count: return False @@ -2457,11 +2541,11 @@ def pk_is_virtual(self, pk: int = None) -> bool: @property def row_count(self) -> int: - """ - Returns the number of rows in the dataset. If the dataset is not a pandas + """Returns the number of rows in the dataset. If the dataset is not a pandas DataFrame, returns 0. - :returns: The number of rows in the dataset. + Returns: + The number of rows in the dataset. """ if isinstance(self.rows, pd.DataFrame): return len(self.rows.index) @@ -2469,15 +2553,15 @@ def row_count(self) -> int: @property def current_row_has_backup(self) -> bool: - """ - Returns True if the current_row has a backup row, and False otherwise. + """Returns True if the current_row has a backup row, and False otherwise. A pandas Series object is stored rows.attrs["row_backup"] before a CellEdit or SyncSelector operation is initiated, so that it can be compared in `Dataset.records_changed` and `Dataset.save_record` or used to restore if changes are discarded during a `prompt_save` operations. - :returns: True if a backup row is present that matches, and False otherwise. + Returns: + True if a backup row is present that matches, and False otherwise. """ if self.rows is None or self.rows.empty: return False @@ -2490,16 +2574,14 @@ def current_row_has_backup(self) -> bool: return False def purge_row_backup(self) -> None: - """ - Deletes the backup row from the dataset. + """Deletes the backup row from the dataset. This method sets the "row_backup" attribute of the dataset to None. """ self.rows.attrs["row_backup"] = None def restore_current_row(self) -> None: - """ - Restores the backup row to the current row in `DataSet.rows`. + """Restores the backup row to the current row in `DataSet.rows`. This method replaces the current row in the dataset with the backup row, if a backup row is present. @@ -2508,8 +2590,7 @@ def restore_current_row(self) -> None: self.rows.iloc[self.current_index] = self.rows.attrs["row_backup"].copy() def get_original_current_row(self) -> pd.Series: - """ - Returns a copy of current row as it was fetched in a query from `SQLDriver`. + """Returns a copy of current row as it was fetched in a query from `SQLDriver`. If a backup of the current row is present, this method returns a copy of that row. Otherwise, it returns a copy of the current row. Returns None if @@ -2533,19 +2614,21 @@ def table_values( apply_search_filter: bool = False, apply_cell_format_fn: bool = True, ) -> List[_TableRow]: - """ - Create a values list of `_TableRows`s for use in a PySimpleGUI Table element. - - :param columns: A list of column names to create table values for. - Defaults to getting them from the `DataSet.rows` DataFrame. - :param mark_unsaved: Place a marker next to virtual records, or records with - unsaved changes. - :param apply_search_filter: Filter rows to only those columns in - `DataSet.search_order` that contain `DataSet.search_string`. - :param apply_cell_format_fn: If set, apply() - `DataSet.column_info[col].cell_format_fn` to rows column - :returns: A list of `_TableRow`s suitable for using with PySimpleGUI Table - element values. + """Create a values list of `_TableRows`s for use in a PySimpleGUI Table element. + + Args: + columns: A list of column names to create table values for. Defaults to + getting them from the `DataSet.rows` DataFrame. + mark_unsaved: Place a marker next to virtual records, or records with + unsaved changes. + apply_search_filter: Filter rows to only those columns in + `DataSet.search_order` that contain `DataSet.search_string`. + apply_cell_format_fn: If set, apply() + `DataSet.column_info[col].cell_format_fn` to rows column + + Returns: + A list of `_TableRow`s suitable for using with PySimpleGUI Table element + values. """ if not self.row_count: return [] @@ -2625,11 +2708,13 @@ def table_values( ] def column_likely_in_selector(self, column: str) -> bool: - """ - Determines whether the given column is likely to be displayed in a selector. + """Determines whether the given column is likely to be displayed in a selector. + + Args: + column: The name of the column to check. - :param column: The name of the column to check. - :return: True if the column is likely to be displayed, False otherwise. + Returns: + True if the column is likely to be displayed, False otherwise. """ # If there are no sg.Table selectors, return False if not any( @@ -2651,11 +2736,13 @@ def column_likely_in_selector(self, column: str) -> bool: def combobox_values( self, column_name, insert_placeholder: bool = True ) -> List[_ElementRow] or None: - """ - Returns the values to use in a sg.Combobox as a list of _ElementRow objects. + """Returns the values to use in a sg.Combobox as a list of _ElementRow objects. - :param column_name: The name of the table column for which to get the values. - :returns: A list of _ElementRow objects representing the possible values for the + Args: + column_name: The name of the table column for which to get the values. + + Returns: + A list of _ElementRow objects representing the possible values for the combobox column, or None if no matching relationship is found. """ if not self.row_count: @@ -2688,11 +2775,13 @@ def combobox_values( return combobox_values def get_related_table_for_column(self, column: str) -> str: - """ - Get parent table name as it relates to this column. + """Get parent table name as it relates to this column. - :param column: The column name to get related table information for - :returns: The name of the related table, or the current table if none are found + Args: + column: The column name to get related table information for + + Returns: + The name of the related table, or the current table if none are found """ rels = self.relationships.get_rels_for(self.table) for rel in rels: @@ -2701,18 +2790,19 @@ def get_related_table_for_column(self, column: str) -> str: return self.table # None could be found, return our own table instead def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): - """ - Maps foreign key descriptions to the specified columns in the given DataFrame. - If passing in a DataSet rows, please pass in a copy: frm[data_key].rows.copy() - - :param rows: The DataFrame containing the data to be processed. - :param columns: (Optional) The list of column names to map foreign key - descriptions to. If none are provided, all columns of the DataFrame will be - searched for foreign-key relationships. - - :returns: The processed DataFrame with foreign key descriptions mapped to the + """Maps foreign key descriptions to the specified columns in the given + DataFrame. If passing in a DataSet rows, please pass in a copy: + frm[data_key].rows.copy() + + Args: + rows: The DataFrame containing the data to be processed. + columns: (Optional) The list of column names to map foreign key descriptions + to. If none are provided, all columns of the DataFrame will be searched + for foreign-key relationships. + + Returns: + The processed DataFrame with foreign key descriptions mapped to the specified columns. - """ if columns is None: columns = rows.columns @@ -2756,21 +2846,23 @@ def quick_editor( skip_prompt_save: bool = False, column_attributes: dict = None, ) -> None: - """ - The quick editor is a dynamic PySimpleGUI Window for quick editing of tables. + """The quick editor is a dynamic PySimpleGUI Window for quick editing of tables. This is very useful for putting a button next to a combobox or listbox so that the available values can be added/edited/deleted easily. Note: This is not typically used by the end user, as it can be configured from the `field()` convenience function. - :param pk_update_funct: (optional) A function to call to determine the pk to - select by default when the quick editor loads. - :param funct_param: (optional) A parameter to pass to the `pk_update_funct` - :param skip_prompt_save: (Optional) True to skip prompting to save dirty records - :param column_attributes: (Optional) Dictionary specifying column attributes - for `DataSet.column_info`. The dictionary should be in the form - {column_name: {attribute: value}}. - :returns: None + Args: + pk_update_funct: (optional) A function to call to determine the pk to select + by default when the quick editor loads. + funct_param: (optional) A parameter to pass to the `pk_update_funct` + skip_prompt_save: (Optional) True to skip prompting to save dirty records + column_attributes: (Optional) Dictionary specifying column attributes for + `DataSet.column_info`. The dictionary should be in the form + {column_name: {attribute: value}}. + + Returns: + None """ # prompt_save if ( @@ -2913,8 +3005,7 @@ def quick_editor( logger.debug(f"This event ({event}) is not yet handled.") def add_simple_transform(self, transforms: SimpleTransformsDict) -> None: - """ - Merge a dictionary of transforms into the `DataSet._simple_transform` + """Merge a dictionary of transforms into the `DataSet._simple_transform` dictionary. Example: @@ -2923,9 +3014,13 @@ def add_simple_transform(self, transforms: SimpleTransformsDict) -> None: 'decode' : lambda row,col: datetime.utcfromtimestamp(int(row[col])).strftime('%m/%d/%y'), # fmt: skip 'encode' : lambda row,col: datetime.strptime(row[col], '%m/%d/%y').replace(tzinfo=timezone.utc).timestamp(), # fmt: skip }} - :param transforms: A dict of dicts containing either 'encode' or 'decode' along - with a callable to do the transform. See example above - :returns: None + + Args: + transforms: A dict of dicts containing either 'encode' or 'decode' along + with a callable to do the transform. See example above + + Returns: + None """ # noqa: E501 for k, v in transforms.items(): if not callable(v): @@ -2933,10 +3028,10 @@ def add_simple_transform(self, transforms: SimpleTransformsDict) -> None: self._simple_transform[k] = v def purge_virtual(self) -> None: - """ - Purge virtual rows from the DataFrame. + """Purge virtual rows from the DataFrame. - :returns: None + Returns: + None """ # remove the rows where virtual is True in place, along with the corresponding # virtual attribute @@ -2945,15 +3040,17 @@ def purge_virtual(self) -> None: self.rows.attrs["virtual"] = [] def sort_by_column(self, column: str, table: str, reverse=False) -> None: - """ - Sort the DataFrame by column. Using the mapped relationships of the database, + """Sort the DataFrame by column. Using the mapped relationships of the database, foreign keys will automatically sort based on the parent table's description column, rather than the foreign key number. - :param column: The name of the column to sort the DataFrame by - :param table: The name of the table the column belongs to - :param reverse: Reverse the sort; False = ASC, True = DESC - :returns: None + Args: + column: The name of the column to sort the DataFrame by + table: The name of the table the column belongs to + reverse: Reverse the sort; False = ASC, True = DESC + + Returns: + None """ # Target sorting by this DataFrame @@ -3004,61 +3101,64 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: self.rows = self.rows.drop(columns=tmp_column, errors="ignore") def sort_by_index(self, index: int, table: str, reverse=False): - """ - Sort the self.rows DataFrame by column index Using the mapped relationships of - the database, foreign keys will automatically sort based on the parent table's - description column, rather than the foreign key number. + """Sort the self.rows DataFrame by column index Using the mapped relationships + of the database, foreign keys will automatically sort based on the parent + table's description column, rather than the foreign key number. + + Args: + index: The index of the column to sort the DateFrame by + table: The name of the table the column belongs to + reverse: Reverse the sort; False = ASC, True = DESC - :param index: The index of the column to sort the DateFrame by - :param table: The name of the table the column belongs to - :param reverse: Reverse the sort; False = ASC, True = DESC - :returns: None + Returns: + None """ column = self.rows.columns[index] self.sort_by_column(column, table, reverse) def store_sort_settings(self) -> list: - """ - Store the current sort settingg. Sort settings are just the sort column and - reverse setting. Sort order can be restored with - `DataSet.load_sort_settings()`. + """Store the current sort settingg. Sort settings are just the sort column and + reverse setting. Sort order can be restored with `DataSet.load_sort_settings()`. - :returns: A list containing the sort_column and the sort_reverse + Returns: + A list containing the sort_column and the sort_reverse """ return [self.rows.attrs["sort_column"], self.rows.attrs["sort_reverse"]] def load_sort_settings(self, sort_settings: list) -> None: - """ - Load a previously stored sort setting. Sort settings are just the sort columm + """Load a previously stored sort setting. Sort settings are just the sort columm and reverse setting. - :param sort_settings: A list as returned by `DataSet.store_sort_settings()` + Args: + sort_settings: A list as returned by `DataSet.store_sort_settings()` """ self.rows.attrs["sort_column"] = sort_settings[0] self.rows.attrs["sort_reverse"] = sort_settings[1] def sort_reset(self) -> None: - """ - Reset the sort order to the original order as defined by the DataFram index + """Reset the sort order to the original order as defined by the DataFram index. - :returns: None + Returns: + None """ # Restore the original sort order self.rows = self.rows.sort_index() def sort(self, table: str, update_elements: bool = True, sort_order=None) -> None: - """ - Sort according to the internal sort_column and sort_reverse variables. This is a - good way to re-sort without changing the sort_cycle. + """Sort according to the internal sort_column and sort_reverse variables. This + is a good way to re-sort without changing the sort_cycle. - :param table: The table associated with this DataSet. Passed along to - `DataSet.sort_by_column()` - :param update_elements: Update associated selectors and navigation buttons, and - table header sort marker. - :param sort_order: Passed to `Dataset.update_headings`. A SORT_* constant - (SORT_NONE, SORT_ASC, SORT_DESC). Note that the update_elements parameter - must = True to use this parameter. - :returns: None + Args: + table: The table associated with this DataSet. Passed along to + `DataSet.sort_by_column()` + update_elements: Update associated selectors and navigation buttons, and + table header sort marker. + sort_order: Passed to `Dataset.update_headings`. A SORT_* constant + (SORT_NONE, SORT_ASC, SORT_DESC). Note that the update_elements + parameter must = True to use this parameter. + + Returns: + None """ pk = self.get_current_pk() if self.rows.attrs["sort_column"] is None: @@ -3081,15 +3181,17 @@ def sort(self, table: str, update_elements: bool = True, sort_order=None) -> Non self._update_headings(self.rows.attrs["sort_column"], sort_order) def sort_cycle(self, column: str, table: str, update_elements: bool = True) -> int: - """ - Cycle between original sort order of the DataFrame, ASC by column, and DESC by - column with each call. + """Cycle between original sort order of the DataFrame, ASC by column, and DESC + by column with each call. - :param column: The column name to cycle the sort on - :param table: The table that the column belongs to - :param update_elements: Passed to `Dataset.sort` to update update associated - selectors and navigation buttons, and table header sort marker. - :returns: A sort constant; SORT_NONE, SORT_ASC, or SORT_DESC + Args: + column: The column name to cycle the sort on + table: The table that the column belongs to + update_elements: Passed to `Dataset.sort` to update update associated + selectors and navigation buttons, and table header sort marker. + + Returns: + A sort constant; SORT_NONE, SORT_ASC, or SORT_DESC """ if column != self.rows.attrs["sort_column"]: self.rows.attrs["sort_column"] = column @@ -3117,14 +3219,16 @@ def _update_headings(self, column, sort_order): ) def insert_row(self, row: dict, idx: int = None) -> None: - """ - Insert a new virtual row into the DataFrame. Virtual rows are ones that exist + """Insert a new virtual row into the DataFrame. Virtual rows are ones that exist in memory, but not in the database. When a save action is performed, virtual rows will be added into the database. - :param row: A dict representation of a row of data - :param idx: The index where the row should be inserted (default to last index) - :returns: None + Args: + row: A dict representation of a row of data + idx: The index where the row should be inserted (default to last index) + + Returns: + None """ row_series = pd.Series(row, dtype=object) # Infer better data types for the Series @@ -3154,14 +3258,16 @@ def validate_field( widget=None, display_message: bool = False, ) -> bool: - """ - Validate the given field value for the specified column. + """Validate the given field value for the specified column. + + Args: + column_name: The name of the column to validate the field against. + new_value: The new value to validate. + widget: The widget associated with the field. (Optional) + display_message: Flag to display validation messages. (Default: False) - :param column_name: The name of the column to validate the field against. - :param new_value: The new value to validate. - :param widget: The widget associated with the field. (Optional) - :param display_message: Flag to display validation messages. (Default: False) - :return: True if the field value is valid, False otherwise. + Returns: + True if the field value is valid, False otherwise. """ if column_name in self.column_info: @@ -3188,42 +3294,44 @@ def validate_field( @dc.dataclass(eq=False) class Form: - """ - `Form` class. + """`Form` class. Maintains an internal version of the actual database `DataSet` objects can be accessed by key, I.e. frm['data_key']. - :param driver: Supported `SQLDriver`. See `Sqlite()`, `Mysql()`, `Postgres()` - :param bind_window: Bind this window to the `Form` - :param parent: (optional)Parent `Form` to base dataset off of - :param filter: (optional) Only import elements with the same filter set. - Typically set with `field()`, but can also be set manually as a dict with - the key 'filter' set in the element's metadata - :param select_first: (optional) Default:True. For each top-level parent, selects - first row, populating children as well. - :param prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when - dirty records are present. - Two modes available, (if pysimplesql is imported as `ss`) use: - - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are present. - :param save_quiet: (optional) Default:False. True to skip info popup on save. - Error popups will still be shown. - :param duplicate_children: (optional) Default:True. If record has children, - prompt user to choose to duplicate current record, or both. - :param description_column_names: (optional) A list of names to use for the - DataSet object's description column, displayed in Listboxes, Comboboxes, and - Tables instead of the primary key. The first matching column of the table is - given priority. If no match is found, the second column is used. Default - list: ['description', 'name', 'title']. - :param live_update: (optional) Default value is False. If True, changes made in - a field will be immediately pushed to associated selectors. If False, - changes will be pushed only after a save action. - :param validate_mode: Passed to `DataSet` init to set validate mode. - `ss.ValidateMode.STRICT` to prevent invalid values from being entered. - `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation - occurs before saving to the database. - :returns: None + Args: + driver: Supported `SQLDriver`. See `Sqlite()`, `Mysql()`, `Postgres()` + bind_window: Bind this window to the `Form` + parent: (optional)Parent `Form` to base dataset off of + filter: (optional) Only import elements with the same filter set. Typically set + with `field()`, but can also be set manually as a dict with the key 'filter' + set in the element's metadata + select_first: (optional) Default:True. For each top-level parent, selects first + row, populating children as well. + prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when dirty + records are present. Two modes available, (if pysimplesql is imported as + `ss`) use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are + present. - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are + present. + save_quiet: (optional) Default:False. True to skip info popup on save. Error + popups will still be shown. + duplicate_children: (optional) Default:True. If record has children, prompt user + to choose to duplicate current record, or both. + description_column_names: (optional) A list of names to use for the DataSet + object's description column, displayed in Listboxes, Comboboxes, and Tables + instead of the primary key. The first matching column of the table is given + priority. If no match is found, the second column is used. Default list: + ['description', 'name', 'title']. + live_update: (optional) Default value is False. If True, changes made in a field + will be immediately pushed to associated selectors. If False, changes will + be pushed only after a save action. + validate_mode: Passed to `DataSet` init to set validate mode. + `ss.ValidateMode.STRICT` to prevent invalid values from being entered. + `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation + occurs before saving to the database. + + Returns: + None """ instances: ClassVar[List[Form]] = [] # Track our instances @@ -3296,10 +3404,10 @@ def __getitem__(self, key: str) -> DataSet: ) from e def close(self, reset_keygen: bool = True, close_driver: bool = True): - """ - Safely close out the `Form`. + """Safely close out the `Form`. - :param reset_keygen: True to reset the keygen for this `Form` + Args: + reset_keygen: True to reset the keygen for this `Form` """ # First delete the dataset associated DataSet.purge_form(self, reset_keygen) @@ -3310,16 +3418,18 @@ def close(self, reset_keygen: bool = True, close_driver: bool = True): self.driver.close() def bind(self, win: sg.Window) -> None: - """ - Bind the PySimpleGUI Window to the Form for the purpose of GUI element, event + """Bind the PySimpleGUI Window to the Form for the purpose of GUI element, event and relationship mapping. This can happen automatically on `Form` creation with the bind parameter and is not typically called by the end user. This function literally just groups all the auto_* methods. See `Form.auto_add_tables()`, `SQLDriver.auto_add_relationships()`, `Form.auto_map_elements()`, `Form.auto_map_events()`. - :param win: The PySimpleGUI window - :returns: None + Args: + win: The PySimpleGUI window + + Returns: + None """ logger.info("Binding Window to Form") self.window = win @@ -3336,41 +3446,45 @@ def bind(self, win: sg.Window) -> None: logger.debug("Binding finished!") def execute(self, query: str) -> pd.DataFrame: - """ - Convenience function to pass along to `SQLDriver.execute()`. + """Convenience function to pass along to `SQLDriver.execute()`. - :param query: The query to execute - :returns: A pandas DataFrame object with attrs set for lastrowid and exception + Args: + query: The query to execute + + Returns: + A pandas DataFrame object with attrs set for lastrowid and exception """ return self.driver.execute(query) def commit(self) -> None: - """ - Convenience function to pass along to `SQLDriver.commit()`. + """Convenience function to pass along to `SQLDriver.commit()`. - :returns: None + Returns: + None """ self.driver.commit() def set_callback( self, callback_name: str, fctn: Callable[[Form, sg.Window], Union[None, bool]] ) -> None: - """ - Set `Form` callbacks. A runtime error will be raised if the callback is not + """Set `Form` callbacks. A runtime error will be raised if the callback is not supported. The following callbacks are supported: update_elements Called after elements are updated via `Form.update_elements()`. This allows for other GUI manipulation on each update of the GUI edit_enable Called before editing mode is enabled. This can be useful for asking for a password for example edit_disable Called after the editing mode is disabled. - {element_name} Called while updating MAPPED element. This overrides the - default element update implementation. Note that the {element_name} callback - function needs to return a value to pass to Win[element].update() + {element_name} Called while updating MAPPED element. This overrides the default + element update implementation. Note that the {element_name} callback function + needs to return a value to pass to Win[element].update() - :param callback_name: The name of the callback, from the list above - :param fctn: The function to call. Note, the function must take in two - parameters, a Form instance, and a PySimpleGUI.Window instance - :returns: None + Args: + callback_name: The name of the callback, from the list above + fctn: The function to call. Note, the function must take in two parameters, + a Form instance, and a PySimpleGUI.Window instance + + Returns: + None """ logger.info(f"Callback {callback_name} being set on Form") supported = ["update_elements", "edit_enable", "edit_disable"] @@ -3400,22 +3514,23 @@ def add_dataset( query: str = "", order_clause: str = "", ) -> None: - """ - Manually add a `DataSet` object to the `Form` When you attach to a database, + """Manually add a `DataSet` object to the `Form` When you attach to a database, PySimpleSQL isn't aware of what it contains until this command is run Note that `Form.auto_add_datasets()` does this automatically, which is called when a `Form` is created. - :param data_key: The key to give this `DataSet`. Use frm['data_key'] to access - it. - :param table: The name of the table in the database - :param pk_column: The primary key column of the table in the database - :param description_column: The column to be used to display to users in - listboxes, comboboxes, etc. - :param query: The initial query for the table. Auto generates "SELECT * FROM - {table}" if none is passed - :param order_clause: The initial sort order for the query - :returns: None + Args: + data_key: The key to give this `DataSet`. Use frm['data_key'] to access it. + table: The name of the table in the database + pk_column: The primary key column of the table in the database + description_column: The column to be used to display to users in listboxes, + comboboxes, etc. + query: The initial query for the table. Auto generates "SELECT * FROM + {table}" if none is passed + order_clause: The initial sort order for the query + + Returns: + None """ self.datasets.update( { @@ -3440,19 +3555,21 @@ def set_fk_column_cascade( update_cascade: bool = None, delete_cascade: bool = None, ) -> None: - """ - Set a foreign key's update_cascade and delete_cascade behavior. + """Set a foreign key's update_cascade and delete_cascade behavior. `SQLDriver.auto_add_relationships()` does this automatically from the database schema. - :param child_table: Child table with the foreign key. - :param fk_column: Foreign key column of the child table. - :param update_cascade: True to requery and filter child table on selected parent - primary key. - :param delete_cascade: True to delete dependent child records if parent record - is deleted. - :returns: None + Args: + child_table: Child table with the foreign key. + fk_column: Foreign key column of the child table. + update_cascade: True to requery and filter child table on selected parent + primary key. + delete_cascade: True to delete dependent child records if parent record is + deleted. + + Returns: + None """ for rel in self.relationships: if rel.child_table == child_table and rel.fk_column == fk_column: @@ -3463,15 +3580,15 @@ def set_fk_column_cascade( rel.delete_cascade = delete_cascade def auto_add_datasets(self) -> None: - """ - Automatically add `DataSet` objects from the database by looping through the + """Automatically add `DataSet` objects from the database by looping through the tables available and creating a `DataSet` object for each. Each dataset key is an optional prefix plus the name of the table. When you attach to a sqlite database, PySimpleSQL isn't aware of what it contains until this command is run. This is called automatically when a `Form ` is created. Note that `Form.add_table()` can do this manually on a per-table basis. - :returns: None + Returns: + None """ logger.info( "Automatically generating dataset for each table in the sqlite database" @@ -3511,8 +3628,7 @@ def map_element( where_column: str = None, where_value: str = None, ) -> None: - """ - Map a PySimpleGUI element to a specific `DataSet` column. This is what makes + """Map a PySimpleGUI element to a specific `DataSet` column. This is what makes the GUI automatically update to the contents of the database. This happens automatically when a PySimpleGUI Window is bound to a `Form` by using the bind parameter of `Form` creation, or by executing `Form.auto_map_elements()` as long @@ -3520,12 +3636,15 @@ def map_element( manually map any element to any `DataSet` column regardless of metadata configuration. - :param element: A PySimpleGUI Element - :param dataset: A `DataSet` object - :param column: The name of the column to bind to the element - :param where_column: Used for ke, value shorthand TODO: expand on this - :param where_value: Used for ey, value shorthand TODO: expand on this - :returns: None + Args: + element: A PySimpleGUI Element + dataset: A `DataSet` object + column: The name of the column to bind to the element + where_column: Used for ke, value shorthand TODO: expand on this + where_value: Used for ey, value shorthand TODO: expand on this + + Returns: + None """ logger.debug(f"Mapping element {element.key}") self.element_map.append( @@ -3533,10 +3652,15 @@ def map_element( ) def add_info_element(self, element: Union[sg.StatusBar, sg.Text]) -> None: - """ - Add an element to be updated with info messages. Must be either - :param element: A PySimpleGUI Element - :returns: None + """Add an element to be updated with info messages. + + Must be either + + Args: + element: A PySimpleGUI Element + + Returns: + None """ if not isinstance(element, (sg.StatusBar, sg.Text)): logger.debug(f"Can only add info {element!s}") @@ -3545,8 +3669,7 @@ def add_info_element(self, element: Union[sg.StatusBar, sg.Text]) -> None: self.popup.info_elements.append(element) def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: - """ - Automatically map PySimpleGUI Elements to `DataSet` columns. A special naming + """Automatically map PySimpleGUI Elements to `DataSet` columns. A special naming convention has to be used for automatic mapping to happen. Note that `Form.map_element()` can be used to manually map an Element to a column. Automatic mapping relies on a special naming convention as well as certain data @@ -3559,9 +3682,12 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: want, but the metadata must contain a dict with the key of 'type' set to TPE_SELECTOR. - :param win: A PySimpleGUI Window - :param keys: (optional) Limit the auto mapping to this list of Element keys - :returns: None + Args: + win: A PySimpleGUI Window + keys: (optional) Limit the auto mapping to this list of Element keys + + Returns: + None """ logger.info("Automapping elements") # Clear previously mapped elements so successive calls won't produce duplicates @@ -3687,13 +3813,16 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: def set_element_clauses( self, element: sg.Element, where_clause: str = None, order_clause: str = None ) -> None: - """ - Set the where and/or order clauses for the specified element in the element map. + """Set the where and/or order clauses for the specified element in the element + map. + + Args: + element: A PySimpleGUI Element + where_clause: (optional) The where clause to set + order_clause: (optional) The order clause to set - :param element: A PySimpleGUI Element - :param where_clause: (optional) The where clause to set - :param order_clause: (optional) The order clause to set - :returns: None + Returns: + None """ for mapped in self.element_map: if mapped.element == element: @@ -3703,20 +3832,22 @@ def set_element_clauses( def map_event( self, event: str, fctn: Callable[[None], None], table: str = None ) -> None: - """ - Manually map a PySimpleGUI event (returned by Window.read()) to a callable. The - callable will execute when the event is detected by `Form.process_events()`. + """Manually map a PySimpleGUI event (returned by Window.read()) to a callable. + The callable will execute when the event is detected by `Form.process_events()`. Most users will not have to manually map any events, as `Form.auto_map_events()` will create most needed events when a PySimpleGUI Window is bound to a `Form` by using the bind parameter of `Form` creation, or by executing `Form.auto_map_elements()`. - :param event: The event to watch for, as returned by PySimpleGUI Window.read() - (an element name for example) - :param fctn: The callable to run when the event is detected. It should take no - parameters and have no return value - :param table: (optional) currently not used - :returns: None + Args: + event: The event to watch for, as returned by PySimpleGUI Window.read() (an + element name for example) + fctn: The callable to run when the event is detected. It should take no + parameters and have no return value + table: (optional) currently not used + + Returns: + None """ dic = {"event": event, "function": fctn, "table": table} logger.debug(f"Mapping event {event} to function {fctn}") @@ -3725,16 +3856,18 @@ def map_event( def replace_event( self, event: str, fctn: Callable[[None], None], table: str = None ) -> None: - """ - Replace an event that was manually mapped with `Form.auto_map_events()` or + """Replace an event that was manually mapped with `Form.auto_map_events()` or `Form.map_event()`. The callable will execute. - :param event: The event to watch for, as returned by PySimpleGUI Window.read() - (an element name for example) - :param fctn: The callable to run when the event is detected. It should take no - parameters and have no return value - :param table: (optional) currently not used - :returns: None + Args: + event: The event to watch for, as returned by PySimpleGUI Window.read() (an + element name for example) + fctn: The callable to run when the event is detected. It should take no + parameters and have no return value + table: (optional) currently not used + + Returns: + None """ for e in self.event_map: if e["event"] == event: @@ -3742,16 +3875,18 @@ def replace_event( e["table"] = table if table is not None else e["table"] def auto_map_events(self, win: sg.Window) -> None: - """ - Automatically map events. pysimplesql relies on certain events to function + """Automatically map events. pysimplesql relies on certain events to function properly. This method maps all the record navigation (previous, next, etc.) and database actions (insert, delete, save, etc.). Note that the event mapper is very general-purpose, and you can add your own event triggers to the mapper using `Form.map_event()`, or even replace one of the auto-generated ones if you have specific needs by using `Form.replace_event()`. - :param win: A PySimpleGUI Window - :returns: None + Args: + win: A PySimpleGUI Window + + Returns: + None """ logger.info("Automapping events") # Clear mapped events to ensure successive calls won't produce duplicates @@ -3839,13 +3974,13 @@ def auto_map_events(self, win: sg.Window) -> None: self.map_event(key, funct, data_key) def edit_protect(self) -> None: - """ - The edit protect system allows records to be protected from accidental editing - by disabling the insert, delete, duplicate and save buttons on the GUI. A - button to toggle the edit protect mode can easily be added by using the + """The edit protect system allows records to be protected from accidental + editing by disabling the insert, delete, duplicate and save buttons on the GUI. + A button to toggle the edit protect mode can easily be added by using the `actions()` convenience function. - :returns: None + Returns: + None """ logger.debug("Toggling edit protect mode.") # Callbacks @@ -3866,20 +4001,20 @@ def edit_protect(self) -> None: self.update_elements(edit_protect_only=True) def get_edit_protect(self) -> bool: - """ - Get the current edit protect state. + """Get the current edit protect state. - :returns: True if edit protect is enabled, False if not enabled + Returns: + True if edit protect is enabled, False if not enabled """ return self._edit_protect def prompt_save(self) -> PromptSaveValue: - """ - Prompt to save if any GUI changes are found the affect any table on this form. - The helps prevent data entry loss when performing an action that changes the - current record of a `DataSet`. + """Prompt to save if any GUI changes are found the affect any table on this + form. The helps prevent data entry loss when performing an action that changes + the current record of a `DataSet`. - :returns: One of the prompt constant values: PROMPT_SAVE_PROCEED, + Returns: + One of the prompt constant values: PROMPT_SAVE_PROCEED, PROMPT_SAVE_DISCARDED, PROMPT_SAVE_NONE """ user_prompted = False # Has the user been prompted yet? @@ -3911,26 +4046,30 @@ def prompt_save(self) -> PromptSaveValue: return PROMPT_SAVE_PROCEED if user_prompted else PROMPT_SAVE_NONE def set_prompt_save(self, mode: int) -> None: - """ - Set the prompt to save action when navigating records for all `DataSet` objects - associated with this `Form`. + """Set the prompt to save action when navigating records for all `DataSet` + objects associated with this `Form`. + + Args: + mode: a constant value. If pysimplesql is imported as `ss`, use: + `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. + `ss.AUTOSAVE_MODE` to autosave when unsaved changes are present. - :param mode: a constant value. If pysimplesql is imported as `ss`, use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - `ss.AUTOSAVE_MODE` to autosave when unsaved changes are present. - :returns: None + Returns: + None """ self._prompt_save = mode for data_key in self.datasets: self[data_key].set_prompt_save(mode) def set_force_save(self, force: bool = False) -> None: - """ - Force save without checking for changes first, so even an unchanged record will - be written back to the database. + """Force save without checking for changes first, so even an unchanged record + will be written back to the database. + + Args: + force: True to force unchanged records to save. - :param force: True to force unchanged records to save. - :returns: None + Returns: + None """ self.force_save = force @@ -3942,8 +4081,9 @@ def set_live_update(self, enable: bool): Window to watch for events that may trigger updates, such as mouse clicks, key presses, or selection changes in a combo box. - :param enable: If True, changes in a field element are immediately reflected in - other elements in the same Form. If False, live-update is disabled. + Args: + enable: If True, changes in a field element are immediately reflected in + other elements in the same Form. If False, live-update is disabled. """ bind_events = ["", "", "<>"] if enable and not self._liveupdate_binds: @@ -3965,18 +4105,19 @@ def save_records( check_prompt_save: bool = False, update_elements: bool = True, ) -> Union[SAVE_SUCCESS, SAVE_FAIL, SAVE_NONE]: - """ - Save records of all `DataSet` objects` associated with this `Form`. + """Save records of all `DataSet` objects` associated with this `Form`. + + Args: + table: Name of table to save, as well as any cascaded relationships. Used in + `DataSet.prompt_save()` + cascade_only: Save only tables with cascaded relationships. Default False. + check_prompt_save: Passed to `DataSet.save_record_recursive` to check if + individual `DataSet` has prompt_save enabled. Used when + `DataSet.save_records()` is called from `Form.prompt_save()`. + update_elements: (optional) Passed to `Form.save_record_recursive()` - :param table: Name of table to save, as well as any cascaded relationships. - Used in `DataSet.prompt_save()` - :param cascade_only: Save only tables with cascaded relationships. Default - False. - :param check_prompt_save: Passed to `DataSet.save_record_recursive` to check if - individual `DataSet` has prompt_save enabled. Used when - `DataSet.save_records()` is called from `Form.prompt_save()`. - :param update_elements: (optional) Passed to `Form.save_record_recursive()` - :returns: result - can be used with RETURN BITMASKS + Returns: + result - can be used with RETURN BITMASKS """ if check_prompt_save: logger.debug("Saving records in all datasets that allow prompt_save...") @@ -4053,18 +4194,20 @@ def update_elements( edit_protect_only: bool = False, omit_elements: List[str] = None, ) -> None: - """ - Updated the GUI elements to reflect values from the database for this `Form` + """Updated the GUI elements to reflect values from the database for this `Form` instance only. Not to be confused with the main `update_elements()`, which updates GUI elements for all `Form` instances. This method also executes `update_selectors()`, which updates selector elements. - :param target_data_key: (optional) dataset key to update elements for, otherwise - updates elements for all datasets - :param edit_protect_only: (optional) If true, only update items affected by - edit_protect - :param omit_elements: A list of elements to omit updating - :returns: None + Args: + target_data_key: (optional) dataset key to update elements for, otherwise + updates elements for all datasets + edit_protect_only: (optional) If true, only update items affected by + edit_protect + omit_elements: A list of elements to omit updating + + Returns: + None """ if omit_elements is None: omit_elements = [] @@ -4098,11 +4241,11 @@ def update_elements( self.callbacks["update_elements"](self, self.window) def update_actions(self, target_data_key: str = None) -> None: - """ - Update state for action-buttons + """Update state for action-buttons. - :param target_data_key: (optional) dataset key to update elements for, otherwise - updates elements for all datasets + Args: + target_data_key: (optional) dataset key to update elements for, otherwise + updates elements for all datasets """ win = self.window for data_key in self.datasets: @@ -4170,15 +4313,15 @@ def update_fields( columns: List[str] = None, combo_values_only: bool = False, ) -> None: - """ - Updated the field elements to reflect their `rows` DataFrame for this `Form` + """Updated the field elements to reflect their `rows` DataFrame for this `Form` instance only. - :param target_data_key: (optional) dataset key to update elements for, otherwise - updates elements for all datasets - :param omit_elements: A list of elements to omit updating - :param columns: A list of column names to update - :param combo_values_only: Updates the value list only for comboboxes. + Args: + target_data_key: (optional) dataset key to update elements for, otherwise + updates elements for all datasets + omit_elements: A list of elements to omit updating + columns: A list of column names to update + combo_values_only: Updates the value list only for comboboxes. """ if omit_elements is None: omit_elements = [] @@ -4350,13 +4493,15 @@ def update_selectors( omit_elements: List[str] = None, search_filter_only: bool = False, ) -> None: - """ - Updated the selector elements to reflect their `rows` DataFrame. + """Updated the selector elements to reflect their `rows` DataFrame. + + Args: + target_data_key: (optional) dataset key to update elements for, otherwise + updates elements for all datasets. + omit_elements: A list of elements to omit updating - :param target_data_key: (optional) dataset key to update elements for, otherwise - updates elements for all datasets. - :param omit_elements: A list of elements to omit updating - :returns: None + Returns: + None """ if omit_elements is None: omit_elements = [] @@ -4472,22 +4617,24 @@ def requery_all( update_elements: bool = True, requery_dependents: bool = True, ) -> None: - """ - Requeries all `DataSet` objects associated with this `Form`. This effectively + """Requeries all `DataSet` objects associated with this `Form`. This effectively re-loads the data from the database into `DataSet` objects. - :param select_first: passed to `DataSet.requery()` -> `DataSet.first()`. If - True, the first record will be selected after the requery - :param filtered: passed to `DataSet.requery()`. If True, the relationships will - be considered and an appropriate WHERE clause will be generated. False will - display all records from the table. - :param update_elements: passed to `DataSet.requery()` -> `DataSet.first()` to - `Form.update_elements()`. Note that the select_first parameter must = True - to use this parameter. - :param requery_dependents: passed to `DataSet.requery()` -> `DataSet.first()` to - `Form.requery_dependents()`. Note that the select_first parameter - must = True to use this parameter. - :returns: None + Args: + select_first: passed to `DataSet.requery()` -> `DataSet.first()`. If True, + the first record will be selected after the requery + filtered: passed to `DataSet.requery()`. If True, the relationships will be + considered and an appropriate WHERE clause will be generated. False will + display all records from the table. + update_elements: passed to `DataSet.requery()` -> `DataSet.first()` to + `Form.update_elements()`. Note that the select_first parameter must = + True to use this parameter. + requery_dependents: passed to `DataSet.requery()` -> `DataSet.first()` to + `Form.requery_dependents()`. Note that the select_first parameter must = + True to use this parameter. + + Returns: + None """ logger.info("Requerying all datasets") @@ -4510,17 +4657,19 @@ def requery_all( ) def process_events(self, event: str, values: list) -> bool: - """ - Process mapped events for this specific `Form` instance. + """Process mapped events for this specific `Form` instance. Not to be confused with the main `process_events()`, which processes events for ALL `Form` instances. This should be called once per iteration in your event loop. Note: Events handled are responsible for requerying and updating elements as needed. - :param event: The event returned by PySimpleGUI.read() - :param values: the values returned by PySimpleGUI.read() - :returns: True if an event was handled, False otherwise + Args: + event: The event returned by PySimpleGUI.read() + values: the values returned by PySimpleGUI.read() + + Returns: + True if an event was handled, False otherwise """ if self.window is None: logger.info( @@ -4572,13 +4721,15 @@ def process_events(self, event: str, values: list) -> bool: def update_element_states( self, table: str, disable: bool = None, visible: bool = None ) -> None: - """ - Disable/enable and/or show/hide all elements associated with a table. + """Disable/enable and/or show/hide all elements associated with a table. - :param table: table name associated with elements to disable/enable - :param disable: True/False to disable/enable element(s), None for no change - :param visible: True/False to make elements visible or not, None for no change - :returns: None + Args: + table: table name associated with elements to disable/enable + disable: True/False to disable/enable element(s), None for no change + visible: True/False to make elements visible or not, None for no change + + Returns: + None """ for mapped in self.element_map: if mapped.table != table: @@ -4597,11 +4748,13 @@ def update_element_states( @classmethod def purge_instance(cls, frm: Form) -> None: - """ - Remove self from Form.instances + """Remove self from Form.instances. + + Args: + frm: the `Form` to purge - :param frm: the `Form` to purge - :returns: None + Returns: + None """ cls.instances = [i for i in cls.instances if i != frm] @@ -4613,9 +4766,8 @@ def purge_instance(cls, frm: Form) -> None: # This is a dummy class for documenting utility functions class Utility: - """ - Utility functions are a collection of functions and classes that directly improve on - aspects of the pysimplesql module. + """Utility functions are a collection of functions and classes that directly improve + on aspects of the pysimplesql module. See the documentation for the following utility functions: `process_events()`, `update_elements()`, `bind()`, `simple_transform()`, `KeyGen()`, @@ -4626,17 +4778,19 @@ class Utility: def process_events(event: str, values: list) -> bool: - """ - Process mapped events for ALL Form instances. + """Process mapped events for ALL Form instances. Not to be confused with `Form.process_events()`, which processes events for individual `Form` instances. This should be called once per iteration in your event loop. Note: Events handled are responsible for requerying and updating elements as needed. - :param event: The event returned by PySimpleGUI.read() - :param values: the values returned by PySimpleGUI.read() - :returns: True if an event was handled, False otherwise + Args: + event: The event returned by PySimpleGUI.read() + values: the values returned by PySimpleGUI.read() + + Returns: + True if an event was handled, False otherwise """ handled = False for i in Form.instances: @@ -4646,38 +4800,40 @@ def process_events(event: str, values: list) -> bool: def update_elements(data_key: str = None, edit_protect_only: bool = False) -> None: - """ - Updated the GUI elements to reflect values from the database for ALL Form instances. - Not to be confused with `Form.update_elements()`, which updates GUI elements for - individual `Form` instances. - - :param data_key: (optional) key of `DataSet` to update elements for, otherwise - updates elements for all datasets. - :param edit_protect_only: (optional) If true, only update items affected by - edit_protect. - :returns: None + """Updated the GUI elements to reflect values from the database for ALL Form + instances. Not to be confused with `Form.update_elements()`, which updates GUI + elements for individual `Form` instances. + + Args: + data_key: (optional) key of `DataSet` to update elements for, otherwise updates + elements for all datasets. + edit_protect_only: (optional) If true, only update items affected by + edit_protect. + + Returns: + None """ for i in Form.instances: i.update_elements(data_key, edit_protect_only) def bind(win: sg.Window) -> None: - """ - Bind ALL forms to window. Not to be confused with `Form.bind()`, which binds + """Bind ALL forms to window. Not to be confused with `Form.bind()`, which binds specific forms to the window. - :param win: The PySimpleGUI window to bind all forms to - :returns: None + Args: + win: The PySimpleGUI window to bind all forms to + + Returns: + None """ for i in Form.instances: i.bind(win) def simple_transform(dataset: DataSet, row, encode): - """ - Convenience transform function that makes it easier to add transforms to your - records. - """ + """Convenience transform function that makes it easier to add transforms to your + records.""" for col, function in dataset._simple_transform.items(): if col in row: msg = f"Transforming {col} from {row[col]}" @@ -4694,19 +4850,20 @@ def update_table_element( values: List[_TableRow], select_rows: List[int], ) -> None: - """ - Updates a PySimpleGUI sg.Table with new data and suppresses extra events emitted. + """Updates a PySimpleGUI sg.Table with new data and suppresses extra events emitted. Call this function instead of simply calling update() on a sg.Table element. - The reason is that updating the selection or values will in turn fire more - changed events, adding up to an endless loop of events. + Without unbinding the virtual "<>" event, updating the selection or + values will in turn fire more changed events, creating an endless loop of events. - :param window: A PySimpleGUI Window containing the sg.Table element to be updated. - :param element: The sg.Table element to be updated. - :param values: A list of table rows to update the sg.Table with. - :param select_rows: List of rows to select as if user did. + Args: + window: A PySimpleGUI Window containing the sg.Table element to be updated. + element: The sg.Table element to be updated. + values: A list of table rows to update the sg.Table with. + select_rows: List of rows to select as if user did. - :returns: None + Returns: + None """ # Disable handling for "<>" event element.widget.unbind("<>") @@ -4724,11 +4881,13 @@ def update_table_element( def checkbox_to_bool(value): - """ - Allows a variety of checkbox values to still return True or False. + """Allows a variety of checkbox values to still return True or False. - :param value: Value to convert into True or False - :returns: bool + Args: + value: Value to convert into True or False + + Returns: + bool """ return str(value).lower() in [ "y", @@ -4743,14 +4902,13 @@ def checkbox_to_bool(value): def shake_widget(widget: Union[sg.Element, tk.Widget], pixels=4, delay_ms=50, repeat=2): - """ - Shakes the given widget by modifying its padx attribute. - - :param widget: The widget to shake. Must be an instance of sg.Element or tk.Widget. - :param pixels: The number of pixels by which to shake the widget horizontally. - :param delay_ms: The delay in milliseconds between each shake movement. - :param repeat: The number of times to repeat the shaking movement. + """Shakes the given widget by modifying its padx attribute. + Args: + widget: The widget to shake. Must be an instance of sg.Element or tk.Widget. + pixels: The number of pixels by which to shake the widget horizontally. + delay_ms: The delay in milliseconds between each shake movement. + repeat: The number of times to repeat the shaking movement. """ if isinstance(widget, sg.Element): widget = widget.Widget @@ -4783,17 +4941,13 @@ def shake_widget(widget: Union[sg.Element, tk.Widget], pixels=4, delay_ms=50, re class Popup: - """ - Popup helper class. + """Popup helper class. Has popup functions for internal use. Stores last info popup as last_info """ def __init__(self, window: sg.Window = None): - """ - Create a new Popup instance - :returns: None. - """ + """Create a new Popup instance :returns: None.""" self.window = window self.popup_info = None self.last_info_msg: str = "" @@ -4810,8 +4964,7 @@ def __init__(self, window: sg.Window = None): } def ok(self, title, msg): - """ - Internal use only. + """Internal use only. Creates sg.Window with LanguagePack OK button """ @@ -4834,8 +4987,7 @@ def ok(self, title, msg): popup_win.close() def yes_no(self, title, msg): - """ - Internal use only. + """Internal use only. Creates sg.Window with LanguagePack Yes/No button """ @@ -4870,17 +5022,17 @@ def yes_no(self, title, msg): def info( self, msg: str, display_message: bool = True, auto_close_seconds: int = None ): - """ - Displays a popup message and saves the message to self.last_info, auto-closing - after x seconds. The title of the popup window is defined in + """Displays a popup message and saves the message to self.last_info, auto- + closing after x seconds. The title of the popup window is defined in lang.info_popup_title. - :param msg: The message to display. - :param display_message: (optional) If True (default), displays the message in - the popup window. If False, only saves `msg` to `self.last_info_msg`. - :param auto_close_seconds: (optional) The number of seconds before the popup - window auto-closes. If not provided, it is obtained from - themepack.popup_info_auto_close_seconds. + Args: + msg: The message to display. + display_message: (optional) If True (default), displays the message in the + popup window. If False, only saves `msg` to `self.last_info_msg`. + auto_close_seconds: (optional) The number of seconds before the popup window + auto-closes. If not provided, it is obtained from + themepack.popup_info_auto_close_seconds. """ title = lang.info_popup_title @@ -4904,9 +5056,7 @@ def info( ) def _auto_close(self): - """ - Use in a tk.after to automatically close the popup_info. - """ + """Use in a tk.after to automatically close the popup_info.""" if self.popup_info: self.popup_info.close() self.popup_info = None @@ -4918,17 +5068,17 @@ def update_info_element( timeout=False, erase: bool = False, ) -> None: - """ - Update any mapped info elements: - - :param message: Text message to update info elements with - :param auto_erase_seconds: The number of seconds before automatically - erasing the information element. If None, the default value from themepack - will be used. - :param timeout: A boolean flag indicating whether to erase the information - element. If True, and the elapsed time since the information element was - last updated exceeds the auto_erase_seconds, the element will be cleared. - :param erase: Default False. Erase info elements + """Update any mapped info elements: + + Args: + message: Text message to update info elements with + auto_erase_seconds: The number of seconds before automatically erasing the + information element. If None, the default value from themepack will be + used. + timeout: A boolean flag indicating whether to erase the information element. + If True, and the elapsed time since the information element was last + updated exceeds the auto_erase_seconds, the element will be cleared. + erase: Default False. Erase info elements """ if auto_erase_seconds is None: auto_erase_seconds = themepack.info_element_auto_erase_seconds @@ -4962,16 +5112,18 @@ def update_info_element( class ProgressBar: def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100): - """ - Creates a progress bar window with a message label and a progress bar. + """Creates a progress bar window with a message label and a progress bar. The progress bar is updated by calling the `update` method to update the progress in incremental steps until the `close` method is called. - :param title: Title of the window - :param max_value: Maximum value of the progress bar - :param hide_delay: Delay in milliseconds before displaying the Window - :returns: None + Args: + title: Title of the window + max_value: Maximum value of the progress bar + hide_delay: Delay in milliseconds before displaying the Window + + Returns: + None """ self.win = None self.title = title @@ -4998,11 +5150,14 @@ def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100): self.phrase_index = 0 def update(self, message: str, current_count: int): - """ - Updates the progress bar with the current progress message and value. - :param message: Message to display - :param current_count: Current value of the progress bar - :returns: None + """Updates the progress bar with the current progress message and value. + + Args: + message: Message to display + current_count: Current value of the progress bar + + Returns: + None """ if time() * 1000 - self.start_time < self.hide_delay: return @@ -5014,10 +5169,10 @@ def update(self, message: str, current_count: int): self.win["bar"].update(current_count=current_count) def close(self): - """ - Closes the progress bar window. + """Closes the progress bar window. - :returns: None + Returns: + None """ if self.win is not None: self.win.close() @@ -5035,8 +5190,7 @@ def _create_window(self): class ProgressAnimate: def __init__(self, title: str, config: dict = None): - """ - Creates an animated progress bar with a message label. + """Creates an animated progress bar with a message label. The progress bar will animate indefinitely, until the process passed in to the `run` method finishes. @@ -5062,9 +5216,12 @@ def __init__(self, title: str, config: dict = None): } Defaults are used for any keys that are not specified in the dictionary. - :param title: Title of the window - :param config: Dictionary of configuration options as listed above - :returns: None + Args: + title: Title of the window + config: Dictionary of configuration options as listed above + + Returns: + None """ default_config = { # oscillators for the bar divider and colors @@ -5126,10 +5283,8 @@ def __init__(self, title: str, config: dict = None): self.completed = asyncio.Event() def run(self, fn: callable, *args, **kwargs): - """ - Runs the function in a separate co-routine, while animating the progress bar in - another. - """ + """Runs the function in a separate co-routine, while animating the progress bar + in another.""" if not callable(fn): raise ValueError("fn must be a callable") @@ -5228,8 +5383,7 @@ def _animated_message(self, phrases: list, phrase_delay: float): class KeyGen: - """ - The keygen system provides a mechanism to generate unique keys for use as + """The keygen system provides a mechanism to generate unique keys for use as PySimpleGUI element keys. This is needed because many auto-generated items will have the same name. If for @@ -5240,28 +5394,32 @@ class KeyGen: """ def __init__(self, separator="!"): - """ - Create a new KeyGen instance. + """Create a new KeyGen instance. + + Args: + separator: The default separator that goes between the key and the + incremental number - :param separator: The default separator that goes between the key and the - incremental number - :returns: None + Returns: + None """ self._keygen = {} self._separator = separator def get(self, key: str, separator: str = None) -> str: - """ - Get a generated key from the `KeyGen`. + """Get a generated key from the `KeyGen`. + + Args: + key: The key from which to generate the new key. If the key has not been + used before, then it will be returned unmodified. For each successive + call with the same key, it will be appended with the separator character + and an incremental number. For example, if the key 'button' was passed + to `KeyGen.get()` 3 times in a row, then the keys 'button', 'button:1', + and 'button:2' would be returned respectively. + separator: (optional) override the default separator wth this separator - :param key: The key from which to generate the new key. If the key has not been - used before, then it will be returned unmodified. For each successive call - with the same key, it will be appended with the separator character and an - incremental number. For example, if the key 'button' was passed to - `KeyGen.get()` 3 times in a row, then the keys 'button', 'button:1', and - 'button:2' would be returned respectively. - :param separator: (optional) override the default separator wth this separator - :returns: None + Returns: + None """ if separator is None: separator = self._separator @@ -5278,29 +5436,31 @@ def get(self, key: str, separator: str = None) -> str: return return_key def reset_key(self, key: str) -> None: - """ - Reset the generation sequence for the supplied key. + """Reset the generation sequence for the supplied key. - :param key: The base key to reset te sequence for + Args: + key: The base key to reset te sequence for """ with contextlib.suppress(KeyError): del self._keygen[key] def reset(self) -> None: - """ - Reset the entire `KeyGen` and remove all keys. + """Reset the entire `KeyGen` and remove all keys. - :returns: None + Returns: + None """ self._keygen = {} def reset_from_form(self, frm: Form) -> None: - """ - Reset keys from the keygen that were from mapped PySimpleGUI elements of that + """Reset keys from the keygen that were from mapped PySimpleGUI elements of that `Form`. - :param frm: The `Form` from which to get the list of mapped elements - :returns: None + Args: + frm: The `Form` from which to get the list of mapped elements + + Returns: + None """ # reset keys related to form for mapped in frm.element_map: @@ -5309,8 +5469,7 @@ def reset_from_form(self, frm: Form) -> None: # create a global KeyGen instance keygen = KeyGen(separator=":") -""" -This is a global keygen instance for general purpose use. +"""This is a global keygen instance for general purpose use. See `KeyGen` for more info """ @@ -5318,8 +5477,7 @@ def reset_from_form(self, frm: Form) -> None: class LazyTable(sg.Table): - """ - The LazyTable is a subclass of sg.Table for improved performance by loading rows + """The LazyTable is a subclass of sg.Table for improved performance by loading rows lazily during scroll events. Updating a sg.Table is generally fast, but with large DataSets that contain thousands of rows, there may be some noticeable lag. LazyTable overcomes this by only inserting a slice of rows during an `update()`. @@ -5364,17 +5522,16 @@ def __setattr__(self, name, value): @property def insert_qty(self): - """Number of rows to insert during an `update(values=)` and scroll events""" + """Number of rows to insert during an `update(values=)` and scroll events.""" if self.lazy_loading: return max(self.NumRows, self.lazy_insert_qty) return len(self.Values) @property def SelectedRows(self): # noqa N802 - """ - Returns the selected row(s) in the LazyTable. + """Returns the selected row(s) in the LazyTable. - :returns: + Returns: - If the LazyTable has data: - Retrieves the index of the selected row by matching the primary key (pk) value with the first selected item in the widget. @@ -5616,14 +5773,12 @@ def add_validate(self, dataset: DataSet, column_name: str): class _TtkStrictInput(ttk.Entry, _StrictInput): - """Internal Ttk Entry with validate commands""" + """Internal Ttk Entry with validate commands.""" class _PlaceholderText(ABC): - """ - An abstract class for PySimpleGUI text-entry elements that allows for the display of - a placeholder text when the input is empty. - """ + """An abstract class for PySimpleGUI text-entry elements that allows for the display + of a placeholder text when the input is empty.""" # fmt: off _non_keys: ClassVar[List[str]] = {"Control_L","Control_R","Alt_L","Alt_R","Shift_L", @@ -5641,8 +5796,7 @@ class _PlaceholderText(ABC): active_placeholder: bool = False def add_placeholder(self, placeholder: str, color: str = None, font: str = None): - """ - Adds a placeholder text to the element. + """Adds a placeholder text to the element. The placeholder text is displayed in the element when the element is empty and unfocused. When the element is clicked or focused, the placeholder text @@ -5652,9 +5806,10 @@ def add_placeholder(self, placeholder: str, color: str = None, font: str = None) This function is based on the recipe by Miguel Martinez Lopez, licensed under MIT. It has been updated to work with PySimpleGUI elements. - :param placeholder: The text to display as placeholder when the input is empty. - :param color: The color of the placeholder text (default None). - :param font: The font of the placeholder text (default None). + Args: + placeholder: The text to display as placeholder when the input is empty. + color: The color of the placeholder text (default None). + font: The font of the placeholder text (default None). """ normal_color = self.widget.cget("fg") normal_font = self.widget.cget("font") @@ -5676,12 +5831,12 @@ def _add_binds(self): pass def update(self, *args, **kwargs): - """ - Updates the input widget with a new value and displays the placeholder text if - the value is empty. + """Updates the input widget with a new value and displays the placeholder text + if the value is empty. - :param args: Optional arguments to pass to `sg.Element.update`. - :param kwargs: Optional keyword arguments to pass to `sg.Element.update`. + Args: + *args: Optional arguments to pass to `sg.Element.update`. + **kwargs: Optional keyword arguments to pass to `sg.Element.update`. """ if not self.placeholder_feature_enabled: super().update(*args, **kwargs) @@ -5713,11 +5868,11 @@ def update(self, *args, **kwargs): super().update(*args, **kwargs) def get(self) -> str: - """ - Returns the current value of the input, or an empty string if the input displays - the placeholder text. + """Returns the current value of the input, or an empty string if the input + displays the placeholder text. - :return: The current value of the input. + Returns: + The current value of the input. """ if self.active_placeholder: return "" @@ -5733,9 +5888,7 @@ def delete_placeholder(self): class _EnhancedInput(_PlaceholderText, sg.Input, _StrictInput): - """ - An Input that allows for the display of a placeholder text when empty. - """ + """An Input that allows for the display of a placeholder text when empty.""" def __init__(self, *args, **kwargs): self.binds = {} @@ -5802,9 +5955,8 @@ def delete_placeholder(self): class _EnhancedMultiline(_PlaceholderText, sg.Multiline): - """ - A Multiline that allows for the display of a placeholder text when focus-out empty. - """ + """A Multiline that allows for the display of a placeholder text when focus-out + empty.""" def __init__(self, *args, **kwargs): self.binds = {} @@ -6180,8 +6332,7 @@ def on_entry_key_release(self, event=None): # This is a dummy class for documenting convenience functions class Convenience: - """ - Convenience functions are a collection of functions and classes that aide in + """Convenience functions are a collection of functions and classes that aide in building PySimpleGUI layouts that conform to pysimplesql standards so that your database application is up and running quickly, and with all the great automatic functionality pysimplesql has to offer. See the documentation for the following @@ -6212,8 +6363,7 @@ def field( pad=None, **kwargs, ) -> sg.Column: - """ - Convenience function for adding PySimpleGUI elements to the Window, so they are + """Convenience function for adding PySimpleGUI elements to the Window, so they are properly configured for pysimplesql. The automatic functionality of pysimplesql relies on accompanying metadata so that the `Form.auto_add_elements()` can pick them up. This convenience function will create a text label, along with an element with @@ -6221,25 +6371,28 @@ def field( record name if none is supplied. See `set_label_size()`, `set_element_size()` and `set_mline_size()` for setting default sizes of these elements. - :param field: The database record in the form of table.column I.e. 'Journal.entry' - :param element: (optional) The element type desired (defaults to PySimpleGUI.Input) - :param size: Overrides the default element size that was set with - `set_element_size()` for this element only. - :param label: The text/label will automatically be generated from the column name. - If a different text/label is desired, it can be specified here. - :param no_label: Do not automatically generate a label for this element - :param label_above: Place the label above the element instead of to the left. - :param quick_editor: For records that reference another table, place a quick edit - button next to the element - :param quick_editor_kwargs: Additional keyword arguments to pass to quick editor. - :param filter: Can be used to reference different `Form`s in the same layout. Use a - matching filter when creating the `Form` with the filter parameter. - :param key: (optional) The key to give this element. See note above about the - default auto generated key. - :param kwargs: Any additional arguments will be passed to the PySimpleGUI element. - :returns: Element(s) to be used in the creation of PySimpleGUI layouts. Note that - this function actually creates multiple Elements wrapped in a PySimpleGUI - Column, but can be treated as a single Element. + Args: + field: The database record in the form of table.column I.e. 'Journal.entry' + element: (optional) The element type desired (defaults to PySimpleGUI.Input) + size: Overrides the default element size that was set with `set_element_size()` + for this element only. + label: The text/label will automatically be generated from the column name. If a + different text/label is desired, it can be specified here. + no_label: Do not automatically generate a label for this element + label_above: Place the label above the element instead of to the left. + quick_editor: For records that reference another table, place a quick edit + button next to the element + quick_editor_kwargs: Additional keyword arguments to pass to quick editor. + filter: Can be used to reference different `Form`s in the same layout. Use a + matching filter when creating the `Form` with the filter parameter. + key: (optional) The key to give this element. See note above about the default + auto generated key. + **kwargs: Any additional arguments will be passed to the PySimpleGUI element. + + Returns: + Element(s) to be used in the creation of PySimpleGUI layouts. Note that this + function actually creates multiple Elements wrapped in a PySimpleGUI Column, but + can be treated as a single Element. """ # TODO: See what the metadata does after initial setup is complete - needed anymore? element = _EnhancedInput if element == sg.Input else element @@ -6386,8 +6539,7 @@ def actions( pad=None, **kwargs, ) -> sg.Column: - """ - Allows for easily adding record navigation and record action elements to the + """Allows for easily adding record navigation and record action elements to the PySimpleGUI window The navigation elements are generated automatically (first, previous, next, last and search). The action elements can be customized by selecting which ones you want generated from the parameters available. This allows @@ -6403,35 +6555,38 @@ def actions( note that these autogenerated keys also pass through the `KeyGen`, so it's possible that these keys could be table_last:action!1, table_last:action!2, etc. - :param table: The table name that this "element" will provide actions for - :param key: (optional) The base key to give the generated elements - :param default: Default edit_protect, navigation, insert, delete, save and search to - either true or false (defaults to True) The individual keyword arguments will - trump the default parameter. This allows for starting with all actions - defaulting to False, then individual ones can be enabled with True - or the - opposite by defaulting them all to True, and disabling the ones not needed with - False. - :param edit_protect: An edit protection mode to prevent accidental changes in the - database. It is a button that toggles the ability on and off to prevent - accidental changes in the database by enabling/disabling the insert, edit, - duplicate, delete and save buttons. - :param navigation: The standard << < > >> (First, previous, next, last) buttons for - navigation - :param insert: Button to insert new records - :param delete: Button to delete current record - :param duplicate: Button to duplicate current record - :param save: Button to save record. Note that the save button feature saves changes - made to any table, therefore only one save button is needed per window. - :param search: A search Input element. Size can be specified with the `search_size` - parameter - :param search_size: The size of the search input element - :param bind_return_key: Bind the return key to the search button. Defaults to true. - :param filter: Can be used to reference different `Form`s in the same layout. Use a - matching filter when creating the `Form` with the filter parameter. - :param pad: The padding to use for the generated elements. - :returns: An element to be used in the creation of PySimpleGUI layouts. Note that - this is technically multiple elements wrapped in a PySimpleGUI.Column, but acts - as one element for the purpose of layout building. + Args: + table: The table name that this "element" will provide actions for + key: (optional) The base key to give the generated elements + default: Default edit_protect, navigation, insert, delete, save and search to + either true or false (defaults to True) The individual keyword arguments + will trump the default parameter. This allows for starting with all actions + defaulting to False, then individual ones can be enabled with True - or the + opposite by defaulting them all to True, and disabling the ones not needed + with False. + edit_protect: An edit protection mode to prevent accidental changes in the + database. It is a button that toggles the ability on and off to prevent + accidental changes in the database by enabling/disabling the insert, edit, + duplicate, delete and save buttons. + navigation: The standard << < > >> (First, previous, next, last) buttons for + navigation + insert: Button to insert new records + delete: Button to delete current record + duplicate: Button to duplicate current record + save: Button to save record. Note that the save button feature saves changes + made to any table, therefore only one save button is needed per window. + search: A search Input element. Size can be specified with the `search_size` + parameter + search_size: The size of the search input element + bind_return_key: Bind the return key to the search button. Defaults to true. + filter: Can be used to reference different `Form`s in the same layout. Use a + matching filter when creating the `Form` with the filter parameter. + pad: The padding to use for the generated elements. + + Returns: + An element to be used in the creation of PySimpleGUI layouts. Note that this is + technically multiple elements wrapped in a PySimpleGUI.Column, but acts as one + element for the purpose of layout building. """ if use_ttk_buttons is None: @@ -6812,25 +6967,26 @@ def selector( key: str = None, **kwargs, ) -> sg.Element: - """ - Selectors in pysimplesql are special elements that allow the user to change records - in the database application. For example, Listboxes, Comboboxes and Tables all - provide a convenient way to users to choose which record they want to select. This - convenience function makes creating selectors very quick and as easy as using a + """Selectors in pysimplesql are special elements that allow the user to change + records in the database application. For example, Listboxes, Comboboxes and Tables + all provide a convenient way to users to choose which record they want to select. + This convenience function makes creating selectors very quick and as easy as using a normal PySimpleGUI element. - :param table: The table name that this selector will act on. - :param element: The type of element you would like to use as a selector (defaults to - a Listbox) - :param size: The desired size of this selector element - :param filter: Can be used to reference different `Form`s in the same layout. Use a - matching filter when creating the `Form` with the filter parameter. - :param key: (optional) The key to give to this selector. If no key is provided, it - will default to table:selector using the name specified in the table parameter. - This is also passed through the keygen, so if selectors all use the default - name, they will be made unique. ie: Journal:selector!1, Journal:selector!2, etc. - :param kwargs: Any additional arguments supplied will be passed on to the - PySimpleGUI element. Note: TableBuilder objects bring their own kwargs. + Args: + table: The table name that this selector will act on. + element: The type of element you would like to use as a selector (defaults to a + Listbox) + size: The desired size of this selector element + filter: Can be used to reference different `Form`s in the same layout. Use a + matching filter when creating the `Form` with the filter parameter. + key: (optional) The key to give to this selector. If no key is provided, it will + default to table:selector using the name specified in the table parameter. + This is also passed through the keygen, so if selectors all use the default + name, they will be made unique. ie: Journal:selector!1, Journal:selector!2, + etc. + **kwargs: Any additional arguments supplied will be passed on to the PySimpleGUI + element. Note: TableBuilder objects bring their own kwargs. """ element = _AutocompleteCombo if element == sg.Combo else element @@ -6964,23 +7120,25 @@ def get_table_kwargs(self): @dc.dataclass class TableBuilder(list): - """ - This is a convenience class used to build table headings for PySimpleGUI. + """This is a convenience class used to build table headings for PySimpleGUI. In addition, `TableBuilder` objects can sort columns in ascending or descending order by clicking on the column in the heading in the PySimpleGUI Table element if the sort_enable parameter is set to True. - :param sort_enable: True to enable sorting by heading column. - :param allow_cell_edits: Double-click to edit a cell value if True. Accepted - edits update both `sg.Table` and associated `field` element. Note: primary - key, generated, or `readonly` columns don't allow cell edits. - :param lazy_loading: For larger DataSets (see `LazyTable`). - :param add_save_heading_button: Adds a save button to the left-most heading - column if True. - :param apply_search_filter: Filter rows to only those columns in - `DataSet.search_order` that contain `Dataself.search_string`. - :returns: None + Args: + sort_enable: True to enable sorting by heading column. + allow_cell_edits: Double-click to edit a cell value if True. Accepted edits + update both `sg.Table` and associated `field` element. Note: primary key, + generated, or `readonly` columns don't allow cell edits. + lazy_loading: For larger DataSets (see `LazyTable`). + add_save_heading_button: Adds a save button to the left-most heading column if + True. + apply_search_filter: Filter rows to only those columns in `DataSet.search_order` + that contain `Dataself.search_string`. + + Returns: + None """ # store our instances @@ -7019,24 +7177,27 @@ def add_column( readonly: bool = False, visible: bool = True, ) -> None: - """ - Add a new heading column to this TableBuilder object. Columns are added in the - order that this method is called. Note that the primary key column does not need - to be included, as primary keys are stored internally in the `_TableRow` class. - - :param column: The name of the column in the database - :param heading: The name of this columns heading (title) - :param width: The width for this column to display within the Table element - :param col_justify: Default 'left'. Available options: 'left', 'right', - 'center', 'default'. - :param heading_justify: Defaults to 'column' inherity `col_justify`. Available - options: 'left', 'right', 'center', 'column', 'default'. - :param readonly: Indicates if the column is read-only when - `TableBuilder.allow_cell_edits` is True. - :param visible: True if the column is visible. Typically, the only hidden - column would be the primary key column if any. This is also useful if the - `DataSet.rows` DataFrame has information that you don't want to display. - :returns: None + """Add a new heading column to this TableBuilder object. Columns are added in + the order that this method is called. Note that the primary key column does not + need to be included, as primary keys are stored internally in the `_TableRow` + class. + + Args: + column: The name of the column in the database + heading: The name of this columns heading (title) + width: The width for this column to display within the Table element + col_justify: Default 'left'. Available options: 'left', 'right', 'center', + 'default'. + heading_justify: Defaults to 'column' inherity `col_justify`. Available + options: 'left', 'right', 'center', 'column', 'default'. + readonly: Indicates if the column is read-only when + `TableBuilder.allow_cell_edits` is True. + visible: True if the column is visible. Typically, the only hidden column + would be the primary key column if any. This is also useful if the + `DataSet.rows` DataFrame has information that you don't want to display. + + Returns: + None """ self.append({"heading": heading, "column": column}) self._width_map.append(width) @@ -7083,11 +7244,11 @@ def element(self) -> Type[LazyTable]: @property def heading_names(self) -> List[str]: - """ - Return a list of heading_names for use with the headings parameter of + """Return a list of heading_names for use with the headings parameter of PySimpleGUI.Table. - :returns: a list of heading names + Returns: + a list of heading names """ headings = [c["heading"] for c in self] if self.add_save_heading_button: @@ -7098,19 +7259,19 @@ def heading_names(self) -> List[str]: @property def columns(self): - """ - Return a list of column names. + """Return a list of column names. - :returns: a list of column names + Returns: + a list of column names """ return [c["column"] for c in self if c["column"] is not None] @property def col_justify_map(self) -> List[str]: - """ - Convenience method for creating PySimpleGUI tables. + """Convenience method for creating PySimpleGUI tables. - :returns: a list column justifications for use with PySimpleGUI Table + Returns: + a list column justifications for use with PySimpleGUI Table cols_justification parameter """ justify = [justify[0].lower() for justify in self._col_justify_map] @@ -7119,10 +7280,10 @@ def col_justify_map(self) -> List[str]: @property def heading_justify_map(self) -> List[str]: - """ - Convenience method for creating PySimpleGUI tables. + """Convenience method for creating PySimpleGUI tables. - :returns: a list heading justifications for use with LazyTable + Returns: + a list heading justifications for use with LazyTable `headings_justification` """ justify = [justify[0].lower() for justify in self._heading_justify_map] @@ -7131,10 +7292,10 @@ def heading_justify_map(self) -> List[str]: @property def heading_anchor_map(self) -> List[str]: - """ - Internal method for passing directly to treeview heading() function. + """Internal method for passing directly to treeview heading() function. - :returns: a list heading anchors for use with treeview heading() function. + Returns: + a list heading anchors for use with treeview heading() function. """ justify = [ TK_ANCHOR_MAP[justify[0].lower()] for justify in self._heading_justify_map @@ -7144,35 +7305,36 @@ def heading_anchor_map(self) -> List[str]: @property def visible_map(self) -> List[Union[bool, int]]: - """ - Convenience method for creating PySimpleGUI tables. + """Convenience method for creating PySimpleGUI tables. - :returns: a list of visible columns for use with th PySimpleGUI Table + Returns: + a list of visible columns for use with th PySimpleGUI Table visible_column_map parameter """ return list(self._visible_map) @property def width_map(self) -> List[int]: - """ - Convenience method for creating PySimpleGUI tables. + """Convenience method for creating PySimpleGUI tables. - :returns: a list column widths for use with th PySimpleGUI Table col_widths - parameter + Returns: + a list column widths for use with th PySimpleGUI Table col_widths parameter """ return list(self._width_map) def _update_headings( self, element: sg.Table, sort_column=None, sort_order: int = None ) -> None: - """ - Perform the actual update to the PySimpleGUI Table heading. + """Perform the actual update to the PySimpleGUI Table heading. Note: Not typically called by the end user. - :param element: The PySimpleGUI Table element - :param sort_column: The column to show the sort direction indicators on - :param sort_order: A SORT_* constant (SORT_NONE, SORT_ASC, SORT_DESC) - :returns: None + Args: + element: The PySimpleGUI Table element + sort_column: The column to show the sort direction indicators on + sort_order: A SORT_* constant (SORT_NONE, SORT_ASC, SORT_DESC) + + Returns: + None """ # Load in our marker characters. We will use them to both display the @@ -7205,15 +7367,17 @@ def _update_headings( ) def enable_heading_function(self, element: sg.Table, fn: callable) -> None: - """ - Enable the sorting callbacks for each column index, or saving by click the + """Enable the sorting callbacks for each column index, or saving by click the unsaved changes column Note: Not typically used by the end user. Called from `Form.auto_map_elements()` - :param element: The PySimpleGUI Table element associated with this TableBuilder - :param fn: A callback functions to run when a heading is clicked. The callback - should take one column parameter. - :returns: None + Args: + element: The PySimpleGUI Table element associated with this TableBuilder + fn: A callback functions to run when a heading is clicked. The callback + should take one column parameter. + + Returns: + None """ if self.sort_enable: for i in range(len(self)): @@ -7234,12 +7398,14 @@ class _HeadingCallback: """Internal class used when sg.Table column headings are clicked.""" def __init__(self, frm_reference: Form, data_key: str): - """ - Create a new _HeadingCallback object. + """Create a new _HeadingCallback object. + + Args: + frm_reference: `Form` object + data_key: `DataSet` key - :param frm_reference: `Form` object - :param data_key: `DataSet` key - :returns: None + Returns: + None """ self.frm: Form = frm_reference self.data_key = data_key @@ -7257,7 +7423,7 @@ def __call__(self, column, save): class _CellEdit: - """Internal class used when sg.Table cells are double-clicked if edit enabled""" + """Internal class used when sg.Table cells are double-clicked if edit enabled.""" def __init__(self, frm_reference: Form): self.frm = frm_reference @@ -7606,7 +7772,7 @@ def get_datakey_and_sgtable(self, treeview, frm): return None def combo_configure(self, event): - """Configures combobox drop-down to be at least as wide as longest value""" + """Configures combobox drop-down to be at least as wide as longest value.""" combo = event.widget style = ttk.Style() @@ -7625,7 +7791,7 @@ def combo_configure(self, event): class _LiveUpdate: - """Internal class used to automatically sync selectors with field changes""" + """Internal class used to automatically sync selectors with field changes.""" def __init__(self, frm_reference: Form): self.frm = frm_reference @@ -7712,14 +7878,13 @@ def delay(self, widget, widget_type): # Change the look and feel of your database application all in one place. class ThemePack: - """ - ThemePacks are user-definable objects that allow for the look and feel of database - applications built with PySimpleGUI + pysimplesql. This includes everything from - icons, the ttk themes, to sounds. Pysimplesql comes with 3 pre-made ThemePacks: - default (aka ss_small), ss_large and ss_text. Creating your own is easy as well! In - fact, a ThemePack can be as simple as one line if you just want to change one aspect - of the default ThemePack. Example: - my_tp = {'search': 'Click here to search'} # I want a different search button. + """ThemePacks are user-definable objects that allow for the look and feel of + database applications built with PySimpleGUI + pysimplesql. This includes + everything from icons, the ttk themes, to sounds. Pysimplesql comes with 3 pre-made + ThemePacks: default (aka ss_small), ss_large and ss_text. Creating your own is easy + as well! In fact, a ThemePack can be as simple as one line if you just want to + change one aspect of the default ThemePack. Example: my_tp = {'search': 'Click here + to search'} # I want a different search button. Once a ThemePack is created, it's very easy to use. Here is a very simple example of using a ThemePack: @@ -7802,9 +7967,7 @@ class ThemePack: # invalid input animation "validate_exception_animation": lambda widget: shake_widget(widget), } - """ - Default Themepack. - """ + """Default Themepack.""" def __init__(self, tp_dict: Dict[str, str] = None) -> None: self.tp_dict = tp_dict or ThemePack.default @@ -7823,8 +7986,7 @@ def __getattr__(self, key): ) from e def __call__(self, tp_dict: Dict[str, str] = None) -> None: - """ - Update the ThemePack object from tp_dict. + """Update the ThemePack object from tp_dict. Example minimal ThemePack: NOTE: You can add additional keys if desired tp_example = { @@ -7849,12 +8011,15 @@ def __call__(self, tp_dict: Dict[str, str] = None) -> None: For Base64, you can convert a whole folder using https://github.com/PySimpleGUI/PySimpleGUI-Base64-Encoder # fmt: skip Remember to us b'' around the string. - :param tp_dict: (optional) A dict formatted as above to create the ThemePack - from. If one is not supplied, a default ThemePack will be generated. Any - keys not present in the supplied tp_dict will be generated from the default - values. Additionally, tp_dict may contain additional keys not specified in - the minimal default ThemePack. - :returns: None + Args: + tp_dict: (optional) A dict formatted as above to create the ThemePack from. + If one is not supplied, a default ThemePack will be generated. Any keys + not present in the supplied tp_dict will be generated from the default + values. Additionally, tp_dict may contain additional keys not specified + in the minimal default ThemePack. + + Returns: + None """ # noqa: E501 # For default use cases, load the default directly to avoid the overhead # of __getattr__() going through 2 key reads @@ -7871,14 +8036,13 @@ def __call__(self, tp_dict: Dict[str, str] = None) -> None: # Change the language text used throughout the program. class LanguagePack: - """ - LanguagePacks are user-definable collections of strings that allow for localization - of strings and messages presented to the end user. + """LanguagePacks are user-definable collections of strings that allow for + localization of strings and messages presented to the end user. Creating your own is easy as well! In fact, a LanguagePack can be as simple as one - line if you just want to change one aspect of the default LanguagePack. Example: - # I want the save popup to display this text in English in all caps - lp_en = {'save_success': 'SAVED!'} + line if you just want to change one aspect of the default LanguagePack. Example: # I + want the save popup to display this text in English in all caps lp_en = + {'save_success': 'SAVED!'} """ default: ClassVar[Dict[Any]] = { @@ -8021,9 +8185,7 @@ class LanguagePack: ValidateRule.MAX_LENGTH: "{value} more than max length, {rule}", ValidateRule.CUSTOM: "{value}{rule}", } - """ - Default LanguagePack. - """ + """Default LanguagePack.""" def __init__(self, lp_dict=None): self.lp_dict = lp_dict or type(self).default @@ -8065,9 +8227,8 @@ def __call__(self, lp_dict=None): class LangFormat(dict): - """ - This is a convenience class used by LanguagePack format_map calls, allowing users to - not include expected variables. + """This is a convenience class used by LanguagePack format_map calls, allowing users + to not include expected variables. Note: This is typically not used by the end user. """ @@ -8086,8 +8247,7 @@ def __missing__(self, key): # This is a dummy class for documenting convenience functions class Abstractions: - """ - Supporting multiple databases in your application can quickly become very + """Supporting multiple databases in your application can quickly become very complicated and unmanageable. pysimplesql abstracts all of this complexity and presents a unified API via abstracting the main concepts of database programming. See the following documentation for a better understanding of how this is @@ -8110,10 +8270,9 @@ class Abstractions: @dc.dataclass class Column: - """ - The `Column` class is a generic column class. It holds a dict containing the column - name, type whether the column is notnull, whether the column is a primary key and - the default value, if any. `Column`s are typically stored in a `ColumnInfo` + """The `Column` class is a generic column class. It holds a dict containing the + column name, type whether the column is notnull, whether the column is a primary + key and the default value, if any. `Column`s are typically stored in a `ColumnInfo` collection. There are multiple ways to get information from a `Column`, including subscript notation, and via properties. The available column info via these methods are name, domain, notnull, default and pk See example: @@ -8146,9 +8305,8 @@ def __contains__(self, item): return item in self.__dict__ def cast(self, value: Any) -> Any: - """ - Cast a value to the appropriate data type as defined by the column info for the - column. This can be useful for comparing values between the database and the + """Cast a value to the appropriate data type as defined by the column info for + the column. This can be useful for comparing values between the database and the GUI. :param value: The value you would like to cast @@ -8188,18 +8346,18 @@ def validate(self, value: Any) -> bool: @dc.dataclass class MinMaxCol(Column): - """ - Base ColumnClass for columns with minimum and maximum constraints. + """Base ColumnClass for columns with minimum and maximum constraints. This class extends the functionality of the base `Column` class to include optional validation based on minimum and maximum values. - :param min_value: The minimum allowed value for the column (inclusive). Defaults - to None, indicating no minimum constraint. - :type min_value: Any valid value type compatible with the column's data type. - :param max_value: The maximum allowed value for the column (inclusive). Defaults - to None, indicating no maximum constraint. - :type max_value: Any valid value type compatible with the column's data type. + Args: + min_value (Any valid value type compatible with the column's data type.): The + minimum allowed value for the column (inclusive). Defaults to None, + indicating no minimum constraint. + max_value (Any valid value type compatible with the column's data type.): The + maximum allowed value for the column (inclusive). Defaults to None, + indicating no maximum constraint. """ min_value: Any = None @@ -8223,15 +8381,15 @@ def validate(self, value): @dc.dataclass class LengthCol(Column): - """ - Base ColumnClass for length-constrained columns. + """Base ColumnClass for length-constrained columns. This class represents a column with length constraints. It inherits from the base `Column` class and adds attributes to store the maximum and minimum length values. The `validate` method is overridden to include length validations. - :param max_length: Maximum length allowed for the column value. - :param min_length: Minimum length allowed for the column value. + Args: + max_length: Maximum length allowed for the column value. + min_length: Minimum length allowed for the column value. """ min_length: int = None @@ -8264,11 +8422,11 @@ def validate(self, value): @dc.dataclass class LocaleCol(Column): - """ - Base ColumnClass that provides Locale functions + """Base ColumnClass that provides Locale functions. - :param negative_symbol: The symbol representing negative values in the locale. - :param currency_symbol: The symbol representing the currency in the locale. + Args: + negative_symbol: The symbol representing negative values in the locale. + currency_symbol: The symbol representing the currency in the locale. Example: col = LocaleCol() @@ -8501,8 +8659,7 @@ def cast(self, value): class ColumnInfo(List): - """ - Column Information Class. + """Column Information Class. The `ColumnInfo` class is a custom container that behaves like a List containing a collection of `Columns`. This class is responsible for maintaining information about @@ -8560,11 +8717,11 @@ def __getitem__(self, item): @property def pk_column(self) -> Union[str, None]: - """ - Get the pk_column for this colection of column_info. + """Get the pk_column for this colection of column_info. - :returns: A string containing the column name of the PK column, or None if one - was not found + Returns: + A string containing the column name of the PK column, or None if one was not + found """ for c in self: if c.pk: @@ -8573,31 +8730,35 @@ def pk_column(self) -> Union[str, None]: @property def names(self) -> List[str]: - """ - Return a List of column names from the `Column`s in this collection. + """Return a List of column names from the `Column`s in this collection. - :returns: List of column names + Returns: + List of column names """ return self._get_list("name") def col_name(self, idx: int) -> str: - """ - Get the column name located at the specified index in this collection of + """Get the column name located at the specified index in this collection of `Column`s. - :param idx: The index of the column to get the name from - :returns: The name of the column at the specified index + Args: + idx: The index of the column to get the name from + + Returns: + The name of the column at the specified index """ return self[idx].name def default_row_dict(self, dataset: DataSet) -> dict: - """ - Return a dictionary of a table row with all defaults assigned. + """Return a dictionary of a table row with all defaults assigned. This is useful for inserting new records to prefill the GUI elements. - :param dataset: a pysimplesql DataSet object - :returns: dict + Args: + dataset: a pysimplesql DataSet object + + Returns: + dict """ d = {} for c in self: @@ -8679,15 +8840,17 @@ def default_row_dict(self, dataset: DataSet) -> dict: return d def set_null_default(self, python_type: str, value: object) -> None: - """ - Set a Null default for a single python type. + """Set a Null default for a single python type. - :param python_type: This should be equal to what calling `.__name__` on the - Column `python_type` would equal: 'str', 'int', 'float', 'Decimal', 'bool', - 'time', 'date', or 'datetime'. - :param value: The new value to set the SQL type to. This can be a literal or - even a callable - :returns: None + Args: + python_type: This should be equal to what calling `.__name__` on the Column + `python_type` would equal: 'str', 'int', 'float', 'Decimal', 'bool', + 'time', 'date', or 'datetime'. + value: The new value to set the SQL type to. This can be a literal or even a + callable + + Returns: + None """ if python_type not in self._python_types: RuntimeError( @@ -8698,14 +8861,17 @@ def set_null_default(self, python_type: str, value: object) -> None: self.null_defaults[python_type] = value def set_null_defaults(self, null_defaults: dict) -> None: - """ - Set Null defaults for all python types. + """Set Null defaults for all python types. Supported types: 'str', 'int', 'float', 'Decimal', 'bool', 'time', 'date', or 'datetime'. - :param null_defaults: A dict of python types and default values. This can be a - literal or even a callable - :returns: None + + Args: + null_defaults: A dict of python types and default values. This can be a + literal or even a callable + + Returns: + None """ # Check if the null_defaults dict has all the required keys: if not all(key in null_defaults for key in self._python_types): @@ -8717,11 +8883,11 @@ def set_null_defaults(self, null_defaults: dict) -> None: self.null_defaults = null_defaults def get_virtual_names(self) -> List[str]: - """ - Get a list of virtual column names. + """Get a list of virtual column names. - :returns: A List of column names that are virtual, or [] if none are present in - this collections + Returns: + A List of column names that are virtual, or [] if none are present in this + collections """ return [c.name for c in self if c.virtual] @@ -8758,10 +8924,8 @@ def _get_list(self, key: str) -> List: # lastrowid and exceptions passed from the driver. # -------------------------------------------------------------------------------------- class Result: - """ - This is a "dummy" Result object that is a convenience for constructing a DataFrame - that has the expected attrs set. - """ + """This is a "dummy" Result object that is a convenience for constructing a + DataFrame that has the expected attrs set.""" @classmethod def set( @@ -8772,13 +8936,13 @@ def set( column_info: ColumnInfo = None, row_backup: pd.Series = None, ): - """ - Create a pandas DataFrame with the row data and expected attrs set. + """Create a pandas DataFrame with the row data and expected attrs set. - :param row_data: A list of dicts of row data - :param lastrowid: The inserted row ID from the last INSERT statement - :param exception: Exceptions passed back from the SQLDriver - :param column_info: An optional ColumnInfo object + Args: + row_data: A list of dicts of row data + lastrowid: The inserted row ID from the last INSERT statement + exception: Exceptions passed back from the SQLDriver + column_info: An optional ColumnInfo object """ rows = pd.DataFrame(row_data) rows.attrs["lastrowid"] = lastrowid @@ -8819,10 +8983,9 @@ class SqlChar: @dc.dataclass class SQLDriver(ABC): - """ - Abstract SQLDriver class. Derive from this class to create drivers that conform to - PySimpleSQL. This ensures that the same code will work the same way regardless of - which database is used. There are a few important things to note: The commented + """Abstract SQLDriver class. Derive from this class to create drivers that conform + to PySimpleSQL. This ensures that the same code will work the same way regardless + of which database is used. There are a few important things to note: The commented code below is broken into methods that **MUST** be implemented in the derived class, methods that. @@ -8838,10 +9001,11 @@ class SQLDriver(ABC): INSERT query is executed with SQLDriver.execute() or a record is inserted with SQLDriver.insert_record() - :param update_cascade: (optional) Default:True. Requery and filter child table - on selected parent primary key. (ON UPDATE CASCADE in SQL) - :param delete_cascade: (optional) Default:True. Delete the dependent child - records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + Args: + update_cascade: (optional) Default:True. Requery and filter child table on + selected parent primary key. (ON UPDATE CASCADE in SQL) + delete_cascade: (optional) Default:True. Delete the dependent child records if + the parent table record is deleted. (ON UPDATE DELETE in SQL) """ host: str = None @@ -8902,14 +9066,13 @@ def _init_db(self) -> None: @abstractmethod def connect(self, *args, **kwargs): - """ - Connect to a database. + """Connect to a database. Connect to a database in the connect() method, assigning the connection to self.con. - Implementation varies by database, you may need only one parameter, or - several depending on how a connection is established with the target database. + Implementation varies by database, you may need only one parameter, or several + depending on how a connection is established with the target database. """ @abstractmethod @@ -8920,16 +9083,17 @@ def execute( column_info: ColumnInfo = None, auto_commit_rollback: bool = False, ): - """ - Implements the native SQL implementation's execute() command. + """Implements the native SQL implementation's execute() command. - :param query: The query string to execute - :param values: Values to pass into the query to replace the placeholders - :param column_info: An optional ColumnInfo object - :param auto_commit_rollback: Automatically commit or rollback depending on - whether an exception was handled. Set to False by default. Set to True to - have exceptions and commit/rollbacks happen automatically - :return: + Args: + query: The query string to execute + values: Values to pass into the query to replace the placeholders + column_info: An optional ColumnInfo object + auto_commit_rollback: Automatically commit or rollback depending on whether + an exception was handled. Set to False by default. Set to True to have + exceptions and commit/rollbacks happen automatically + + Returns: """ @abstractmethod @@ -8968,16 +9132,18 @@ def next_pk(self, table: str, pk_column: str) -> int: return 1 def check_keyword(self, keyword: str, key: str = None) -> None: - """ - Check keyword to see if it is a reserved word. If it is raise a + """Check keyword to see if it is a reserved word. If it is raise a ReservedKeywordError. Checks to see if the database name is in keys and uses the database name for the key if it exists, otherwise defaults to 'common' in the RESERVED set. Override this with the specific key for the database if needed for best results. - :param keyword: the value to check against reserved words - :param key: The key in the RESERVED set to check in - :returns: None + Args: + keyword: the value to check against reserved words + key: The key in the RESERVED set to check in + + Returns: + None """ if not self.check_reserved_keywords: return @@ -9052,13 +9218,13 @@ def max_pk(self, table: str, pk_column: str) -> int: return 0 def generate_join_clause(self, dataset: DataSet) -> str: - """ - Automatically generates a join clause from the Relationships that have been set. + """Automatically generates a join clause from the Relationships that have been + set. This typically isn't used by end users. - :returns: A join string to be used in a sqlite3 query - :rtype: str + Returns: + str: A join string to be used in a sqlite3 query """ join = "" for r in self.relationships: @@ -9067,14 +9233,13 @@ def generate_join_clause(self, dataset: DataSet) -> str: return join if not dataset.join_clause else dataset.join_clause def generate_where_clause(self, dataset: DataSet) -> str: - """ - Generates a where clause from the Relationships that have been set, as well as - the DataSet's where clause. + """Generates a where clause from the Relationships that have been set, as well + as the DataSet's where clause. This is not typically used by end users. - :returns: A where clause string to be used in a sqlite3 query - :rtype: str + Returns: + str: A where clause string to be used in a sqlite3 query """ where = "" for r in self.relationships: @@ -9108,14 +9273,16 @@ def generate_query( where_clause: bool = True, order_clause: bool = True, ) -> str: - """ - Generate a query string using the relationships that have been set. + """Generate a query string using the relationships that have been set. + + Args: + dataset: A `DataSet` object + join_clause: True to auto-generate `join` clause, False to not + where_clause: True to auto-generate `where` clause, False to not + order_clause: True to auto-generate `order by` clause, False to not - :param dataset: A `DataSet` object - :param join_clause: True to auto-generate `join` clause, False to not - :param where_clause: True to auto-generate `where` clause, False to not - :param order_clause: True to auto-generate `order by` clause, False to not - :returns: a query string for use with sqlite3 + Returns: + a query string for use with sqlite3 """ return ( f"{dataset.query}" @@ -9202,9 +9369,8 @@ def _delete_record_recursive( return None def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: - """ - Duplicates a record in a database table and optionally duplicates its dependent - records. + """Duplicates a record in a database table and optionally duplicates its + dependent records. The function uses all columns found in `Dataset.column_info` and select all except the primary key column, inserting a duplicate record with the @@ -9217,9 +9383,10 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: Note that this function assumes the primary key column is auto-incrementing and that no columns are set to unique. - :param dataset: The `Dataset` of the the record to be duplicated. - :param children: (optional) Whether to duplicate dependent records. Defaults to - False. + Args: + dataset: The `Dataset` of the the record to be duplicated. + children: (optional) Whether to duplicate dependent records. Defaults to + False. """ # Get variables @@ -9309,16 +9476,16 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: def _insert_duplicate_record( self, table: str, columns: str, pk_column: str, pk: int ) -> pd.DataFrame: - """ - Inserts duplicate record, sets attrs["lastrowid"] to new record's pk. + """Inserts duplicate record, sets attrs["lastrowid"] to new record's pk. Used by `SQLDriver.duplicate_record` to handle database-specific differences in returning new primary keys. - :param table: Escaped table name of record to be duplicated - :param columns: Escaped and comman (,) seperated list of columns - :param pk_column: Non-escaped pk_column - :param pk: Primary key of record + Args: + table: Escaped table name of record to be duplicated + columns: Escaped and comman (,) seperated list of columns + pk_column: Non-escaped pk_column + pk: Primary key of record """ query = ( f"INSERT INTO {table} ({columns}) " @@ -9397,25 +9564,27 @@ def add_relationship( update_cascade: bool, delete_cascade: bool, ) -> None: - """ - Add a foreign key relationship between two dataset of the database When you + """Add a foreign key relationship between two dataset of the database When you attach a database, PySimpleSQL isn't aware of the relationships contained until dataset are added via `Form.add_data`, and the relationship of various tables is set with this function. Note that `SQLDriver.auto_add_relationships()` will do this automatically from the schema of the database, which also happens automatically when a `SQLDriver` is created. - :param join: The join type of the relationship ('LEFT JOIN', 'INNER JOIN', - 'RIGHT JOIN') - :param child_table: The child table containing the foreign key - :param fk_column: The foreign key column of the child table - :param parent_table: The parent table containing the primary key - :param pk_column: The primary key column of the parent table - :param update_cascade: Requery and filter child table results on selected parent - primary key (ON UPDATE CASCADE in SQL) - :param delete_cascade: Delete the dependent child records if the parent table - record is deleted (ON UPDATE DELETE in SQL) - :returns: None + Args: + join: The join type of the relationship ('LEFT JOIN', 'INNER JOIN', 'RIGHT + JOIN') + child_table: The child table containing the foreign key + fk_column: The foreign key column of the child table + parent_table: The parent table containing the primary key + pk_column: The primary key column of the parent table + update_cascade: Requery and filter child table results on selected parent + primary key (ON UPDATE CASCADE in SQL) + delete_cascade: Delete the dependent child records if the parent table + record is deleted (ON UPDATE DELETE in SQL) + + Returns: + None """ self.relationships.append( Relationship( @@ -9433,8 +9602,7 @@ def add_relationship( # Make sure to send a list of table names to requery if you want # dependent dataset to requery automatically def auto_add_relationships(self) -> None: - """ - Automatically add a foreign key relationship between tables of the database. + """Automatically add a foreign key relationship between tables of the database. This is done by foreign key constraints within the database. Automatically requery the child table if the parent table changes (ON UPDATE CASCADE in sql is set) When you attach a database, PySimpleSQL isn't aware of the relationships @@ -9442,7 +9610,8 @@ def auto_add_relationships(self) -> None: This happens automatically during `Form` creation. Note that `Form.add_relationship()` can do this manually. - :returns: None + Returns: + None """ logger.info("Automatically adding foreign key relationships") # Clear any current rels so that successive calls will not double the entries @@ -9466,14 +9635,16 @@ def auto_add_relationships(self) -> None: ) def check_reserved_keywords(self, value: bool) -> None: - """ - SQLDrivers can check to make sure that field names respect their own reserved + """SQLDrivers can check to make sure that field names respect their own reserved keywords. By default, all SQLDrivers will check for their respective keywords. You can choose to disable this feature with this method. - :param value: True to check for reserved keywords in field names, false to skip - this check - :return: None + Args: + value: True to check for reserved keywords in field names, false to skip + this check + + Returns: + None """ self._CHECK_RESERVED_KEYWORDS = value @@ -9512,9 +9683,7 @@ def _get_column_class(self, domain) -> Union[ColumnClass, None]: # -------------------------------------------------------------------------------------- @dc.dataclass class Sqlite(SQLDriver): - """ - The SQLite driver supports SQLite3 databases. - """ + """The SQLite driver supports SQLite3 databases.""" global sqlite3 # noqa PLW0603 import sqlite3 @@ -9556,10 +9725,11 @@ def __init__( skip_sql_if_db_exists: bool = True, ): """ - :param update_cascade: (optional) Default:True. Requery and filter child table - on selected parent primary key. (ON UPDATE CASCADE in SQL) - :param delete_cascade: (optional) Default:True. Delete the dependent child - records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + Args: + update_cascade: (optional) Default:True. Requery and filter child table on + selected parent primary key. (ON UPDATE CASCADE in SQL) + delete_cascade: (optional) Default:True. Delete the dependent child records + if the parent table record is deleted. (ON UPDATE DELETE in SQL) """ self._database = str(database) self.sql_commands = sql_commands @@ -9803,8 +9973,7 @@ def _register_type_callables(self): @dc.dataclass class Flatfile(Sqlite): - """ - The Flatfile driver adds support for flatfile databases such as CSV files to + """The Flatfile driver adds support for flatfile databases such as CSV files to pysimplesql. The flatfile data is loaded into an internal SQlite database, where it can be used @@ -9822,28 +9991,27 @@ def __init__( table: str = None, pk_col: str = None, ) -> None: - """ - Create a new Flatfile driver instance. - - :param file_path: The path to the flatfile - :param delimiter: The delimiter for the flatfile. Defaults to ','. Tabs ('\t') - are another popular option - :param quotechar: The quoting character specified by the flatfile. - Defaults to '"' - :param header_row_num: The row containing the header column names. - Defaults to 0 - :param table: The name to give this table in pysimplesql. Default is 'Flatfile' - :param pk_col: The column name that acts as a primary key for the dataset. See - below how to use this parameter: - - If no pk_col parameter is supplied, then a generic primary key column named - 'pk' will be generated with AUTO INCREMENT and PRIMARY KEY set. This is a - virtual column and will not be written back out to the flatfile. - - If the pk_col parameter is supplied, and it exists in the header row, then - it will be used as the primary key for the dataset. If this column does - not exist in the header row, then a virtual primary key column with this - name will be created with AUTO INCREMENT and PRIMARY KEY set. As above, the - virtual primary key column that was created will not be written to the - flatfile. + """Create a new Flatfile driver instance. + + Args: + file_path: The path to the flatfile + delimiter: The delimiter for the flatfile. Defaults to ','. Tabs ('\t') are + another popular option + quotechar: The quoting character specified by the flatfile. Defaults to '"' + header_row_num: The row containing the header column names. Defaults to 0 + table: The name to give this table in pysimplesql. Default is 'Flatfile' + pk_col: The column name that acts as a primary key for the dataset. See + below how to use this parameter: + - If no pk_col parameter is supplied, then a generic primary key column + named 'pk' will be generated with AUTO INCREMENT and PRIMARY KEY set. + This is a virtual column and will not be written back out to the + flatfile. + - If the pk_col parameter is supplied, and it exists in the header row, + then it will be used as the primary key for the dataset. If this + column does not exist in the header row, then a virtual primary key + column with this name will be created with AUTO INCREMENT and PRIMARY + KEY set. As above, the virtual primary key column that was created + will not be written to the flatfile. """ self.file_path = file_path self.delimiter = delimiter @@ -9973,9 +10141,7 @@ def save_record( # -------------------------------------------------------------------------------------- @dc.dataclass class Mysql(SQLDriver): - """ - The Mysql driver supports MySQL databases. - """ + """The Mysql driver supports MySQL databases.""" tinyint1_is_boolean: bool = True @@ -10229,16 +10395,16 @@ def constraint(self, constraint_name): def _insert_duplicate_record( self, table: str, columns: str, pk_column: str, pk: int ) -> pd.DataFrame: - """ - Inserts duplicate record, sets attrs["lastrowid"] to new record's pk. + """Inserts duplicate record, sets attrs["lastrowid"] to new record's pk. Used by `SQLDriver.duplicate_record` to handle database-specific differences in returning new primary keys. - :param table: Escaped table name of record to be duplicated - :param columns: Escaped and comman (,) seperated list of columns - :param pk_column: Non-escaped pk_column - :param pk: Primary key of record + Args: + table: Escaped table name of record to be duplicated + columns: Escaped and comman (,) seperated list of columns + pk_column: Non-escaped pk_column + pk: Primary key of record """ query = ( f"INSERT INTO {table} ({columns}) " @@ -10267,9 +10433,7 @@ def _insert_duplicate_record( # it more clear for the end user. @dc.dataclass class Mariadb(Mysql): - """ - The Mariadb driver supports MariaDB databases. - """ + """The Mariadb driver supports MariaDB databases.""" NAME: ClassVar[str] = "MariaDB" @@ -10279,9 +10443,7 @@ class Mariadb(Mysql): # -------------------------------------------------------------------------------------- @dc.dataclass class Postgres(SQLDriver): - """ - The Postgres driver supports PostgreSQL databases. - """ + """The Postgres driver supports PostgreSQL databases.""" sql_char: dc.InitVar[SqlChar] = SqlChar(table_quote='"') # noqa RUF009 sync_sequences: bool = False @@ -10601,9 +10763,7 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- @dc.dataclass class Sqlserver(SQLDriver): - """ - The Sqlserver driver supports Microsoft SQL Server databases. - """ + """The Sqlserver driver supports Microsoft SQL Server databases.""" sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 placeholder="?", table_quote="[]" @@ -10910,10 +11070,9 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- @dc.dataclass class MSAccess(SQLDriver): - """ - The MSAccess driver supports Microsoft Access databases. - Note that only database interactions are supported, including stored Queries, but - not operations dealing with Forms, Reports, etc. + """The MSAccess driver supports Microsoft Access databases. Note that only database + interactions are supported, including stored Queries, but not operations dealing + with Forms, Reports, etc. Note: `Jackcess` and `UCanAccess` libraries may not accurately report decimal places for `Number` or `Currency` columns. Manual configuration of decimal places may @@ -10951,30 +11110,28 @@ def __init__( infer_datetype_from_default_function: bool = True, use_newer_jackcess: bool = False, ): - """ - Initialize the MSAccess class. - - :param database_file: The path to the MS Access database file. - :param overwrite_file: If True, prompts the user if the file already exists. If - the user declines to overwrite the file, the provided SQL commands or script - will not be executed. - :param sql_commands: Optional SQL commands to execute after opening the - database. - :param sql_script: Optional SQL script file to execute after opening the - database. - :param sql_script_encoding: The encoding of the SQL script file. Defaults to - 'utf-8'. - :param update_cascade: (optional) Default:True. Requery and filter child table - on selected parent primary key. (ON UPDATE CASCADE in SQL) - :param delete_cascade: (optional) Default:True. Delete the dependent child - records if the parent table record is deleted. (ON UPDATE DELETE in SQL) - :param infer_datetype_from_default_function: If True, specializes a DateTime - column by examining the column's default function. A DateTime column with - '=Date()' will be treated as a 'DateCol', and '=Time()' will be treated as a - 'TimeCol'. Defaults to True. - :param use_newer_jackcess: If True, uses a newer version of the Jackcess library - for improved compatibility, specifically allowing handling of 'attachment' - columns. Defaults to False. + """Initialize the MSAccess class. + + Args: + database_file: The path to the MS Access database file. + overwrite_file: If True, prompts the user if the file already exists. If the + user declines to overwrite the file, the provided SQL commands or script + will not be executed. + sql_commands: Optional SQL commands to execute after opening the database. + sql_script: Optional SQL script file to execute after opening the database. + sql_script_encoding: The encoding of the SQL script file. Defaults to + 'utf-8'. + update_cascade: (optional) Default:True. Requery and filter child table on + selected parent primary key. (ON UPDATE CASCADE in SQL) + delete_cascade: (optional) Default:True. Delete the dependent child records + if the parent table record is deleted. (ON UPDATE DELETE in SQL) + infer_datetype_from_default_function: If True, specializes a DateTime column + by examining the column's default function. A DateTime column with + '=Date()' will be treated as a 'DateCol', and '=Time()' will be treated + as a 'TimeCol'. Defaults to True. + use_newer_jackcess: If True, uses a newer version of the Jackcess library + for improved compatibility, specifically allowing handling of + 'attachment' columns. Defaults to False. """ self.database_file = str(database_file) @@ -11377,8 +11534,9 @@ def _register_default_converters(self): # TYPEDDICTS AND TYPEALIASES # -------------------------- class Driver: - """ - The `Driver` class allows for easy driver creation. It is a simple wrapper around + """The `Driver` class allows for easy driver creation. + + It is a simple wrapper around the various `SQLDriver` classes. """ From 62c5887f8501324f893052f9cce0f307b4013845 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Tue, 25 Jul 2023 08:55:37 -0400 Subject: [PATCH 107/121] Update .git-blame-ignore-revs --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 5fed50b9..b95d9996 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -18,3 +18,5 @@ cb040bf0656ab6b3c019fadc6adf98c7d4ba01ea f7addad546672815db9293f772db71c57521f8e8 fbeec4c4322b7a1f8dc4cd82ac3c10e6c313901a +# google style docstrings +e17857d4369d57961b6f75385fd1e5a1d2434869 \ No newline at end of file From 079f9a5c9ab5a2bf2cf0d2f435d27dacad707f6f Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Tue, 25 Jul 2023 09:33:35 -0400 Subject: [PATCH 108/121] Change mkdocstrings to google Fix a few 'Critical' mkdocstrings errors --- mkdocs.yml | 4 +++- pysimplesql/pysimplesql.py | 49 +++++++++++++++++++------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 0eabc3e0..8ae0af4c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,3 +1,5 @@ +# mkdocs gh-deploy --no-history + site_name: pysimplesql site_url: https://example.com/ @@ -16,5 +18,5 @@ plugins: handlers: python: options: - docstring_style: "sphinx" + docstring_style: "google" diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index aad6003a..31ff0e16 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -831,31 +831,31 @@ def set_callback( supported. The following callbacks are supported: - before_save called before a record is saved. The save will continue if the - callback returns true, or the record will rollback if the callback + - before_save: called before a record is saved. The save will continue if + the callback returns true, or the record will rollback if the callback returns false. - after_save called after a record is saved. The save will commit to the + - after_save: called after a record is saved. The save will commit to the database if the callback returns true, else it will rollback the transaction - before_update Alias for before_save - after_update Alias for after_save - before_delete called before a record is deleted. The delete will move + - before_update: Alias for before_save + - after_update: Alias for after_save + - before_delete: called before a record is deleted. The delete will move forward if the callback returns true, else the transaction will rollback - after_delete called after a record is deleted. The delete will commit to + - after_delete: called after a record is deleted. The delete will commit to the database if the callback returns true, else it will rollback the transaction - before_duplicate called before a record is duplicate. The duplicate will + - before_duplicate: called before a record is duplicate. The duplicate will move forward if the callback returns true, else the transaction will rollback - after_duplicate called after a record is duplicate. The duplicate will + - after_duplicate: called after a record is duplicate. The duplicate will commit to the database if the callback returns true, else it will rollback the transaction - before_search called before searching. The search will continue if the + - before_search: called before searching. The search will continue if the callback returns True - after_search called after a search has been performed. The record change + - after_search: called after a search has been performed. The record change will undo if the callback returns False - record_changed called after a record has changed (previous,next, etc.) - after_record_edit called after the internal `DataSet` row is edited via a + - record_changed: called after a record has changed (previous,next, etc.) + - after_record_edit: called after the internal `DataSet` row is edited via a `sg.Table` cell-edit, or `field` live-update. Args: @@ -912,10 +912,11 @@ def set_transform(self, fn: callable) -> None: Args: fn: A callable function to preform encode/decode. This function should - take three arguments: query, row (which will be populated by a dictionary of the - row data), and an encode parameter (1 to encode, 0 to decode - see constants - `TFORM_ENCODE` and `TFORM_DECODE`). Note that this transform works on one row at - a time. See the example `journal_with_data_manipulation.py` for a usage example. + take three arguments: query, row (which will be populated by a + dictionary of the row data), and an encode parameter (1 to encode, 0 to + decode - see constants `TFORM_ENCODE` and `TFORM_DECODE`). Note that + this transform works on one row at a time. See the example + `journal_with_data_manipulation.py` for a usage example. Returns: None @@ -1393,7 +1394,7 @@ def last( update_elements: bool = True, requery_dependents: bool = True, skip_prompt_save: bool = False, - ): + ) -> None: """Move to the last record of the table. Only one entry in the table is ever considered "Selected". This is one of @@ -1433,7 +1434,7 @@ def next( update_elements: bool = True, requery_dependents: bool = True, skip_prompt_save: bool = False, - ): + ) -> None: """Move to the next record of the table. Only one entry in the table is ever considered "Selected". This is one of @@ -1473,7 +1474,7 @@ def previous( update_elements: bool = True, requery_dependents: bool = True, skip_prompt_save: bool = False, - ): + ) -> None: """Move to the previous record of the table. Only one entry in the table is ever considered "Selected". This is one of @@ -1887,7 +1888,7 @@ def add_selector( self.selector.append(d) def insert_record( - self, values: Dict[str : Union[str, int]] = None, skip_prompt_save: bool = False + self, values: Dict[str, Union[str, int]] = None, skip_prompt_save: bool = False ) -> None: """Insert a new record virtually in the `DataSet` object. @@ -2735,7 +2736,7 @@ def column_likely_in_selector(self, column: str) -> bool: def combobox_values( self, column_name, insert_placeholder: bool = True - ) -> List[_ElementRow] or None: + ) -> Union[List[_ElementRow], None]: """Returns the values to use in a sg.Combobox as a list of _ElementRow objects. Args: @@ -7074,14 +7075,14 @@ class TableStyler: # PySimpleGUI Table kwargs that are compatible with pysimplesql justification: TableJustify = "left" row_height: int = None - font: str or Tuple[str, int] or None = None + font: Union[str, Tuple[str, int], None] = None text_color: str = None background_color: str = None alternating_row_color: str = None selected_row_colors: Tuple[str, str] = (None, None) header_text_color: str = None header_background_color: str = None - header_font: str or Tuple[str, int] or None = None + header_font: Union[str, Tuple[str, int], None] = None header_border_width: int = None header_relief: str = None vertical_scroll_only: bool = True From 49a213ed9d11f3d965f2e6daab3b5ffec5ddd9f0 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:45:47 -0400 Subject: [PATCH 109/121] Rename _LastSearch to _PrevSearch Adds clarity, since Last is used elsewhere for the last record of a table, not previous :) --- pysimplesql/pysimplesql.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 31ff0e16..a8978df8 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -290,7 +290,9 @@ class ValidateResponse: @dc.dataclass -class _LastSearch: +class _PrevSearch: + """Internal Class. Keeps track of previous search to cycle through results""" + search_string: str = None column: str = None pks: List[int] = dc.field(default_factory=list) @@ -613,8 +615,8 @@ class DataSet: """`DataSet` objects are used for an internal representation of database tables. - `DataSet` instances are added by the following `Form` methods: `Form.add_table`, - `Form.auto_add_tables`. A `DataSet` is synonymous for a SQL Table (though you can + `DataSet` instances are added by the following `Form` methods: `Form.add_dataset`, + `Form.auto_add_datasets`. A `DataSet` is synonymous for a SQL Table (though you can technically have multiple `DataSet` objects referencing the same table, with each `DataSet` object having its own sorting, where clause, etc.). Note: While users will interact with DataSet objects often in pysimplesql, they @@ -683,7 +685,7 @@ def __post_init__(self, data_key, frm_reference, prompt_save): self.where_clause: str = "" # In addition to generated where clause! self.search_order: List[str] = [] - self._last_search: _LastSearch = _LastSearch() + self._prev_search: _PrevSearch = _PrevSearch() self._search_string: tk.StringVar = None self.callbacks: CallbacksDict = {} @@ -1567,14 +1569,14 @@ def search( ): return SEARCH_ABORTED - # Reset _last_search if search_string is different - if search_string != self._last_search.search_string: - self._last_search = _LastSearch(search_string) + # Reset _prev_search if search_string is different + if search_string != self._prev_search.search_string: + self._prev_search = _PrevSearch(search_string) - # Reorder search_columns to start with the column in _last_search + # Reorder search_columns to start with the column in _prev_search search_columns = self.search_order.copy() - if self._last_search.column in search_columns: - idx = search_columns.index(self._last_search.column) + if self._prev_search.column in search_columns: + idx = search_columns.index(self._prev_search.column) search_columns = search_columns[idx:] + search_columns[:idx] # reorder rows to be idx + 1, and wrap around back to the beginning @@ -1587,8 +1589,8 @@ def search( pk = None for column in search_columns: - # update _last_search column - self._last_search.column = column + # update _prev_search column + self._prev_search.column = column # search through processed rows, looking for search_string result = rows[ @@ -1602,7 +1604,7 @@ def search( pk = result.iloc[0][self.pk_column] # search next column if the same pk is found again - if pk in self._last_search.pks: + if pk in self._prev_search.pks: continue # if pk is same as one we are on, we can just updated_elements @@ -1617,8 +1619,8 @@ def search( break if pk: - # Update _last_search with the pk - self._last_search.pks.append(pk) + # Update _prev_search with the pk + self._prev_search.pks.append(pk) # jump to the pk self.set_by_pk( From f17b54aa0ce1c00847784bef530bd7130ff9db17 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 26 Jul 2023 13:46:06 -0400 Subject: [PATCH 110/121] Docs --- mkdocs.yml | 7 ++++++ pysimplesql/pysimplesql.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 8ae0af4c..450eea29 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,10 +7,17 @@ theme: name: "material" logo: "assets/icon.svg" favicon: "assets/icon.svg" + features: + - content.code.copy nav: - Home: index.md - Docs: pysimplesql.md + +markdown_extensions: + - admonition + - codehilite + - pymdownx.superfences plugins: - search diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index a8978df8..7a2fd2fb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -262,16 +262,36 @@ class Boolean(enum.Flag): + """ + Enumeration class providing a convenient way to differentiate when a function may + return a 'truthy' or 'falsy' value, such as 1, "", or 0. + """ + TRUE = True + """Represents the boolean value True.""" FALSE = False + """Represents the boolean value False.""" class ValidateMode(enum.Enum): + """ + Enumeration class representing different validation modes. + """ + STRICT = "strict" + """Strict prevents invalid values from being entered.""" RELAXED = "relaxed" + """Relaxed allows invalid input, but ensures validation occurs before saving to the + database.""" + DISABLED = "disabled" + """Validation is turned off, and no checks or restrictions are applied.""" class ValidateRule(enum.Enum): + """ + Mostly internal class. May need to override if creating new custom ColumnClass. + """ + REQUIRED = "required" PYTHON_TYPE = "python_type" PRECISION = "precision" @@ -282,8 +302,36 @@ class ValidateRule(enum.Enum): CUSTOM = "custom" + @dc.dataclass class ValidateResponse: + """ + Represents the response returned by `ColumnClass.validate` method. + + Attributes: + exception: Indicates validation failure, if any. None for valid responses. + value: The value that was being validated. + rule: The specific `ValidateRule` that caused the exception, if applicable. + + + Example of how to create a response from an exception: + + ```python + response = frm[data_key].column_info[col].validate(value) + if response.exception: + msg = f"{ss.lang.dataset_save_validate_error_header}" + field = ss.lang.dataset_save_validate_error_field.format_map( + ss.LangFormat(field=col) + ) + exception = ss.lang[response.exception].format_map( + ss.LangFormat(value=response.value, rule=response.rule) + ) + msg += f"{field}{exception}" + frm.popup.ok(lang.dataset_save_validate_error_title, msg) + ``` + + """ + exception: Union[ValidateRule, None] = None value: str = None rule: str = None From 95de05037c68345b952581c668ca7e0a430d2314 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:35:55 -0400 Subject: [PATCH 111/121] Docs --- docs/{pysimplesql.md => api.md} | 2 ++ mkdocs.yml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) rename docs/{pysimplesql.md => api.md} (50%) diff --git a/docs/pysimplesql.md b/docs/api.md similarity index 50% rename from docs/pysimplesql.md rename to docs/api.md index 685ebc1c..93117467 100644 --- a/docs/pysimplesql.md +++ b/docs/api.md @@ -1,3 +1,5 @@ # Reference ::: pysimplesql.pysimplesql + options: + members_order: source \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 450eea29..a5ecb62d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,7 +12,7 @@ theme: nav: - Home: index.md - - Docs: pysimplesql.md + - API: api.md markdown_extensions: - admonition @@ -21,9 +21,13 @@ markdown_extensions: plugins: - search +- autorefs - mkdocstrings: handlers: python: + import: + - url: https://docs.python-requests.org/en/master/objects.inv + domains: [std, py] options: docstring_style: "google" From 56f08abd93cf3b5a5e3a62498f2a4284352179d0 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:34:39 -0400 Subject: [PATCH 112/121] Large Docs update (#341) * ValidateRule documentation * Update pysimplesql.py * Update pysimplesql.py * docs * Docs * Docs * autotyping 1 of * --bool-param * --int-param, --float-param, --str-param, --bytes-param * --int-param, --float-param, --str-param, --bytes-param * --annotate-magics * type hinting * Docs, and moving constants to enums * Fix: Check for 'TableBuilder' key intead of searching metadata * Update pysimplesql.py * import dataclass as dataclass, so we don't have to use dc. everywhere * use init=False to make these instance vars only * Get ruff to pass, harmonize sql_script/sql_commands * Rename parent_virtual, to is_parent_virtual Small fix too * . --- .libcst.codemod.yaml | 17 + doc_scripts/griffe_extension.py | 36 + docs/api.md | 5 - docs/index.md | 20 +- examples/MSAccess_examples/install_java.py | 4 +- examples/SQLite_examples/address_book.py | 4 +- examples/SQLite_examples/image_store.py | 6 +- examples/SQLite_examples/orders.py | 6 +- examples/orders_multiple_databases.py | 10 +- examples/tutorial_files/Journal/v4/journal.py | 2 +- mkdocs.yml | 7 +- pysimplesql/__init__.py | 3 +- pysimplesql/docker_utils.py | 15 +- pysimplesql/language_pack.py | 3 +- pysimplesql/pysimplesql.py | 1267 +++++++++-------- pysimplesql/reserved_sql_keywords.py | 1 + pysimplesql/theme_pack.py | 1 + ruff.toml | 15 +- setup.py | 12 +- tests/progressanimate_test.py | 10 +- tests/sqldriver_test.py | 4 +- 21 files changed, 825 insertions(+), 623 deletions(-) create mode 100644 .libcst.codemod.yaml create mode 100644 doc_scripts/griffe_extension.py delete mode 100644 docs/api.md diff --git a/.libcst.codemod.yaml b/.libcst.codemod.yaml new file mode 100644 index 00000000..bd182034 --- /dev/null +++ b/.libcst.codemod.yaml @@ -0,0 +1,17 @@ +# String that LibCST should look for in code which indicates that the +# module is generated code. +generated_code_marker: '@generated' +# Command line and arguments for invoking a code formatter. Anything +# specified here must be capable of taking code via stdin and returning +# formatted code via stdout. +formatter: ['black', '-'] +# List of regex patterns which LibCST will evaluate against filenames to +# determine if the module should be touched. +blacklist_patterns: [] +# List of modules that contain codemods inside of them. +modules: +- 'libcst.codemod.commands' - 'autotyping' +# Absolute or relative path of the repository root, used for providing +# full-repo metadata. Relative paths should be specified with this file +# location as the base. +repo_root: '.' diff --git a/doc_scripts/griffe_extension.py b/doc_scripts/griffe_extension.py new file mode 100644 index 00000000..689dea37 --- /dev/null +++ b/doc_scripts/griffe_extension.py @@ -0,0 +1,36 @@ +import ast +import re + +from griffe import Extension, Object, ObjectNode + + +class RegexUrl(Extension): + IGNORE = ["sg"] # + + def regex_replace(self, input_string: str, regex_pattern, prefix: str): + compiled_pattern = re.compile(regex_pattern) + + def replace_function(match): + parts = match.group(1).split(".") + if any(parts[0].startswith(prefix) for prefix in self.IGNORE): + return match.group(0) + + # get text section of url, we will only use the last obj + text = parts[-1] + + fn_suffix = "" + if match.group(2): + # pass () as html encoding + fn_suffix = "()" + complete_path = prefix + match.group(1) + return f"[{text}{fn_suffix}][{complete_path}]" + + return compiled_pattern.sub(replace_function, input_string) + + def on_instance(self, node: ast.AST | ObjectNode, obj: Object) -> None: + if obj.docstring: + # regex pattern matches a valid non-private class name or function, with or without a '()' at the end + regex_pattern = r"\`([A-Za-z][A-Za-z0-9_.]*)(\(\))*\`" + obj.docstring.value = self.regex_replace( + obj.docstring.value, regex_pattern, "pysimplesql.pysimplesql." + ) diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 93117467..00000000 --- a/docs/api.md +++ /dev/null @@ -1,5 +0,0 @@ -# Reference - -::: pysimplesql.pysimplesql - options: - members_order: source \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 000ea345..00c76c99 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,5 @@ -# Welcome to MkDocs +# API Reference (more pages to come) -For full documentation visit [mkdocs.org](https://www.mkdocs.org). - -## Commands - -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs -h` - Print help message and exit. - -## Project layout - - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +::: pysimplesql.pysimplesql + options: + members_order: source diff --git a/examples/MSAccess_examples/install_java.py b/examples/MSAccess_examples/install_java.py index bf0dd2ae..28e73e97 100644 --- a/examples/MSAccess_examples/install_java.py +++ b/examples/MSAccess_examples/install_java.py @@ -25,7 +25,7 @@ # ------------------------------------------------- # ROUTINES TO INSTALL JAVA IF USER DOES NOT HAVE IT # ------------------------------------------------- -def _is_java_installed(): +def _is_java_installed() -> bool: if "JAVA_HOME" in os.environ: return True previous_jre = load_setting("General", "java_home") @@ -91,7 +91,7 @@ def java_check_install() -> bool: return True -def save_setting(section: str, key: str, value: str): +def save_setting(section: str, key: str, value: str) -> None: config = configparser.ConfigParser() config.read(SETTINGS_FILE) diff --git a/examples/SQLite_examples/address_book.py b/examples/SQLite_examples/address_book.py index 09d91c81..6e943e49 100644 --- a/examples/SQLite_examples/address_book.py +++ b/examples/SQLite_examples/address_book.py @@ -9,7 +9,7 @@ # Zip code validation -def validate_zip(): +def validate_zip() -> bool: zipcode = win['Addresses.zip'].get() if len(zipcode) != 5: sg.popup('Check your zip code and try again!', title="Zip code validation failed!") @@ -97,7 +97,7 @@ def validate_zip(): [sg.Text("Zip:"+" "*63), ss.field("Addresses.zip", size=(6, 1), no_label=True)], [ss.actions("Addresses", edit_protect=False, duplicate=True)], # sg.StatusBar sets character limit based on initial value. Here we are filling it with 100 spaces. - [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})] + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.ElementType.INFO})] ] win = sg.Window('Address book example', layout, finalize=True, ttk_theme=ss.themepack.ttk_theme) diff --git a/examples/SQLite_examples/image_store.py b/examples/SQLite_examples/image_store.py index c64d5b8c..0674265e 100644 --- a/examples/SQLite_examples/image_store.py +++ b/examples/SQLite_examples/image_store.py @@ -22,7 +22,7 @@ # Note in the code later in this file, that you can choose to either: # 1) thumbnail the image prior to saving, so that you never store a large image in the database # 2) thumbnail the image only for display purposes, storing the full resolution image in the database -def thumbnail(image_data, size=(320, 240)): +def thumbnail(image_data, size: int=(320, 240)): img = Image.open(BytesIO(image_data)) img.thumbnail(size) with BytesIO() as output: @@ -72,7 +72,7 @@ def thumbnail(image_data, size=(320, 240)): # Another callback to update the sg.Image element when the elements update # first callback for encoding before saving to the database -def encode_image(): +def encode_image() -> bool: if not win['image_path'].get(): return False with open(win['image_path'].get(), 'rb') as file: @@ -89,7 +89,7 @@ def encode_image(): # Second callback updates the sg.Image element with the image data -def update_display(frm: ss.Form, win: sg.Window): +def update_display(frm: ss.Form, win: sg.Window) -> None: # Handle case where there are no records visible = len(frm["Image"].rows) == 0 win['no_records'].update(visible=visible) diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 4b8a1c31..3c64e341 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -38,7 +38,7 @@ # create your own validator to be passed to a # frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn # used below in the quick_editor arguments -def is_valid_email(email): +def is_valid_email(email: str): valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None if not valid_email: return ss.ValidateResponse( @@ -268,7 +268,7 @@ def is_valid_email(email): [ss.field("order_details.price", sg.Text)], [ss.field("order_details.subtotal", sg.Text)], [sg.Sizer(h_pixels=0, v_pixels=10)], - [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})], + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.ElementType.INFO})], ] layout.append([sg.Frame("Order Details", orderdetails_layout, expand_x=True)]) @@ -308,7 +308,7 @@ def is_valid_email(email): # Application-side code to update orders `total` # when saving/deleting order_details line item # ---------------------------------------------- -def update_orders(frm_reference, window, data_key): +def update_orders(frm_reference, window, data_key) -> bool: if data_key == "order_details": order_id = frm["order_details"]["order_id"] driver.execute( diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index 137c6fd2..f134fe70 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -87,12 +87,12 @@ class SqlFormat(dict): - def __missing__(self, key): + def __missing__(self, key) -> str: return "" class Template: - def __init__(self, template_string): + def __init__(self, template_string: str) -> None: self.template_string = template_string def render(self, context): @@ -103,7 +103,7 @@ def render(self, context): # create your own validator to be passed to a # frm[DATA_KEY].column_info[COLUMN_NAME].custom_validate_fn # used below in the quick_editor arguments -def is_valid_email(email): +def is_valid_email(email: str): valid_email = re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None if not valid_email: return ss.ValidateResponse( @@ -476,7 +476,7 @@ def is_valid_email(email): [ss.field("order_details.price", sg.Text)], [ss.field("order_details.subtotal", sg.Text)], [sg.Sizer(h_pixels=0, v_pixels=10)], - [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.TYPE_INFO})], + [sg.StatusBar(" " * 100, key="info_msg", metadata={"type": ss.ElementType.INFO})], ] layout.append([sg.Frame("Order Details", orderdetails_layout, expand_x=True)]) @@ -555,7 +555,7 @@ def is_valid_email(email): # Application-side code to update orders `total` # when saving/deleting order_details line item # ---------------------------------------------- -def update_orders(frm_reference, window, data_key): +def update_orders(frm_reference, window, data_key) -> bool: if data_key == "order_details": order_id = frm["order_details"]["order_id"] driver.execute( diff --git a/examples/tutorial_files/Journal/v4/journal.py b/examples/tutorial_files/Journal/v4/journal.py index 26313576..9aed387e 100644 --- a/examples/tutorial_files/Journal/v4/journal.py +++ b/examples/tutorial_files/Journal/v4/journal.py @@ -63,7 +63,7 @@ # --------------- # DATA VALIDATION # --------------- -def cb_validate(): +def cb_validate() -> bool: date=win['Journal.entry_date'].Get() if date[4] == '-' and date[7]=='-' and len(date)==10: # Make sure the date is 10 digits and has two dashes in the right place if str.isdigit(date[:4]): # Make sure the first 4 digits represent a year diff --git a/mkdocs.yml b/mkdocs.yml index a5ecb62d..1f3a3ec4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,7 +12,6 @@ theme: nav: - Home: index.md - - API: api.md markdown_extensions: - admonition @@ -30,4 +29,8 @@ plugins: domains: [std, py] options: docstring_style: "google" - + docstring_options: + ignore_init_summary: true + merge_init_into_class: true + extensions: + - doc_scripts/griffe_extension.py:RegexUrl diff --git a/pysimplesql/__init__.py b/pysimplesql/__init__.py index 07dbe0ca..4b0d9bc9 100644 --- a/pysimplesql/__init__.py +++ b/pysimplesql/__init__.py @@ -1,5 +1,4 @@ -""" -Write data-driven desktop apps fast! Lightweight Python library supports SQLite, +"""Write data-driven desktop apps fast! Lightweight Python library supports SQLite, MySQL/MariaDB, PostgreSQL & Flatfile CSV. Uses PySimpleGUI layouts. """ diff --git a/pysimplesql/docker_utils.py b/pysimplesql/docker_utils.py index 61feb4b9..ef7fc25d 100644 --- a/pysimplesql/docker_utils.py +++ b/pysimplesql/docker_utils.py @@ -1,5 +1,4 @@ -""" -DOCKER UTILITIES +"""DOCKER UTILITIES. This file is not used for pysimplesql base installation. It exists only as a collection of utility functions for examples which provide databases in Docker containers for @@ -18,8 +17,7 @@ def docker_image_installed(image: str) -> bool: - """ - Check if the specified Docker image is installed locally. + """Check if the specified Docker image is installed locally. :param image: The Docker image, including the tag ("pysimplesql/examples:postgres") :return: True if the image is installed, False otherwise @@ -35,8 +33,7 @@ def docker_image_installed(image: str) -> bool: def docker_image_is_latest(image: str) -> bool: - """ - Check if a new version of a Docker image is available for download. + """Check if a new version of a Docker image is available for download. :param image: The Docker image, including the tag ("pysimplesql/examples:postgres") :return: True if a newer version is available, False otherwise @@ -55,8 +52,7 @@ def docker_image_is_latest(image: str) -> bool: def docker_image_pull(image: str, latest: bool = True) -> None: - """ - Pull the supplied docker image, displaying a progress bar. + """Pull the supplied docker image, displaying a progress bar. :param latest: Ensure that the latest docker image is used (updates the local image) :return: @@ -106,8 +102,7 @@ def docker_image_pull(image: str, latest: bool = True) -> None: def docker_container_start( image: str, container_name: str, ports: dict ) -> docker.models.containers.Container: - """ - Create and/or start a Docker container with the specified image and container name. + """Create and/or start a Docker container with the specified image/container name. :param image: The Docker image to use for the container :param container_name: The name to use for the container diff --git a/pysimplesql/language_pack.py b/pysimplesql/language_pack.py index ec3e77da..9f0de276 100644 --- a/pysimplesql/language_pack.py +++ b/pysimplesql/language_pack.py @@ -1,5 +1,4 @@ -""" -ChatGPT prompt: +r"""ChatGPT prompt: I'm working on language localization for my python application. Can you look at this dict and make a spanish version? Please keep strings in brackets {} unaltered. diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7a2fd2fb..e73337e4 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -56,9 +56,7 @@ import asyncio import calendar import contextlib -import dataclasses as dc import datetime as dt -import enum import functools import inspect import itertools @@ -72,7 +70,10 @@ import tkinter as tk import tkinter.font as tkfont from abc import ABC, abstractmethod +from dataclasses import InitVar, dataclass, fields +from dataclasses import field as field_ from decimal import Decimal, DecimalException +from enum import Enum, Flag, auto from time import sleep, time from tkinter import ttk from typing import ( @@ -136,40 +137,46 @@ # ---------------------------------------------- locale.setlocale(locale.LC_ALL, "") -# --------------------------- -# Types for automatic mapping -# --------------------------- -TYPE_RECORD: int = 1 -TYPE_SELECTOR: int = 2 -TYPE_EVENT: int = 3 -TYPE_INFO: int = 4 - # ----------------- # Transform actions # ----------------- TFORM_ENCODE: int = 1 +"""TODO""" TFORM_DECODE: int = 0 +"""TODO""" + + +class ElementType(Enum): + """Types for automatic mapping.""" + + EVENT = auto() + FIELD = auto() + INFO = auto() + SELECTOR = auto() + + +class EventType(Enum): + """Event Types.""" + + FUNCTION = auto() + """Custom events (requires 'function')""" + + # DataSet-level events (requires 'table' dictionary key) + FIRST = auto() + PREVIOUS = auto() + NEXT = auto() + LAST = auto() + SEARCH = auto() + INSERT = auto() + DELETE = auto() + DUPLICATE = auto() + SAVE = auto() + QUICK_EDIT = auto() + + # Form-level events + SAVE_DB = auto() + EDIT_PROTECT_DB = auto() -# ----------- -# Event types -# ----------- -# Custom events (requires 'function' dictionary key) -EVENT_FUNCTION: int = 0 -# DataSet-level events (requires 'table' dictionary key) -EVENT_FIRST: int = 1 -EVENT_PREVIOUS: int = 2 -EVENT_NEXT: int = 3 -EVENT_LAST: int = 4 -EVENT_SEARCH: int = 5 -EVENT_INSERT: int = 6 -EVENT_DELETE: int = 7 -EVENT_DUPLICATE: int = 13 -EVENT_SAVE: int = 8 -EVENT_QUICK_EDIT: int = 9 -# Form-level events -EVENT_SEARCH_DB: int = 10 -EVENT_SAVE_DB: int = 11 -EVENT_EDIT_PROTECT_DB: int = 12 # ---------------- # GENERIC BITMASKS @@ -177,25 +184,36 @@ # Can be used with other bitmask values SHOW_MESSAGE: int = 4096 -# --------------------------- -# PROMPT_SAVE RETURN BITMASKS -# --------------------------- -PROMPT_SAVE_PROCEED: int = 2 -PROMPT_SAVE_NONE: int = 4 -PROMPT_SAVE_DISCARDED: int = 8 + +class PromptSaveReturn(Enum): + """prompt_save return enums.""" + + PROCEED = auto() + """After prompt_save, proceeded to save""" + NONE = auto() + """Found no records changed""" + DISCARDED = auto() + """User declined to save""" + + # --------------------------- # PROMPT_SAVE MODES # --------------------------- PROMPT_MODE: int = 1 +"""TODO""" AUTOSAVE_MODE: int = 2 +"""TODO""" PROMPT_SAVE_MODES = Literal[PROMPT_MODE, AUTOSAVE_MODE] # --------------------------- # RECORD SAVE RETURN BITMASKS # --------------------------- -SAVE_FAIL: int = 1 # Save failed due to callback -SAVE_SUCCESS: int = 2 # Save was successful -SAVE_NONE: int = 4 # There was nothing to save +SAVE_FAIL: int = 1 +"""Save failed due to callback or database error""" +SAVE_SUCCESS: int = 2 +"""Save was successful""" +SAVE_NONE: int = 4 +"""There was nothing to save""" # ---------------------- # SEARCH RETURN BITMASKS @@ -261,10 +279,12 @@ TIME_FORMAT = "%H:%M:%S" -class Boolean(enum.Flag): - """ - Enumeration class providing a convenient way to differentiate when a function may +class Boolean(Flag): + """Enumeration class providing a convenient way to differentiate when a function may return a 'truthy' or 'falsy' value, such as 1, "", or 0. + + Used in `DataSet.value_changed` + """ TRUE = True @@ -273,10 +293,8 @@ class Boolean(enum.Flag): """Represents the boolean value False.""" -class ValidateMode(enum.Enum): - """ - Enumeration class representing different validation modes. - """ +class ValidateMode(Enum): + """Enumeration class representing different validation modes.""" STRICT = "strict" """Strict prevents invalid values from being entered.""" @@ -287,26 +305,44 @@ class ValidateMode(enum.Enum): """Validation is turned off, and no checks or restrictions are applied.""" -class ValidateRule(enum.Enum): - """ - Mostly internal class. May need to override if creating new custom ColumnClass. - """ +class ValidateRule(Enum): + """Collection of enums used `ValidateResponse`.""" REQUIRED = "required" + """Required field. Either set as 'NOTNULL' in database, or later in ColumnClass""" PYTHON_TYPE = "python_type" + """After casting, value is still not correct python type.""" PRECISION = "precision" + """Value has too many numerical places""" MIN_VALUE = "min_value" + """Value less than set mininum value""" MAX_VALUE = "max_value" + """Value greater than set maximum value""" MIN_LENGTH = "min_length" + """Value's length is less than minimum length""" MAX_LENGTH = "max_length" + """Value's length is greater than than maximum length""" CUSTOM = "custom" + r"""Special enum to be used when returning a ValidateResponse in your own + `custom_validate_fn'. + Example: + ```python + import re + def is_valid_email(email): + valid_email = re.match(r".+\@.+\..+", email) is not None + if not valid_email: + return ss.ValidateResponse( + ss.ValidateRule.CUSTOM, email, " is not a valid email" + ) + return ss.ValidateResponse() + ``` + """ -@dc.dataclass +@dataclass class ValidateResponse: - """ - Represents the response returned by `ColumnClass.validate` method. + """Represents the response returned by `Column.validate` method. Attributes: exception: Indicates validation failure, if any. None for valid responses. @@ -314,8 +350,8 @@ class ValidateResponse: rule: The specific `ValidateRule` that caused the exception, if applicable. - Example of how to create a response from an exception: - + Example: + How how to create a ok popup from an exception: ```python response = frm[data_key].column_info[col].validate(value) if response.exception: @@ -337,18 +373,33 @@ class ValidateResponse: rule: str = None -@dc.dataclass +@dataclass class _PrevSearch: - """Internal Class. Keeps track of previous search to cycle through results""" + """Internal Class. Keeps track of previous search to cycle through results.""" search_string: str = None column: str = None - pks: List[int] = dc.field(default_factory=list) + pks: List[int] = field_(default_factory=list) class CellFormatFn: + """Collection of functions to pre-format values before populating `sg.Table` values. + + Each function must accept and return 1 value. Additional arguments can be filled in + via a lambda. + + Example: + ```python + fn = lambda x: ss.CellFormatFn.decimal_places(x, 2) + frm[data_key].column_info[col].cell_format_fn = fn + ``` + """ + @staticmethod - def bool_to_checkbox(val): + def bool_to_checkbox( + val: Union[str, int, bool] + ) -> Union[themepack.checkbox_true, themepack.checkbox_false]: + """Converts a boolean value to a themepack.checkbox_true/false.""" return ( themepack.checkbox_true if checkbox_to_bool(val) @@ -356,7 +407,8 @@ def bool_to_checkbox(val): ) @staticmethod - def decimal_places(val, decimal_places): + def decimal_places(val: Union[int, float, Decimal], decimal_places: int): + """Format the value to specified decimal places using the system locale.""" format_string = f"%.{decimal_places}f" if val not in EMPTY: return locale.format_string(format_string, val) @@ -368,31 +420,29 @@ def decimal_places(val, decimal_places): # ------- # TODO: Combine _TableRow and _ElementRow into one class for simplicity class _TableRow(list): - """Convenience class used by Tables to associate a primary key with a row of data. Note: This is typically not used by the end user. """ - def __init__(self, pk: int, *args, **kwargs): + def __init__(self, pk: int, *args, **kwargs) -> None: self.pk = pk super().__init__(*args, **kwargs) - def __str__(self): + def __str__(self) -> str: return str(self[:]) - def __int__(self): + def __int__(self) -> int: if isinstance(self.pk, np.int64): return self.pk.tolist() return self.pk - def __repr__(self): + def __repr__(self) -> str: # Add some extra information that could be useful for debugging return f"_TableRow(pk={self.pk}): {super().__repr__()}" class _ElementRow: - """Convenience class used by listboxes and comboboxes to associate a primary key with a row of data. @@ -403,13 +453,13 @@ def __init__(self, pk: int, val: Union[str, int]) -> None: self.pk = pk self.val = val - def __repr__(self): + def __repr__(self) -> str: return str(self.val) - def __str__(self): + def __str__(self) -> str: return str(self.val) - def __int__(self): + def __int__(self) -> int: if isinstance(self.pk, np.int64): return self.pk.tolist() return self.pk @@ -432,9 +482,10 @@ def get_instance(self): return self -@dc.dataclass +@dataclass class Relationship: - """ + """Information from Foreign-Keys + Args: join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. child_table: The table name of the fk table @@ -443,10 +494,7 @@ class Relationship: pk_column: The parent table's primary key column update_cascade: True if the child's fk_column ON UPDATE rule is 'CASCADE' delete_cascade: True if the child's fk_column ON DELETE rule is 'CASCADE' - driver: A `SQLDriver` instance - - Returns: - None + driver: A `SQLDriver` instance. """ join_type: str @@ -466,12 +514,12 @@ def on_update_cascade(self): def on_delete_cascade(self): return bool(self.delete_cascade and self.driver.delete_cascade) - def __str__(self): + def __str__(self) -> str: """Return a join clause when cast to a string.""" return self.driver.relationship_to_join_clause(self) -@dc.dataclass +@dataclass class RelationshipStore(list): """Used to track primary/foreign key relationships in the database. @@ -544,7 +592,7 @@ def get_parent(self, table: str) -> Union[str, None]: return r.parent_table return None - def parent_virtual(self, table: str, frm: Form) -> Union[bool, None]: + def is_parent_virtual(self, table: str, frm: Form) -> Union[bool, None]: """Return True if current row of parent table is virtual. Args: @@ -617,9 +665,8 @@ def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, st } -@dc.dataclass +@dataclass class ElementMap: - """Map a PySimpleGUI element to a specific `DataSet` column. This is what makes the GUI automatically update to the contents of the database. @@ -645,22 +692,21 @@ class ElementMap: where_column: str = None where_value: str = None - def __post_init__(self): + def __post_init__(self) -> None: self.table = self.dataset.table def __getitem__(self, key): return self.__dict__[key] - def __setitem__(self, key, value): + def __setitem__(self, key, value) -> None: self.__dict__[key] = value - def __contains__(self, item): + def __contains__(self, item) -> bool: return item in self.__dict__ -@dc.dataclass(eq=False) +@dataclass(eq=False) class DataSet: - """`DataSet` objects are used for an internal representation of database tables. `DataSet` instances are added by the following `Form` methods: `Form.add_dataset`, @@ -687,42 +733,65 @@ class DataSet: appropriate WHERE clause will be generated. False will display all records in the table. prompt_save: (optional) Default: Mode set in `Form`. Prompt to save changes when - dirty records are present. There are two modes available, (if pysimplesql is - imported as `ss`) use: `ss.PROMPT_MODE` to prompt to save when unsaved - changes are present. `ss.AUTOSAVE_MODE` to automatically save when unsaved - changes are present. + dirty records are present. There are two modes available, `PROMPT_MODE` + to prompt to save when unsaved changes are present. `AUTOSAVE_MODE` to + automatically save when unsaved changes are present. save_quiet: (optional) Default: Set in `Form`. True to skip info popup on save. Error popups will still be shown. duplicate_children: (optional) Default: Set in `Form`. If record has children, prompt user to choose to duplicate current record, or both. - validate_mode: `ss.ValidateMode.STRICT` to prevent invalid values from being - entered. `ss.ValidateMode.RELAXED` allows invalid input, but ensures + validate_mode: `ValidateMode.STRICT` to prevent invalid values from being + entered. `ValidateMode.RELAXED` allows invalid input, but ensures validation occurs before saving to the database. + + Attributes: + [pysimplesql.pysimplesql.DataSet.key] + + Attributes: + key: TODO """ instances: ClassVar[List[DataSet]] = [] # Track our own instances - data_key: dc.InitVar[str] - frm_reference: dc.InitVar[Form] + data_key: InitVar[str] + frm_reference: InitVar[Form] table: str pk_column: str description_column: str + """TODO""" query: Optional[str] = "" order_clause: Optional[str] = "" filtered: bool = True - prompt_save: dc.InitVar[PROMPT_SAVE_MODES] = None + prompt_save: InitVar[PROMPT_SAVE_MODES] = None save_quiet: bool = None duplicate_children: bool = None validate_mode: ValidateMode = None - def __post_init__(self, data_key, frm_reference, prompt_save): + # non-init, instance-vars, here for documentation + key: str = field_(init=False) + """Short for 'data_key'""" + frm: Form = field_(init=False) + """TODO""" + driver: Driver = field_(init=False) + """TODO""" + relationships: RelationshipStore = field_(init=False) + """TODO""" + rows: pd.DataFrame = field_(init=False) + """TODO""" + join_clause: str = field_(init=False) + """TODO""" + where_clause: str = field_(init=False) + """TODO""" + search_order: List[str] = field_(init=False) + """TODO""" + + def __post_init__(self, data_key, frm_reference, prompt_save) -> None: DataSet.instances.append(self) self.key: str = data_key self.frm = frm_reference self.driver = self.frm.driver self.relationships = self.driver.relationships - self.rows: pd.DataFrame = Result.set() self._current_index: int = 0 self.column_info: ColumnInfo = None @@ -731,8 +800,8 @@ def __post_init__(self, data_key, frm_reference, prompt_save): # initally empty clauses self.join_clause: str = "" self.where_clause: str = "" # In addition to generated where clause! - self.search_order: List[str] = [] + self._prev_search: _PrevSearch = _PrevSearch() self._search_string: tk.StringVar = None @@ -793,7 +862,7 @@ def search_string(self): return None @search_string.setter - def search_string(self, val: str): + def search_string(self, val: str) -> None: if self._search_string is not None: self._search_string.set(val) @@ -804,7 +873,7 @@ def current_index(self): @current_index.setter # Keeps the current_index in bounds - def current_index(self, val: int): + def current_index(self, val: int) -> None: if val > self.row_count - 1: self._current_index = self.row_count - 1 elif val < 0: @@ -851,10 +920,8 @@ def set_prompt_save(self, mode: int) -> None: """Set the prompt to save action when navigating records. Args: - mode: a constant value. If pysimplesql is imported as `ss`, use: - - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are - present. + mode: Use `PROMPT_MODE` to prompt to save when unsaved changes are present. + `AUTOSAVE_MODE` to automatically save when unsaved changes are present. Returns: None @@ -911,7 +978,7 @@ def set_callback( Args: callback: The name of the callback, from the list above fctn: The function to call. Note, the function must take at least two - parameters, a `Form` instance, and a `PySimpleGUI.Window` instance, with + parameters, a `Form` instance, and a `sg.Window` instance, with an optional `DataSet.key`, and return True or False Returns: @@ -951,7 +1018,7 @@ def _invoke_callback(self, callback, *args): return callback(*args[:expected_args]) raise ValueError("Unexpected number of parameters in the callback function") - def set_transform(self, fn: callable) -> None: + def set_transform(self, fn: Callable) -> None: """Set a transform on the data for this `DataSet`. Here you can set custom a custom transform to both decode data from the @@ -966,7 +1033,7 @@ def set_transform(self, fn: callable) -> None: dictionary of the row data), and an encode parameter (1 to encode, 0 to decode - see constants `TFORM_ENCODE` and `TFORM_DECODE`). Note that this transform works on one row at a time. See the example - `journal_with_data_manipulation.py` for a usage example. + 'journal_with_data_manipulation.py' for a usage example. Returns: None @@ -1070,7 +1137,7 @@ def set_description_column(self, column: str) -> None: """ self.description_column = column - def records_changed(self, column: str = None, recursive=True) -> bool: + def records_changed(self, column: str = None, recursive: bool = True) -> bool: """Checks if records have been changed. This is done by comparing PySimpleGUI control values with the stored `DataSet` @@ -1214,23 +1281,23 @@ def value_changed( def prompt_save( self, update_elements: bool = True - ) -> Union[PROMPT_SAVE_PROCEED, PROMPT_SAVE_DISCARDED, PROMPT_SAVE_NONE]: + ) -> Union[Type[PromptSaveReturn], SAVE_FAIL]: """Prompts the user, asking if they want to save when changes are detected. This is called when the current record is about to change. Args: update_elements: (optional) Passed to `Form.save_records()` -> - `Form.save_records_recursive()` to update_elements. Additionally used to - discard changes if user reply's 'No' to prompt. + `DataSet.save_record_recursive()` to update_elements. Additionally used + to discard changes if user reply's 'No' to prompt. Returns: - A prompt return value of one of the following: `PROMPT_PROCEED`, - `PROMPT_DISCARDED`, or `PROMPT_NONE`. + A prompt return value of one of the following: `PromptSaveReturn.PROCEED`, + `PromptSaveReturn.DISCARDED`, or `PromptSaveReturn.NONE`. """ # Return False if there is nothing to check or _prompt_save is False if self.current_index is None or not self.row_count or not self._prompt_save: - return PROMPT_SAVE_NONE + return PromptSaveReturn.NONE # See if any rows are virtual vrows = len(self.virtual_pks) @@ -1256,7 +1323,7 @@ def prompt_save( # set all selectors back to previous position self.frm.update_selectors() return SAVE_FAIL - return PROMPT_SAVE_PROCEED + return PromptSaveReturn.PROCEED # if no self.purge_virtual() self.restore_current_row() @@ -1266,9 +1333,9 @@ def prompt_save( if vrows and update_elements: self.frm.update_elements(self.key) - return PROMPT_SAVE_DISCARDED + return PromptSaveReturn.DISCARDED # if no changes - return PROMPT_SAVE_NONE + return PromptSaveReturn.NONE def requery( self, @@ -1310,7 +1377,7 @@ def requery( parent_table = self.relationships.get_parent(self.table) if parent_table and ( not len(self.frm[parent_table].rows.index) - or self.relationships.parent_virtual(self.table, self.frm) + or self.relationships.is_parent_virtual(self.table, self.frm) ): # purge rows self.rows = Result.set(pd.DataFrame(columns=self.column_info.names)) @@ -1967,7 +2034,7 @@ def insert_record( if ( parent_table and not len(self.frm[parent_table].rows) - or self.relationships.parent_virtual(self.table, self.frm) + or self.relationships.is_parent_virtual(self.table, self.frm) ): logger.debug(f"{parent_table=} is empty or current row is virtual") return @@ -2304,7 +2371,7 @@ def save_record( def save_record_recursive( self, results: SaveResultsDict, - display_message=False, + display_message: bool = False, check_prompt_save: bool = False, update_elements: bool = True, ) -> SaveResultsDict: @@ -2312,12 +2379,13 @@ def save_record_recursive( tables. Args: - results: Used in Form.save_records to collect DataSet.save_record returns. - Pass an empty dict to get list of {table : result} - display_message: Passed to DataSet.save_record. Displays a message that + results: Used in `Form.save_records` to collect `DataSet.save_record` + returns. Pass an empty dict to get list of {table : result} + display_message: Passed to `DataSet.save_record`. Displays a message that updates were saved successfully, otherwise is silent on success. - check_prompt_save: Used when called from Form.prompt_save. Updates elements - without saving if individual `DataSet._prompt_save()` is False. + check_prompt_save: Used when called from `Form.prompt_save`. Updates + elements without saving if individual `DataSet._prompt_save()` is False. + update_elements: Update GUI elements, additionally passed to dependents. Returns: dict of {table : results} @@ -2334,7 +2402,7 @@ def save_record_recursive( if check_prompt_save and self._prompt_save is False: if update_elements: self.frm.update_elements(self.key) - results[self.table] = PROMPT_SAVE_NONE + results[self.table] = PromptSaveReturn.NONE return results # otherwise, proceed result = self.save_record( @@ -2606,10 +2674,10 @@ def row_count(self) -> int: def current_row_has_backup(self) -> bool: """Returns True if the current_row has a backup row, and False otherwise. - A pandas Series object is stored rows.attrs["row_backup"] before a CellEdit or - SyncSelector operation is initiated, so that it can be compared in - `Dataset.records_changed` and `Dataset.save_record` or used to restore if - changes are discarded during a `prompt_save` operations. + A pandas Series object is stored rows.attrs["row_backup"] before a 'CellEdit' or + 'LiveUpdate' operation is initiated, so that it can be compared in + `DataSet.records_changed` and `DataSet.save_record` or used to restore if + changes are discarded during a `DataSet.prompt_save` operations. Returns: True if a backup row is present that matches, and False otherwise. @@ -2654,7 +2722,7 @@ def get_original_current_row(self) -> pd.Series: return None def backup_current_row(self) -> None: - """Creates a backup copy of the current row in `DataSet.rows`""" + """Creates a backup copy of the current row in `DataSet.rows`.""" if not self.current_row_has_backup: self.rows.attrs["row_backup"] = self.get_current_row().copy() @@ -2785,12 +2853,14 @@ def column_likely_in_selector(self, column: str) -> bool: ) def combobox_values( - self, column_name, insert_placeholder: bool = True + self, column_name: str, insert_placeholder: bool = True ) -> Union[List[_ElementRow], None]: """Returns the values to use in a sg.Combobox as a list of _ElementRow objects. Args: column_name: The name of the table column for which to get the values. + insert_placeholder: If True, inserts `Languagepack.combo_placeholder` as + first value. Returns: A list of _ElementRow objects representing the possible values for the @@ -2840,10 +2910,16 @@ def get_related_table_for_column(self, column: str) -> str: return rel.parent_table return self.table # None could be found, return our own table instead - def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): + def map_fk_descriptions( + self, rows: pd.DataFrame, columns: list[str] = None + ) -> pd.DataFrame: """Maps foreign key descriptions to the specified columns in the given - DataFrame. If passing in a DataSet rows, please pass in a copy: - frm[data_key].rows.copy() + DataFrame. + + + Note: + If passing in `DataSet.rows`, please pass in a copy, eg: + ```frm[data_key].rows.copy()``` Args: rows: The DataFrame containing the data to be processed. @@ -2853,7 +2929,7 @@ def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): Returns: The processed DataFrame with foreign key descriptions mapped to the - specified columns. + specified columns. """ if columns is None: columns = rows.columns @@ -2892,7 +2968,7 @@ def map_fk_descriptions(self, rows: pd.DataFrame, columns: list[str] = None): def quick_editor( self, - pk_update_funct: callable = None, + pk_update_funct: Callable = None, funct_param: any = None, skip_prompt_save: bool = False, column_attributes: dict = None, @@ -2906,7 +2982,7 @@ def quick_editor( Args: pk_update_funct: (optional) A function to call to determine the pk to select by default when the quick editor loads. - funct_param: (optional) A parameter to pass to the `pk_update_funct` + funct_param: (optional) A parameter to pass to the 'pk_update_funct' skip_prompt_save: (Optional) True to skip prompting to save dirty records column_attributes: (Optional) Dictionary specifying column attributes for `DataSet.column_info`. The dictionary should be in the form @@ -3004,7 +3080,9 @@ def quick_editor( layout.append( [ sg.StatusBar( - " " * 100, key="info:quick_editor", metadata={"type": TYPE_INFO} + " " * 100, + key="info:quick_editor", + metadata={"type": ElementType.INFO}, ) ], ) @@ -3060,11 +3138,12 @@ def add_simple_transform(self, transforms: SimpleTransformsDict) -> None: dictionary. Example: - ------- - {'entry_date' : { - 'decode' : lambda row,col: datetime.utcfromtimestamp(int(row[col])).strftime('%m/%d/%y'), # fmt: skip - 'encode' : lambda row,col: datetime.strptime(row[col], '%m/%d/%y').replace(tzinfo=timezone.utc).timestamp(), # fmt: skip - }} + ```python + {'entry_date' : { + 'decode' : lambda row,col: datetime.utcfromtimestamp(int(row[col])).strftime('%m/%d/%y'), + 'encode' : lambda row,col: datetime.strptime(row[col], '%m/%d/%y').replace(tzinfo=timezone.utc).timestamp(), + }} + ``` Args: transforms: A dict of dicts containing either 'encode' or 'decode' along @@ -3090,7 +3169,7 @@ def purge_virtual(self) -> None: self.rows = self.rows.drop(index=virtual_rows.index) self.rows.attrs["virtual"] = [] - def sort_by_column(self, column: str, table: str, reverse=False) -> None: + def sort_by_column(self, column: str, table: str, reverse: bool = False) -> None: """Sort the DataFrame by column. Using the mapped relationships of the database, foreign keys will automatically sort based on the parent table's description column, rather than the foreign key number. @@ -3151,7 +3230,7 @@ def sort_by_column(self, column: str, table: str, reverse=False) -> None: if tmp_column is not None: self.rows = self.rows.drop(columns=tmp_column, errors="ignore") - def sort_by_index(self, index: int, table: str, reverse=False): + def sort_by_index(self, index: int, table: str, reverse: bool = False) -> None: """Sort the self.rows DataFrame by column index Using the mapped relationships of the database, foreign keys will automatically sort based on the parent table's description column, rather than the foreign key number. @@ -3204,9 +3283,8 @@ def sort(self, table: str, update_elements: bool = True, sort_order=None) -> Non `DataSet.sort_by_column()` update_elements: Update associated selectors and navigation buttons, and table header sort marker. - sort_order: Passed to `Dataset.update_headings`. A SORT_* constant - (SORT_NONE, SORT_ASC, SORT_DESC). Note that the update_elements - parameter must = True to use this parameter. + sort_order: A SORT_* constant (SORT_NONE, SORT_ASC, SORT_DESC). + Note that the update_elements parameter must = True to use Returns: None @@ -3238,7 +3316,7 @@ def sort_cycle(self, column: str, table: str, update_elements: bool = True) -> i Args: column: The column name to cycle the sort on table: The table that the column belongs to - update_elements: Passed to `Dataset.sort` to update update associated + update_elements: Passed to `DataSet.sort` to update update associated selectors and navigation buttons, and table header sort marker. Returns: @@ -3258,7 +3336,7 @@ def sort_cycle(self, column: str, table: str, update_elements: bool = True) -> i self.sort(table, update_elements=update_elements, sort_order=SORT_NONE) return SORT_NONE - def _update_headings(self, column, sort_order): + def _update_headings(self, column, sort_order) -> None: for e in self.selector: element = e["element"] if ( @@ -3320,7 +3398,6 @@ def validate_field( Returns: True if the field value is valid, False otherwise. """ - if column_name in self.column_info: # Validate the new value against the column's validation rules response = self.column_info[column_name].validate(new_value) @@ -3342,9 +3419,8 @@ def validate_field( return None -@dc.dataclass(eq=False) +@dataclass(eq=False) class Form: - """`Form` class. Maintains an internal version of the actual database @@ -3359,11 +3435,10 @@ class Form: set in the element's metadata select_first: (optional) Default:True. For each top-level parent, selects first row, populating children as well. - prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when dirty - records are present. Two modes available, (if pysimplesql is imported as - `ss`) use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are - present. - `ss.AUTOSAVE_MODE` to automatically save when unsaved changes are - present. + prompt_save: (optional) Default:PROMPT_MODE. Prompt to save changes when + dirty records are present. There are two modes available, `PROMPT_MODE` + to prompt to save when unsaved changes are present. `AUTOSAVE_MODE` to + automatically save when unsaved changes are present. save_quiet: (optional) Default:False. True to skip info popup on save. Error popups will still be shown. duplicate_children: (optional) Default:True. If record has children, prompt user @@ -3377,8 +3452,8 @@ class Form: will be immediately pushed to associated selectors. If False, changes will be pushed only after a save action. validate_mode: Passed to `DataSet` init to set validate mode. - `ss.ValidateMode.STRICT` to prevent invalid values from being entered. - `ss.ValidateMode.RELAXED` allows invalid input, but ensures validation + `ValidateMode.STRICT` to prevent invalid values from being entered. + `ValidateMode.RELAXED` allows invalid input, but ensures validation occurs before saving to the database. Returns: @@ -3388,14 +3463,14 @@ class Form: instances: ClassVar[List[Form]] = [] # Track our instances driver: SQLDriver - bind_window: dc.InitVar[sg.Window] = None + bind_window: InitVar[sg.Window] = None parent: Form = None # TODO: This doesn't seem to really be used filter: str = None - select_first: dc.InitVar[bool] = True - prompt_save: dc.InitVar[PROMPT_SAVE_MODES] = PROMPT_MODE + select_first: InitVar[bool] = True + prompt_save: InitVar[PROMPT_SAVE_MODES] = PROMPT_MODE save_quiet: bool = False duplicate_children: bool = True - description_column_names: List[str] = dc.field( + description_column_names: List[str] = field_( default_factory=lambda: ["description", "name", "title"] ) live_update: bool = False @@ -3406,7 +3481,7 @@ def __post_init__( bind_window, select_first, prompt_save, - ): + ) -> None: Form.instances.append(self) self.window: Optional[sg.Window] = 0 @@ -3440,7 +3515,7 @@ def __post_init__( self.bind(self.window) win_pb.close() - def __del__(self): + def __del__(self) -> None: self.close() # Override the [] operator to retrieve dataset by key @@ -3454,11 +3529,12 @@ def __getitem__(self, key: str) -> DataSet: f"proper permissions set, or any number of db configuration issues." ) from e - def close(self, reset_keygen: bool = True, close_driver: bool = True): + def close(self, reset_keygen: bool = True, close_driver: bool = True) -> None: """Safely close out the `Form`. Args: reset_keygen: True to reset the keygen for this `Form` + close_driver: True to also close associated `Form.driver` """ # First delete the dataset associated DataSet.purge_form(self, reset_keygen) @@ -3472,8 +3548,7 @@ def bind(self, win: sg.Window) -> None: """Bind the PySimpleGUI Window to the Form for the purpose of GUI element, event and relationship mapping. This can happen automatically on `Form` creation with the bind parameter and is not typically called by the end user. This function - literally just groups all the auto_* methods. See `Form.auto_add_tables()`, - `SQLDriver.auto_add_relationships()`, `Form.auto_map_elements()`, + literally just groups all the auto_* methods. `Form.auto_map_elements()`, `Form.auto_map_events()`. Args: @@ -3497,7 +3572,9 @@ def bind(self, win: sg.Window) -> None: logger.debug("Binding finished!") def execute(self, query: str) -> pd.DataFrame: - """Convenience function to pass along to `SQLDriver.execute()`. + """Execute a query. + + Convenience function to pass along to `SQLDriver.execute`. Args: query: The query to execute @@ -3508,7 +3585,9 @@ def execute(self, query: str) -> pd.DataFrame: return self.driver.execute(query) def commit(self) -> None: - """Convenience function to pass along to `SQLDriver.commit()`. + """Commit a transaction. + + Convenience function to pass along to `SQLDriver.commit()`. Returns: None @@ -3518,7 +3597,9 @@ def commit(self) -> None: def set_callback( self, callback_name: str, fctn: Callable[[Form, sg.Window], Union[None, bool]] ) -> None: - """Set `Form` callbacks. A runtime error will be raised if the callback is not + """Set `Form` callbacks. + + A runtime error will be raised if the callback is not supported. The following callbacks are supported: update_elements Called after elements are updated via `Form.update_elements()`. This allows for other GUI manipulation on each update of the GUI edit_enable Called before editing mode is @@ -3632,11 +3713,11 @@ def set_fk_column_cascade( def auto_add_datasets(self) -> None: """Automatically add `DataSet` objects from the database by looping through the - tables available and creating a `DataSet` object for each. Each dataset key is - an optional prefix plus the name of the table. When you attach to a sqlite - database, PySimpleSQL isn't aware of what it contains until this command is run. + tables available and creating a `DataSet` object for each. Each dataset key by default + name of the table. + This is called automatically when a `Form ` is created. Note that - `Form.add_table()` can do this manually on a per-table basis. + `Form.add_dataset()` can do this manually on a per-table basis. Returns: None @@ -3729,9 +3810,9 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: make elements that conform to this standard, but this information will allow you to do this manually if needed. For individual fields, Element keys must be named 'Table.column'. Additionally, the metadata must contain a dict with the key of - 'type' set to `TYPE_RECORD`. For selectors, the key can be named whatever you - want, but the metadata must contain a dict with the key of 'type' set to - TPE_SELECTOR. + 'type' set to `ElementType.FIELD`. For selectors, the key can be named whatever + you want, but the metadata must contain a dict with the key of 'type' set to + `ElementType.SELECTOR`. Args: win: A PySimpleGUI Window @@ -3760,7 +3841,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: element.metadata["Form"] = self # Skip this element if it's an event - if element.metadata["type"] == TYPE_EVENT: + if element.metadata["type"] == ElementType.EVENT: continue if element.metadata["Form"] != self: @@ -3770,7 +3851,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: continue # Map Record Element - if element.metadata["type"] == TYPE_RECORD: + if element.metadata["type"] == ElementType.FIELD: # Does this record imply a where clause (indicated by ?) # If so, we can strip out the information we need data_key = element.metadata["data_key"] @@ -3819,7 +3900,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: element.add_validate(self[table], col) # Map Selector Element - elif element.metadata["type"] == TYPE_SELECTOR: + elif element.metadata["type"] == ElementType.SELECTOR: k = element.metadata["table"] if k is None: continue @@ -3850,7 +3931,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: # 2 Run TableBuilder._update_headings() with the: # Table element, sort_column, sort_reverse # 3 Run update_elements() to see the changes - table_builder.enable_heading_function( + table_builder._enable_heading_function( element, _HeadingCallback(self, data_key), ) @@ -3858,7 +3939,7 @@ def auto_map_elements(self, win: sg.Window, keys: List[str] = None) -> None: else: logger.debug(f"Can not add selector {element!s}") - elif element.metadata["type"] == TYPE_INFO: + elif element.metadata["type"] == ElementType.INFO: self.add_info_element(element) def set_element_clauses( @@ -3952,7 +4033,7 @@ def auto_map_events(self, win: sg.Window) -> None: continue if element.metadata["Form"] != self: continue - if element.metadata["type"] == TYPE_EVENT: + if element.metadata["type"] == ElementType.EVENT: event_type = element.metadata["event_type"] table = element.metadata["table"] column = element.metadata["column"] @@ -3961,36 +4042,36 @@ def auto_map_events(self, win: sg.Window) -> None: data_key = table data_key = data_key if data_key in self.datasets else None - if event_type == EVENT_FIRST: + if event_type == EventType.FIRST: if data_key: funct = self[data_key].first - elif event_type == EVENT_PREVIOUS: + elif event_type == EventType.PREVIOUS: if data_key: funct = self[data_key].previous - elif event_type == EVENT_NEXT: + elif event_type == EventType.NEXT: if data_key: funct = self[data_key].next - elif event_type == EVENT_LAST: + elif event_type == EventType.LAST: if data_key: funct = self[data_key].last - elif event_type == EVENT_SAVE: + elif event_type == EventType.SAVE: if data_key: funct = self[data_key].save_record - elif event_type == EVENT_INSERT: + elif event_type == EventType.INSERT: if data_key: funct = self[data_key].insert_record - elif event_type == EVENT_DELETE: + elif event_type == EventType.DELETE: if data_key: funct = self[data_key].delete_record - elif event_type == EVENT_DUPLICATE: + elif event_type == EventType.DUPLICATE: if data_key: funct = self[data_key].duplicate_record - elif event_type == EVENT_EDIT_PROTECT_DB: + elif event_type == EventType.EDIT_PROTECT_DB: self.edit_protect() # Enable it! funct = self.edit_protect - elif event_type == EVENT_SAVE_DB: + elif event_type == EventType.SAVE_DB: funct = self.save_records - elif event_type == EVENT_SEARCH: + elif event_type == EventType.SEARCH: # Build the search box name search_element, command = key.split(":") search_box = f"{search_element}:search_input" @@ -4003,8 +4084,7 @@ def auto_map_events(self, win: sg.Window) -> None: ) # bind dataset self.window[search_box].bind_dataset(self[data_key]) - # elif event_type==EVENT_SEARCH_DB: - elif event_type == EVENT_QUICK_EDIT: + elif event_type == EventType.QUICK_EDIT: quick_editor_kwargs = {} if "quick_editor_kwargs" in element.metadata: quick_editor_kwargs = element.metadata["quick_editor_kwargs"] @@ -4016,7 +4096,7 @@ def auto_map_events(self, win: sg.Window) -> None: column, **quick_editor_kwargs if quick_editor_kwargs else {}, ) - elif event_type == EVENT_FUNCTION: + elif event_type == EventType.FUNCTION: funct = function else: logger.debug(f"Unsupported event_type: {event_type}") @@ -4059,14 +4139,14 @@ def get_edit_protect(self) -> bool: """ return self._edit_protect - def prompt_save(self) -> PromptSaveValue: + def prompt_save(self) -> Type[PromptSaveReturn]: """Prompt to save if any GUI changes are found the affect any table on this form. The helps prevent data entry loss when performing an action that changes the current record of a `DataSet`. Returns: - One of the prompt constant values: PROMPT_SAVE_PROCEED, - PROMPT_SAVE_DISCARDED, PROMPT_SAVE_NONE + One of the prompt constant values: PromptSaveReturn.PROCEED, + PromptSaveReturn.DISCARDED, PromptSaveReturn.NONE """ user_prompted = False # Has the user been prompted yet? for data_key in self.datasets: @@ -4090,20 +4170,19 @@ def prompt_save(self) -> PromptSaveValue: self[data_key_].restore_current_row() self.update_elements() # We did have a change, regardless if the user chose not to save - return PROMPT_SAVE_DISCARDED + return PromptSaveReturn.DISCARDED break if user_prompted: self.save_records(check_prompt_save=True) - return PROMPT_SAVE_PROCEED if user_prompted else PROMPT_SAVE_NONE + return PromptSaveReturn.PROCEED if user_prompted else PromptSaveReturn.NONE def set_prompt_save(self, mode: int) -> None: """Set the prompt to save action when navigating records for all `DataSet` objects associated with this `Form`. Args: - mode: a constant value. If pysimplesql is imported as `ss`, use: - `ss.PROMPT_MODE` to prompt to save when unsaved changes are present. - `ss.AUTOSAVE_MODE` to autosave when unsaved changes are present. + mode: Use `PROMPT_MODE` to prompt to save when unsaved changes are present. + `AUTOSAVE_MODE` to autosave when unsaved changes are present. Returns: None @@ -4124,7 +4203,7 @@ def set_force_save(self, force: bool = False) -> None: """ self.force_save = force - def set_live_update(self, enable: bool): + def set_live_update(self, enable: bool) -> None: """Toggle the immediate sync of field elements with other elements in Form. When live-update is enabled, changes in a field element are immediately @@ -4164,7 +4243,7 @@ def save_records( cascade_only: Save only tables with cascaded relationships. Default False. check_prompt_save: Passed to `DataSet.save_record_recursive` to check if individual `DataSet` has prompt_save enabled. Used when - `DataSet.save_records()` is called from `Form.prompt_save()`. + `Form.save_records()` is called from `Form.prompt_save()`. update_elements: (optional) Passed to `Form.save_record_recursive()` Returns: @@ -4342,7 +4421,9 @@ def update_actions(self, target_data_key: str = None) -> None: disable = bool( not self[parent].row_count or self._edit_protect - or self.relationships.parent_virtual(data_key, self) + or self.relationships.is_parent_virtual( + self[data_key].table, self + ) ) else: disable = self._edit_protect @@ -4550,6 +4631,8 @@ def update_selectors( target_data_key: (optional) dataset key to update elements for, otherwise updates elements for all datasets. omit_elements: A list of elements to omit updating + search_filter_only: Only update Table elements that have enabled + `TableBuilder.apply_search_filter`. Returns: None @@ -4622,13 +4705,13 @@ def update_selectors( logger.debug("update_elements: Table selector found...") # Populate entries apply_search_filter = False - try: + columns = None # default to all columns + + if "TableBuilder" in element.metadata: columns = element.metadata["TableBuilder"].columns apply_search_filter = element.metadata[ "TableBuilder" ].apply_search_filter - except KeyError: - columns = None # default to all columns # skip Tables that don't request search_filter if search_filter_only and not apply_search_filter: @@ -4687,7 +4770,6 @@ def requery_all( Returns: None """ - logger.info("Requerying all datasets") # first let datasets requery through cascade @@ -4816,7 +4898,6 @@ def purge_instance(cls, frm: Form) -> None: # These functions exist as utilities to the pysimplesql module # This is a dummy class for documenting utility functions class Utility: - """Utility functions are a collection of functions and classes that directly improve on aspects of the pysimplesql module. @@ -4869,22 +4950,21 @@ def update_elements(data_key: str = None, edit_protect_only: bool = False) -> No def bind(win: sg.Window) -> None: - """Bind ALL forms to window. Not to be confused with `Form.bind()`, which binds - specific forms to the window. + """Bind all `Form` instances to specific window. + + Not to be confused with `Form.bind()`, which binds specific form to the window. Args: win: The PySimpleGUI window to bind all forms to - - Returns: - None """ for i in Form.instances: i.bind(win) -def simple_transform(dataset: DataSet, row, encode): +def simple_transform(dataset: DataSet, row, encode) -> None: """Convenience transform function that makes it easier to add transforms to your - records.""" + records. + """ for col, function in dataset._simple_transform.items(): if col in row: msg = f"Transforming {col} from {row[col]}" @@ -4931,7 +5011,7 @@ def update_table_element( element.widget.bind("<>", element._treeview_selected) -def checkbox_to_bool(value): +def checkbox_to_bool(value: Union[str, int, bool]) -> bool: """Allows a variety of checkbox values to still return True or False. Args: @@ -4952,7 +5032,12 @@ def checkbox_to_bool(value): ] -def shake_widget(widget: Union[sg.Element, tk.Widget], pixels=4, delay_ms=50, repeat=2): +def shake_widget( + widget: Union[sg.Element, tk.Widget], + pixels: int = 4, + delay_ms: int = 50, + repeat: int = 2, +) -> None: """Shakes the given widget by modifying its padx attribute. Args: @@ -4991,13 +5076,12 @@ def shake_widget(widget: Union[sg.Element, tk.Widget], pixels=4, delay_ms=50, re class Popup: - """Popup helper class. Has popup functions for internal use. Stores last info popup as last_info """ - def __init__(self, window: sg.Window = None): + def __init__(self, window: sg.Window = None) -> None: """Create a new Popup instance :returns: None.""" self.window = window self.popup_info = None @@ -5014,7 +5098,7 @@ def __init__(self, window: sg.Window = None): "finalize": True, } - def ok(self, title, msg): + def ok(self, title, msg) -> None: """Internal use only. Creates sg.Window with LanguagePack OK button @@ -5072,7 +5156,7 @@ def yes_no(self, title, msg): def info( self, msg: str, display_message: bool = True, auto_close_seconds: int = None - ): + ) -> None: """Displays a popup message and saves the message to self.last_info, auto- closing after x seconds. The title of the popup window is defined in lang.info_popup_title. @@ -5085,7 +5169,6 @@ def info( auto-closes. If not provided, it is obtained from themepack.popup_info_auto_close_seconds. """ - title = lang.info_popup_title if auto_close_seconds is None: auto_close_seconds = themepack.popup_info_auto_close_seconds @@ -5106,7 +5189,7 @@ def info( int(auto_close_seconds * 1000), self._auto_close ) - def _auto_close(self): + def _auto_close(self) -> None: """Use in a tk.after to automatically close the popup_info.""" if self.popup_info: self.popup_info.close() @@ -5116,10 +5199,10 @@ def update_info_element( self, message: str = None, auto_erase_seconds: int = None, - timeout=False, + timeout: bool = False, erase: bool = False, ) -> None: - """Update any mapped info elements: + """Update any mapped info elements. Args: message: Text message to update info elements with @@ -5162,11 +5245,11 @@ def update_info_element( class ProgressBar: - def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100): + def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100) -> None: """Creates a progress bar window with a message label and a progress bar. - The progress bar is updated by calling the `update` method to update the - progress in incremental steps until the `close` method is called. + The progress bar is updated by calling the `ProgressBar.update` method to update + the progress in incremental steps until the `ProgressBar.close` method is called Args: title: Title of the window @@ -5200,7 +5283,7 @@ def __init__(self, title: str, max_value: int = 100, hide_delay: int = 100): self.last_phrase_time = None self.phrase_index = 0 - def update(self, message: str, current_count: int): + def update(self, message: str, current_count: int) -> None: """Updates the progress bar with the current progress message and value. Args: @@ -5219,7 +5302,7 @@ def update(self, message: str, current_count: int): self.win["message"].update(message) self.win["bar"].update(current_count=current_count) - def close(self): + def close(self) -> None: """Closes the progress bar window. Returns: @@ -5228,7 +5311,7 @@ def close(self): if self.win is not None: self.win.close() - def _create_window(self): + def _create_window(self) -> None: self.win = sg.Window( self.title, layout=self.layout, @@ -5240,11 +5323,11 @@ def _create_window(self): class ProgressAnimate: - def __init__(self, title: str, config: dict = None): + def __init__(self, title: str, config: dict = None) -> None: """Creates an animated progress bar with a message label. The progress bar will animate indefinitely, until the process passed in to the - `run` method finishes. + `ProgressAnimate.run` method finishes. The config for the animated progress bar contains oscillators for the bar divider and colors, a list of phrases to be displayed, and the number of seconds @@ -5333,18 +5416,19 @@ def __init__(self, title: str, config: dict = None): self.phrase_index = 0 self.completed = asyncio.Event() - def run(self, fn: callable, *args, **kwargs): + def run(self, fn: Callable, *args, **kwargs): """Runs the function in a separate co-routine, while animating the progress bar - in another.""" + in another. + """ if not callable(fn): raise ValueError("fn must be a callable") return asyncio.run(self._dispatch(fn, *args, **kwargs)) - def close(self): + def close(self) -> None: self.win = None - async def _gui(self): + async def _gui(self) -> None: if self.win is None: self.win = sg.Window( self.title, @@ -5362,7 +5446,7 @@ async def _gui(self): await asyncio.sleep(0.05) self.win.close() - async def run_process(self, fn: callable, *args, **kwargs): + async def run_process(self, fn: Callable, *args, **kwargs): loop = asyncio.get_running_loop() try: return await loop.run_in_executor( @@ -5374,14 +5458,14 @@ async def run_process(self, fn: callable, *args, **kwargs): finally: self.completed.set() - async def _dispatch(self, fn: callable, *args, **kwargs): + async def _dispatch(self, fn: Callable, *args, **kwargs): # Dispatch to the multiple asyncio co-processes gui_task = asyncio.create_task(self._gui()) result = await self.run_process(fn, *args, **kwargs) await gui_task return result - def _animate(self, config: dict = None): + def _animate(self, config: dict = None) -> None: def _oscillate_params(oscillator): return ( oscillator["value_start"], @@ -5433,7 +5517,6 @@ def _animated_message(self, phrases: list, phrase_delay: float): class KeyGen: - """The keygen system provides a mechanism to generate unique keys for use as PySimpleGUI element keys. @@ -5444,7 +5527,7 @@ class KeyGen: automatically, see `keygen` for info. """ - def __init__(self, separator="!"): + def __init__(self, separator: str = "!") -> None: """Create a new KeyGen instance. Args: @@ -5527,21 +5610,34 @@ def reset_from_form(self, frm: Form) -> None: class LazyTable(sg.Table): - """The LazyTable is a subclass of sg.Table for improved performance by loading rows lazily during scroll events. Updating a sg.Table is generally fast, but with large DataSets that contain thousands of rows, there may be some noticeable lag. LazyTable overcomes this by only inserting a slice of rows during an `update()`. - To use, simply replace `sg.Table` with `ss.LazyTable` as the `element` argument in a - selector() function call in your layout. + To use, simply replace `sg.Table` with `LazyTable` as the 'element' argument in a + `selector()` function call in your layout. Expects values in the form of [_TableRow(pk, values)], and only becomes active after - a update(values=, selected_rows=[int]) call. Please note that LazyTable does not - support the `sg.Table` `row_colors` argument. + a update(values=, selected_rows=[int]) call. + + + Note: + LazyTable does not support the `sg.Table.row_colors` argument. """ - def __init__(self, *args, lazy_loading=False, **kwargs): + def __init__(self, *args, lazy_loading: bool = False, **kwargs) -> None: + """Initilize LazyTable. + + Args: + *args: `sg.Table` specific args + lazy_loading: True to enable lazy loading + **kwargs: Additional `sg.Table` specific kwargs. + + + Returns: + None + """ # remove LazyTable only self.headings_justification = kwargs.pop("headings_justification", None) cols_justification = kwargs.pop("cols_justification", None) @@ -5565,7 +5661,7 @@ def __init__(self, *args, lazy_loading=False, **kwargs): self._bg = None self._fg = None - def __setattr__(self, name, value): + def __setattr__(self, name: str, value) -> None: if name == "SelectedRows": # Handle PySimpleGui attempts to set our SelectedRows property return @@ -5606,11 +5702,11 @@ def SelectedRows(self): # noqa N802 def update( self, values=None, - num_rows=None, + num_rows: Optional[int] = None, visible=None, select_rows=None, alternating_row_color=None, - ): + ) -> None: # check if we shouldn't be doing this update # PySimpleGUI version support (PyPi version doesn't support quick_check) kwargs = {} @@ -5704,7 +5800,7 @@ def update( # and make sure its visible self.widget.see(row_iid) - def _handle_scroll(self, x0, x1): + def _handle_scroll(self, x0, x1) -> None: if float(x0) == 0.0 and self._start_index > 0: with self._lock: self._handle_start_scroll() @@ -5716,7 +5812,7 @@ def _handle_scroll(self, x0, x1): # else, set the scroll self.vsb.set(x0, x1) - def _handle_start_scroll(self): + def _handle_start_scroll(self) -> None: # determine slice num_rows = min(self._start_index, self.insert_qty) new_start_index = max(0, self._start_index - num_rows) @@ -5742,7 +5838,7 @@ def _handle_start_scroll(self): row_iid = self.tree_ids[self.insert_qty + self.NumRows - 1] self.widget.see(row_iid) - def _handle_end_scroll(self): + def _handle_end_scroll(self) -> None: num_rows = len(self.values) # determine slice start_index = max(0, self._end_index) @@ -5781,7 +5877,7 @@ def _set_colors(self, iid, toggle_color): self.widget.tag_configure(iid, background=self._bg, foreground=self._fg) return toggle_color - def _handle_extra_kwargs(self): + def _handle_extra_kwargs(self) -> None: if self.headings_justification: for i, heading_id in enumerate(self.Widget["columns"]): self.Widget.heading( @@ -5797,7 +5893,7 @@ def _handle_extra_kwargs(self): class _StrictInput: - def strict_validate(self, value, action): + def strict_validate(self, value, action) -> bool: if hasattr(self, "active_placeholder"): active_placeholder = self.active_placeholder else: @@ -5811,7 +5907,7 @@ def strict_validate(self, value, action): return False return True - def add_validate(self, dataset: DataSet, column_name: str): + def add_validate(self, dataset: DataSet, column_name: str) -> None: self.dataset: DataSet = dataset self.column_name = column_name widget = self.widget if isinstance(self, sg.Input) else self @@ -5829,7 +5925,8 @@ class _TtkStrictInput(ttk.Entry, _StrictInput): class _PlaceholderText(ABC): """An abstract class for PySimpleGUI text-entry elements that allows for the display - of a placeholder text when the input is empty.""" + of a placeholder text when the input is empty. + """ # fmt: off _non_keys: ClassVar[List[str]] = {"Control_L","Control_R","Alt_L","Alt_R","Shift_L", @@ -5837,7 +5934,7 @@ class _PlaceholderText(ABC): "Left", "Right","Home","End","Page_Up","Page_Down","F1","F2","F3","F4", "F5","F6","F7","F8","F9","F10","F11","F12", "Delete"} # fmt: on - binds: dict = dc.field(default_factory=lambda: dict) + binds: dict = field_(default_factory=lambda: dict) placeholder_feature_enabled: bool = False normal_color: str = None normal_font: str = None @@ -5846,7 +5943,9 @@ class _PlaceholderText(ABC): placeholder_font: str = None active_placeholder: bool = False - def add_placeholder(self, placeholder: str, color: str = None, font: str = None): + def add_placeholder( + self, placeholder: str, color: str = None, font: str = None + ) -> None: """Adds a placeholder text to the element. The placeholder text is displayed in the element when the element is empty and @@ -5881,7 +5980,7 @@ def add_placeholder(self, placeholder: str, color: str = None, font: str = None) def _add_binds(self): pass - def update(self, *args, **kwargs): + def update(self, *args, **kwargs) -> None: """Updates the input widget with a new value and displays the placeholder text if the value is empty. @@ -5941,11 +6040,11 @@ def delete_placeholder(self): class _EnhancedInput(_PlaceholderText, sg.Input, _StrictInput): """An Input that allows for the display of a placeholder text when empty.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.binds = {} super().__init__(*args, **kwargs) - def _add_binds(self): + def _add_binds(self) -> None: widget = self.widget if self.binds: # remove any existing binds @@ -5962,18 +6061,18 @@ def on_key(event): self.delete_placeholder() return None - def on_key_release(event): + def on_key_release(event) -> None: if widget.get() in EMPTY: with contextlib.suppress(tk.TclError): self.insert_placeholder() widget.icursor(0) - def on_focusin(event): + def on_focusin(event) -> None: if self.active_placeholder: # Move cursor to the beginning if the field has a placeholder widget.icursor(0) - def on_focusout(event): + def on_focusout(event) -> None: if not widget.get(): self.insert_placeholder() @@ -5993,13 +6092,13 @@ def disable_placeholder_select(event): if not widget.get(): self.insert_placeholder() - def insert_placeholder(self): + def insert_placeholder(self) -> None: self.active_placeholder = True self.widget.delete(0, "end") self.widget.insert(0, self.placeholder_text) self.widget.config(fg=self.placeholder_color, font=self.placeholder_font) - def delete_placeholder(self): + def delete_placeholder(self) -> None: self.active_placeholder = False self.widget.delete(0, "end") self.widget.config(fg=self.normal_color, font=self.normal_font) @@ -6007,24 +6106,25 @@ def delete_placeholder(self): class _EnhancedMultiline(_PlaceholderText, sg.Multiline): """A Multiline that allows for the display of a placeholder text when focus-out - empty.""" + empty. + """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.binds = {} super().__init__(*args, **kwargs) - def _add_binds(self): + def _add_binds(self) -> None: widget = self.widget if self.binds: for event, bind in self.binds.items(): self.widget.unbind(event, bind) self.binds = {} - def on_focusin(event): + def on_focusin(event) -> None: if self.active_placeholder: self.delete_placeholder() - def on_focusout(event): + def on_focusout(event) -> None: if not widget.get("1.0", "end-1c").strip(): self.insert_placeholder() @@ -6034,19 +6134,19 @@ def on_focusout(event): self.binds[""] = widget.bind("", on_focusin, "+") self.binds[""] = widget.bind("", on_focusout, "+") - def insert_placeholder(self): + def insert_placeholder(self) -> None: self.widget.insert("1.0", self.placeholder_text) self.widget.config(fg=self.placeholder_color, font=self.placeholder_font) self.active_placeholder = True - def delete_placeholder(self): + def delete_placeholder(self) -> None: self.widget.delete("1.0", "end") self.widget.config(fg=self.normal_color, font=self.normal_font) self.active_placeholder = False class _SearchInput(_EnhancedInput): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: self.dataset = None self.search_string = None # Track the StringVar super().__init__(*args, **kwargs) @@ -6054,10 +6154,10 @@ def __init__(self, *args, **kwargs): self.search_non_keys.remove("BackSpace") self.search_non_keys.remove("Delete") - def _add_binds(self): + def _add_binds(self) -> None: super()._add_binds() # Call the parent method to maintain existing binds - def on_key_release(event): + def on_key_release(event) -> None: # update selectors after each key-release if ( event.keysym not in self.search_non_keys @@ -6072,14 +6172,14 @@ def on_key_release(event): "", on_key_release, "+" ) - def bind_dataset(self, dataset): + def bind_dataset(self, dataset) -> None: self.dataset = dataset self.search_string = dataset._search_string if self.search_string is None: self.search_string = dataset._search_string = tk.StringVar() self.search_string.trace_add("write", self._on_search_string_change) - def _on_search_string_change(self, *args): + def _on_search_string_change(self, *args) -> None: if ( not self.active_placeholder and self.get() != self.search_string.get() @@ -6090,13 +6190,13 @@ def _on_search_string_change(self, *args): class _AutoCompleteLogic: - _completion_list: List[Union[str, _ElementRow]] = dc.field(default_factory=list) - _hits: List[int] = dc.field(default_factory=list) + _completion_list: List[Union[str, _ElementRow]] = field_(default_factory=list) + _hits: List[int] = field_(default_factory=list) _hit_index: int = 0 position: int = 0 finalized: bool = False - def _autocomplete_combo(self, completion_list, delta=0): + def _autocomplete_combo(self, completion_list, delta: int = 0): widget = self.Widget """Perform autocompletion on a Combobox widget based on the current input.""" if delta: @@ -6135,12 +6235,12 @@ def _autocomplete_combo(self, completion_list, delta=0): return hits - def autocomplete(self, delta=0): + def autocomplete(self, delta: int = 0) -> None: """Perform autocompletion based on the current input.""" self._hits = self._autocomplete_combo(self._completion_list, delta) self._hit_index = 0 - def handle_keyrelease(self, event): + def handle_keyrelease(self, event) -> None: """Handle key release event for autocompletion and navigation.""" if event.keysym == "BackSpace": self.Widget.delete(self.Widget.position, tk.END) @@ -6169,7 +6269,7 @@ class _AutocompleteCombo(_AutoCompleteLogic, sg.Combo): once to activate autocompletion, eg `window['combo_key'].update(values=values)` """ - def update(self, *args, **kwargs): + def update(self, *args, **kwargs) -> None: """Update the Combo widget with new values.""" if "values" in kwargs and kwargs["values"] is not None: self._completion_list = [str(row) for row in kwargs["values"]] @@ -6184,7 +6284,7 @@ def update(self, *args, **kwargs): class _TtkCombo(_AutoCompleteLogic, ttk.Combobox): """Customized Combo widget with autocompletion feature.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: """Initialize the Combo widget.""" self._completion_list = [str(row) for row in kwargs["values"]] self.Widget = self @@ -6196,7 +6296,7 @@ class _TtkCalendar(ttk.Frame): # Modified from Tkinter GUI Application Development Cookbook, MIT License. - def __init__(self, master, init_date, textvariable, **kwargs): + def __init__(self, master, init_date, textvariable, **kwargs) -> None: # TODO, set these in themepack? fwday = kwargs.pop("firstweekday", calendar.MONDAY) sel_bg = kwargs.pop("selectbackground", "#ecffc4") @@ -6259,7 +6359,7 @@ def create_canvas(self, bg, fg): self.table.bind("", self.pressed_callback, "+") return canvas - def build_calendar(self): + def build_calendar(self) -> None: year, month = self.cal_date.year, self.cal_date.month month_name = self.cal.formatmonthname(year, month, 0) month_weeks = self.cal.monthdayscalendar(year, month) @@ -6270,7 +6370,7 @@ def build_calendar(self): fmt_week = [f"{day:02d}" if day else "" for day in (week or [])] self.table.item(item, values=fmt_week) - def pressed_callback(self, event): + def pressed_callback(self, event) -> None: x, y, widget = event.x, event.y, event.widget item = widget.identify_row(y) column = widget.identify_column(x) @@ -6289,7 +6389,7 @@ def pressed_callback(self, event): self.draw_selection(bbox) self.textvariable.set(self.cal_date.strftime(DATE_FORMAT)) - def draw_selection(self, bbox): + def draw_selection(self, bbox) -> None: canvas, text = self.canvas, "%02d" % self.cal_date.day x, y, width, height = bbox textw = self.font.measure(text) @@ -6298,12 +6398,12 @@ def draw_selection(self, bbox): canvas.itemconfigure(canvas.text, text=text) canvas.place(x=x, y=y) - def set_date(self, dateobj): + def set_date(self, dateobj) -> None: self.cal_date = dateobj self.canvas.place_forget() self.build_calendar() - def select_date(self): + def select_date(self) -> None: bbox = self.get_bbox_for_date(self.cal_date) if bbox: self.draw_selection(bbox) @@ -6319,7 +6419,7 @@ def get_bbox_for_date(self, new_date): return self.table.bbox(item, column) return None - def move_month(self, offset): + def move_month(self, offset: int) -> None: self.canvas.place_forget() month = self.cal_date.month - 1 + offset year = self.cal_date.year + month // 12 @@ -6327,14 +6427,14 @@ def move_month(self, offset): self.cal_date = dt.date(year, month, 1) self.build_calendar() - def minsize(self, e): + def minsize(self, e) -> None: width, height = self.master.geometry().split("x") height = height[: height.index("+")] self.master.minsize(width, height) class _DatePicker(_TtkStrictInput): - def __init__(self, master, dataset, column_name, init_date, **kwargs): + def __init__(self, master, dataset, column_name: str, init_date, **kwargs) -> None: self.dataset = dataset self.column_name = column_name textvariable = kwargs["textvariable"] @@ -6348,18 +6448,18 @@ def __init__(self, master, dataset, column_name, init_date, **kwargs): self.bind("", self.on_entry_key_release, "+") self.calendar.bind("", self.hide_calendar, "+") - def show_calendar(self, event=None): + def show_calendar(self, event=None) -> None: self.configure(state=tk.DISABLED) self.calendar.place(in_=self, relx=0, rely=1) self.calendar.focus_force() self.calendar.select_date() - def hide_calendar(self, event=None): + def hide_calendar(self, event=None) -> None: self.configure(state=tk.NORMAL) self.calendar.place_forget() self.focus_force() - def on_entry_key_release(self, event=None): + def on_entry_key_release(self, event=None) -> None: date = self.get() date = self.dataset.column_info[self.column_name].cast(date) # Check if the user has typed a valid date @@ -6382,7 +6482,6 @@ def on_entry_key_release(self, event=None): # This is a dummy class for documenting convenience functions class Convenience: - """Convenience functions are a collection of functions and classes that aide in building PySimpleGUI layouts that conform to pysimplesql standards so that your database application is up and running quickly, and with all the great automatic @@ -6416,17 +6515,15 @@ def field( ) -> sg.Column: """Convenience function for adding PySimpleGUI elements to the Window, so they are properly configured for pysimplesql. The automatic functionality of pysimplesql - relies on accompanying metadata so that the `Form.auto_add_elements()` can pick them + relies on accompanying metadata so that the `Form.auto_map_elements()` can pick them up. This convenience function will create a text label, along with an element with the above metadata already set up for you. Note: The element key will default to the - record name if none is supplied. See `set_label_size()`, `set_element_size()` and - `set_mline_size()` for setting default sizes of these elements. + field name if none is supplied. Args: field: The database record in the form of table.column I.e. 'Journal.entry' element: (optional) The element type desired (defaults to PySimpleGUI.Input) - size: Overrides the default element size that was set with `set_element_size()` - for this element only. + size: Overrides the default element size for this element only. label: The text/label will automatically be generated from the column name. If a different text/label is desired, it can be specified here. no_label: Do not automatically generate a label for this element @@ -6438,6 +6535,10 @@ def field( matching filter when creating the `Form` with the filter parameter. key: (optional) The key to give this element. See note above about the default auto generated key. + use_ttk_buttons: Use ttk buttons for all action buttons. If None, defaults to + setting `ThemePack.use_ttk_buttons`. + pad: The padding to use for the generated elements. If None, defaults to setting + `ThemePack.default_element_pad`. **kwargs: Any additional arguments will be passed to the PySimpleGUI element. Returns: @@ -6485,7 +6586,7 @@ def field( key=key, size=size or themepack.default_mline_size, metadata={ - "type": TYPE_RECORD, + "type": ElementType.FIELD, "Form": None, "filter": filter, "field": field, @@ -6500,7 +6601,7 @@ def field( key=key, size=size or themepack.default_element_size, metadata={ - "type": TYPE_RECORD, + "type": ElementType.FIELD, "Form": None, "filter": filter, "field": field, @@ -6537,8 +6638,8 @@ def field( # Add the quick editor button where appropriate if element == _AutocompleteCombo and quick_editor: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_QUICK_EDIT, + "type": ElementType.EVENT, + "event_type": EventType.QUICK_EDIT, "table": table, "column": column, "function": None, @@ -6595,7 +6696,7 @@ def actions( previous, next, last and search). The action elements can be customized by selecting which ones you want generated from the parameters available. This allows full control over what is available to the user of your database application. Check - out `ThemePacks` to give any of these autogenerated controls a custom look!. + out `ThemePack` to give any of these autogenerated controls a custom look!. Note: By default, the base element keys generated for PySimpleGUI will be `table:action` using the name of the table passed in the table parameter plus the @@ -6626,20 +6727,23 @@ def actions( duplicate: Button to duplicate current record save: Button to save record. Note that the save button feature saves changes made to any table, therefore only one save button is needed per window. - search: A search Input element. Size can be specified with the `search_size` + search: A search Input element. Size can be specified with the 'search_size' parameter search_size: The size of the search input element bind_return_key: Bind the return key to the search button. Defaults to true. filter: Can be used to reference different `Form`s in the same layout. Use a matching filter when creating the `Form` with the filter parameter. - pad: The padding to use for the generated elements. + use_ttk_buttons: Use ttk buttons for all action buttons. If None, defaults to + setting `ThemePack.use_ttk_buttons`. + pad: The padding to use for the generated elements. If None, defaults to setting + `ThemePack.action_button_pad`. + **kwargs: Any additional arguments will be passed to the PySimpleGUI element. Returns: An element to be used in the creation of PySimpleGUI layouts. Note that this is technically multiple elements wrapped in a PySimpleGUI.Column, but acts as one element for the purpose of layout building. """ - if use_ttk_buttons is None: use_ttk_buttons = themepack.use_ttk_buttons if pad is None: @@ -6659,8 +6763,8 @@ def actions( # Form-level events if edit_protect: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_EDIT_PROTECT_DB, + "type": ElementType.EVENT, + "event_type": EventType.EDIT_PROTECT_DB, "table": None, "column": None, "function": None, @@ -6693,8 +6797,8 @@ def actions( ) if save: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_SAVE_DB, + "type": ElementType.EVENT, + "event_type": EventType.SAVE_DB, "table": None, "column": None, "function": None, @@ -6722,8 +6826,8 @@ def actions( if navigation: # first meta = { - "type": TYPE_EVENT, - "event_type": EVENT_FIRST, + "type": ElementType.EVENT, + "event_type": EventType.FIRST, "table": table, "column": None, "function": None, @@ -6756,8 +6860,8 @@ def actions( ) # previous meta = { - "type": TYPE_EVENT, - "event_type": EVENT_PREVIOUS, + "type": ElementType.EVENT, + "event_type": EventType.PREVIOUS, "table": table, "column": None, "function": None, @@ -6790,8 +6894,8 @@ def actions( ) # next meta = { - "type": TYPE_EVENT, - "event_type": EVENT_NEXT, + "type": ElementType.EVENT, + "event_type": EventType.NEXT, "table": table, "column": None, "function": None, @@ -6824,8 +6928,8 @@ def actions( ) # last meta = { - "type": TYPE_EVENT, - "event_type": EVENT_LAST, + "type": ElementType.EVENT, + "event_type": EventType.LAST, "table": table, "column": None, "function": None, @@ -6858,8 +6962,8 @@ def actions( ) if duplicate: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_DUPLICATE, + "type": ElementType.EVENT, + "event_type": EventType.DUPLICATE, "table": table, "column": None, "function": None, @@ -6892,8 +6996,8 @@ def actions( ) if insert: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_INSERT, + "type": ElementType.EVENT, + "event_type": EventType.INSERT, "table": table, "column": None, "function": None, @@ -6926,8 +7030,8 @@ def actions( ) if delete: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_DELETE, + "type": ElementType.EVENT, + "event_type": EventType.DELETE, "table": table, "column": None, "function": None, @@ -6960,8 +7064,8 @@ def actions( ) if search: meta = { - "type": TYPE_EVENT, - "event_type": EVENT_SEARCH, + "type": ElementType.EVENT, + "event_type": EventType.SEARCH, "table": table, "column": None, "function": None, @@ -7044,7 +7148,12 @@ def selector( key = f"{table}:selector" if key is None else key key = keygen.get(key) - meta = {"type": TYPE_SELECTOR, "table": table, "Form": None, "filter": filter} + meta = { + "type": ElementType.SELECTOR, + "table": table, + "Form": None, + "filter": filter, + } if element == sg.Listbox: layout = element( values=(), @@ -7117,10 +7226,12 @@ def selector( return layout -@dc.dataclass +@dataclass class TableStyler: + """TODO.""" + # pysimplesql specific - frame_pack_kwargs: Dict[str] = dc.field(default_factory=dict) + frame_pack_kwargs: Dict[str] = field_(default_factory=dict) # PySimpleGUI Table kwargs that are compatible with pysimplesql justification: TableJustify = "left" @@ -7152,13 +7263,13 @@ class TableStyler: expand_y: bool = False visible: bool = True - def __repr__(self): + def __repr__(self) -> str: attrs = self.get_table_kwargs() return f"TableStyler({attrs})" def get_table_kwargs(self): non_default_attributes = {} - for field in dc.fields(self): + for field in fields(self): if ( getattr(self, field.name) != field.default and getattr(self, field.name) @@ -7168,9 +7279,8 @@ def get_table_kwargs(self): return non_default_attributes -@dc.dataclass +@dataclass class TableBuilder(list): - """This is a convenience class used to build table headings for PySimpleGUI. In addition, `TableBuilder` objects can sort columns in ascending or descending @@ -7178,6 +7288,7 @@ class TableBuilder(list): the sort_enable parameter is set to True. Args: + num_rows: Number of rows to display in the table. sort_enable: True to enable sorting by heading column. allow_cell_edits: Double-click to edit a cell value if True. Accepted edits update both `sg.Table` and associated `field` element. Note: primary key, @@ -7187,6 +7298,7 @@ class TableBuilder(list): True. apply_search_filter: Filter rows to only those columns in `DataSet.search_order` that contain `Dataself.search_string`. + style: see `TableStyler`. Returns: None @@ -7196,14 +7308,23 @@ class TableBuilder(list): instances: ClassVar[List[TableBuilder]] = [] num_rows: int + """Number of rows to display in the table.""" sort_enable: bool = True + """True to enable sorting by heading column.""" allow_cell_edits: bool = False - apply_search_filter: bool = False + """Double-click to edit a cell value if True. Accepted edits update both `sg.Table` + and associated `field` element. Note: primary key, generated, or `readonly` columns + don't allow cell edits.""" lazy_loading: bool = False + """For larger DataSets (see `LazyTable`).""" add_save_heading_button: bool = False - style: TableStyler = dc.field(default_factory=TableStyler) + """Adds a save button to the left-most heading column if True.""" + apply_search_filter: bool = False + """Filter rows to only those columns in `DataSet.search_order` that contain + `Dataself.search_string`.""" + style: TableStyler = field_(default_factory=TableStyler) - def __post_init__(self): + def __post_init__(self) -> None: # Store this instance in the master list of instances TableBuilder.instances.append(self) @@ -7335,7 +7456,7 @@ def heading_justify_map(self) -> List[str]: Returns: a list heading justifications for use with LazyTable - `headings_justification` + `headings_justification` """ justify = [justify[0].lower() for justify in self._heading_justify_map] justify.insert(0, "l") @@ -7387,7 +7508,6 @@ def _update_headings( Returns: None """ - # Load in our marker characters. We will use them to both display the # sort direction and to detect current direction try: @@ -7417,10 +7537,15 @@ def _update_headings( i, text=x["heading"], anchor=self.heading_anchor_map[i] ) - def enable_heading_function(self, element: sg.Table, fn: callable) -> None: - """Enable the sorting callbacks for each column index, or saving by click the - unsaved changes column - Note: Not typically used by the end user. Called from `Form.auto_map_elements()` + def _enable_heading_function(self, element: sg.Table, fn: Callable) -> None: + """Adds appropriate heading function to underlying 'tk.treeview.heading()'. + + Enable the sorting callbacks for each column index, or saving by clicking the + unsaved changes column. + + + Note: + Not typically used by the end user. Called from `Form.auto_map_elements()`. Args: element: The PySimpleGUI Table element associated with this TableBuilder @@ -7440,15 +7565,16 @@ def enable_heading_function(self, element: sg.Table, fn: callable) -> None: if self.add_save_heading_button: element.widget.heading(0, command=functools.partial(fn, None, save=True)) - def insert(self, idx, heading: str, column: str = None, *args, **kwargs): + def insert( + self, idx: int, heading: str, column: str = None, *args, **kwargs + ) -> None: super().insert(idx, {"heading": heading, "column": column}) class _HeadingCallback: - """Internal class used when sg.Table column headings are clicked.""" - def __init__(self, frm_reference: Form, data_key: str): + def __init__(self, frm_reference: Form, data_key: str) -> None: """Create a new _HeadingCallback object. Args: @@ -7461,7 +7587,7 @@ def __init__(self, frm_reference: Form, data_key: str): self.frm: Form = frm_reference self.data_key = data_key - def __call__(self, column, save): + def __call__(self, column, save: bool) -> None: dataset = self.frm[self.data_key] if save: dataset.save_record() @@ -7473,14 +7599,13 @@ def __call__(self, column, save): class _CellEdit: - """Internal class used when sg.Table cells are double-clicked if edit enabled.""" - def __init__(self, frm_reference: Form): + def __init__(self, frm_reference: Form) -> None: self.frm = frm_reference self.active_edit = False - def __call__(self, event): + def __call__(self, event) -> None: # if double click a treeview if isinstance(event.widget, ttk.Treeview): tk_widget = event.widget @@ -7489,7 +7614,7 @@ def __call__(self, event): if region == "cell": self.edit(event) - def edit(self, event): + def edit(self, event) -> None: treeview = event.widget # only allow 1 edit at a time @@ -7704,7 +7829,7 @@ def accept( combobox_values: _ElementRow, widget_type, field_var, - ): + ) -> None: # get current entry text new_value = field_var.get() @@ -7769,7 +7894,7 @@ def accept( self.destroy() - def destroy(self): + def destroy(self) -> None: # unbind self.frm.window.TKroot.unbind("", self.destroy_bind) @@ -7786,7 +7911,7 @@ def single_click_callback( self, event, accept_dict, - ): + ) -> None: # destroy if you click a heading while editing if isinstance(event.widget, ttk.Treeview): tk_widget = event.widget @@ -7822,9 +7947,8 @@ def get_datakey_and_sgtable(self, treeview, frm): return data_key, element return None - def combo_configure(self, event): + def combo_configure(self, event) -> None: """Configures combobox drop-down to be at least as wide as longest value.""" - combo = event.widget style = ttk.Style() @@ -7841,16 +7965,15 @@ def combo_configure(self, event): class _LiveUpdate: - """Internal class used to automatically sync selectors with field changes.""" - def __init__(self, frm_reference: Form): + def __init__(self, frm_reference: Form) -> None: self.frm = frm_reference self.last_event_widget = None self.last_event_time = None self.delay_seconds = themepack.live_update_typing_delay_seconds - def __call__(self, event): + def __call__(self, event) -> None: # keep track of time on same widget if event.widget == self.last_event_widget: self.last_event_time = time() @@ -7872,7 +7995,7 @@ def __call__(self, event): lambda: self.delay(event.widget, widget_type), ) - def sync(self, widget, widget_type): + def sync(self, widget, widget_type) -> None: for e in self.frm.element_map: if e["element"].widget == widget: data_key = e["table"] @@ -7914,7 +8037,7 @@ def sync(self, widget, widget_type): if dataset.column_likely_in_selector(column): self.frm.update_selectors(dataset.key) - def delay(self, widget, widget_type): + def delay(self, widget, widget_type) -> None: if self.last_event_time: elapsed_sec = time() - self.last_event_time if elapsed_sec >= self.delay_seconds: @@ -7928,7 +8051,6 @@ def delay(self, widget, widget_type): # ====================================================================================== # Change the look and feel of your database application all in one place. class ThemePack: - """ThemePacks are user-definable objects that allow for the look and feel of database applications built with PySimpleGUI + pysimplesql. This includes everything from icons, the ttk themes, to sounds. Pysimplesql comes with 3 pre-made @@ -8021,6 +8143,7 @@ class ThemePack: """Default Themepack.""" def __init__(self, tp_dict: Dict[str, str] = None) -> None: + """Initialize the `ThemePack` class.""" self.tp_dict = tp_dict or ThemePack.default def __getattr__(self, key): @@ -8086,7 +8209,6 @@ def __call__(self, tp_dict: Dict[str, str] = None) -> None: # ====================================================================================== # Change the language text used throughout the program. class LanguagePack: - """LanguagePacks are user-definable collections of strings that allow for localization of strings and messages presented to the end user. @@ -8128,13 +8250,13 @@ class LanguagePack: "startup_relationships": "Adding relationships", "startup_binding": "Binding window to Form", # ------------------------------------------------------------------------------ - # Progress bar displayed during sqldriver operations + # Progress bar displayed during SQLDriver operations # ------------------------------------------------------------------------------ - "sqldriver_init": "{name} connection", - "sqldriver_connecting": "Connecting to database", - "sqldriver_execute": "Executing SQL commands", - "sqldriver_file_not_found_title": "Trouble finding db file", - "sqldriver_file_not_found": "Could not find file\n{file}", + "SQLDriver_init": "{name} connection", + "SQLDriver_connecting": "Connecting to database", + "SQLDriver_execute": "Executing SQL commands", + "SQLDriver_file_not_found_title": "Trouble finding db file", + "SQLDriver_file_not_found": "Could not find file\n{file}", # ------------------------------------------------------------------------------ # Default ProgressAnimate Phrases # ------------------------------------------------------------------------------ @@ -8238,7 +8360,8 @@ class LanguagePack: } """Default LanguagePack.""" - def __init__(self, lp_dict=None): + def __init__(self, lp_dict=None) -> None: + """Initialize the `LanguagePack` class.""" self.lp_dict = lp_dict or type(self).default def __getattr__(self, key): @@ -8265,7 +8388,7 @@ def __getitem__(self, key): f"LanguagePack object has no attribute '{key}'" ) from e - def __call__(self, lp_dict=None): + def __call__(self, lp_dict=None) -> None: """Update the LanguagePack instance.""" # For default use cases, load the default directly to avoid the overhead # of __getattr__() going through 2 key reads @@ -8277,7 +8400,6 @@ def __call__(self, lp_dict=None): class LangFormat(dict): - """This is a convenience class used by LanguagePack format_map calls, allowing users to not include expected variables. @@ -8297,7 +8419,6 @@ def __missing__(self, key): # This is a dummy class for documenting convenience functions class Abstractions: - """Supporting multiple databases in your application can quickly become very complicated and unmanageable. pysimplesql abstracts all of this complexity and presents a unified API via abstracting the main concepts of database programming. @@ -8318,10 +8439,11 @@ class Abstractions: # The column abstraction hides the complexity of dealing with SQL columns, getting their # names, default values, data types, primary key status and notnull status # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class Column: + """Base `ColumnClass` represents a SQL column and helps casting/validating values. - """The `Column` class is a generic column class. It holds a dict containing the + The `Column` class is a generic column class. It holds a dict containing the column name, type whether the column is notnull, whether the column is a primary key and the default value, if any. `Column`s are typically stored in a `ColumnInfo` collection. There are multiple ways to get information from a `Column`, including @@ -8341,18 +8463,18 @@ class Column: virtual: bool = False generated: bool = False python_type: Type[T] = object - custom_cast_fn: callable = None - custom_validate_fn: callable = None - cell_format_fn: callable = None + custom_cast_fn: Callable = None + custom_validate_fn: Callable = None + cell_format_fn: Callable = None domain_args: List[str, int] = None def __getitem__(self, key): return self.__dict__[key] - def __setitem__(self, key, value): + def __setitem__(self, key, value) -> None: self.__dict__[key] = value - def __contains__(self, item): + def __contains__(self, item) -> bool: return item in self.__dict__ def cast(self, value: Any) -> Any: @@ -8371,6 +8493,7 @@ def cast(self, value: Any) -> Any: return str(value) def validate(self, value: Any) -> bool: + """TODO.""" value = self.cast(value) if self.notnull and value in EMPTY: @@ -8395,7 +8518,7 @@ def validate(self, value: Any) -> bool: return ValidateResponse() -@dc.dataclass +@dataclass class MinMaxCol(Column): """Base ColumnClass for columns with minimum and maximum constraints. @@ -8430,7 +8553,7 @@ def validate(self, value): return ValidateResponse() -@dc.dataclass +@dataclass class LengthCol(Column): """Base ColumnClass for length-constrained columns. @@ -8446,7 +8569,7 @@ class LengthCol(Column): min_length: int = None max_length: int = None - def __post_init__(self): + def __post_init__(self) -> None: if self.domain_args and self.max_length is None: self.max_length = ( int(self.domain_args[0]) if self.domain_args[0] is not None else None @@ -8471,7 +8594,7 @@ def validate(self, value): return ValidateResponse() -@dc.dataclass +@dataclass class LocaleCol(Column): """Base ColumnClass that provides Locale functions. @@ -8498,22 +8621,22 @@ def strip_locale(self, value): return locale.delocalize(value) -@dc.dataclass +@dataclass class BoolCol(Column): - python_type: Type[bool] = dc.field(default=bool, init=False) + python_type: Type[bool] = field_(default=bool, init=False) - def __post_init__(self): + def __post_init__(self) -> None: if themepack.display_bool_as_checkbox: - self.cell_format_fn: callable = CellFormatFn.bool_to_checkbox + self.cell_format_fn: Callable = CellFormatFn.bool_to_checkbox def cast(self, value): return checkbox_to_bool(value) -@dc.dataclass +@dataclass class DateCol(MinMaxCol): date_format: str = DATE_FORMAT - python_type: Type[dt.date] = dc.field(default=dt.date, init=False) + python_type: Type[dt.date] = field_(default=dt.date, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -8554,9 +8677,9 @@ def cast(self, value): return super().cast(value) -@dc.dataclass +@dataclass class DateTimeCol(MinMaxCol): - datetime_format_list: List[str] = dc.field( + datetime_format_list: List[str] = field_( default_factory=lambda: [ DATETIME_FORMAT, DATETIME_FORMAT_MICROSECOND, @@ -8564,7 +8687,7 @@ class DateTimeCol(MinMaxCol): TIMESTAMP_FORMAT_MICROSECOND, ] ) - python_type: Type[dt.datetime] = dc.field(default=dt.datetime, init=False) + python_type: Type[dt.datetime] = field_(default=dt.datetime, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -8580,13 +8703,13 @@ def cast(self, value): return super().cast(value) -@dc.dataclass +@dataclass class DecimalCol(LocaleCol, MinMaxCol): precision: int = DECIMAL_PRECISION scale: int = DECIMAL_SCALE - python_type: Type[Decimal] = dc.field(default=Decimal, init=False) + python_type: Type[Decimal] = field_(default=Decimal, init=False) - def __post_init__(self): + def __post_init__(self) -> None: if self.domain_args: try: self.precision = ( @@ -8611,7 +8734,7 @@ def __post_init__(self): f"Unable to set {self.NAME} column decimal scale to " f"{self.domain_args[1]}" ) - self.cell_format_fn: callable = lambda x: CellFormatFn.decimal_places( + self.cell_format_fn: Callable = lambda x: CellFormatFn.decimal_places( x, self.scale ) @@ -8640,9 +8763,9 @@ def validate(self, value): return ValidateResponse() -@dc.dataclass +@dataclass class FloatCol(LocaleCol, LengthCol, MinMaxCol): - python_type: Type[float] = dc.field(default=float, init=False) + python_type: Type[float] = field_(default=float, init=False) def cast(self, value): value = self.strip_locale(value) @@ -8652,10 +8775,10 @@ def cast(self, value): return super().cast(value) -@dc.dataclass +@dataclass class IntCol(LocaleCol, LengthCol, MinMaxCol): truncate_decimals: bool = False - python_type: Type[int] = dc.field(default=int, init=False) + python_type: Type[int] = field_(default=int, init=False) def cast(self, value, truncate_decimals: bool = None): truncate_decimals = ( @@ -8681,18 +8804,18 @@ def cast(self, value, truncate_decimals: bool = None): return super().cast(value_backup) -@dc.dataclass +@dataclass class StrCol(LengthCol): - python_type: Type[str] = dc.field(default=str, init=False) + python_type: Type[str] = field_(default=str, init=False) def cast(self, value): return super().cast(value) -@dc.dataclass +@dataclass class TimeCol(MinMaxCol): time_format: str = TIME_FORMAT - python_type: Type[dt.time] = dc.field(default=dt.time, init=False) + python_type: Type[dt.time] = field_(default=dt.time, init=False) def cast(self, value): if isinstance(value, self.python_type): @@ -8709,12 +8832,10 @@ def cast(self, value): class ColumnInfo(List): + """Custom container that behaves like a List containing a collection of `Columns`. - """Column Information Class. - - The `ColumnInfo` class is a custom container that behaves like a List containing a - collection of `Columns`. This class is responsible for maintaining information about - all the columns (`Column`) in a table. While the individual `Column` elements of + This class is responsible for maintaining information about all the columns + (`Column`) in a table. While the individual `Column` elements of this collection contain information such as default values, primary key status, SQL data type, column name, and the notnull status - this class ties them all together into a collection and adds functionality to set default values for null columns and @@ -8737,7 +8858,8 @@ class ColumnInfo(List): "datetime", ] - def __init__(self, driver: SQLDriver, table: str): + def __init__(self, driver: SQLDriver, table: str) -> None: + """Initilize a ColumnInfo instance.""" self.driver = driver self.table = table @@ -8756,7 +8878,7 @@ def __init__(self, driver: SQLDriver, table: str): } super().__init__() - def __contains__(self, item): + def __contains__(self, item) -> bool: if isinstance(item, str): return self._contains_key_value_pair("name", item) return super().__contains__(item) @@ -8976,7 +9098,8 @@ def _get_list(self, key: str) -> List: # -------------------------------------------------------------------------------------- class Result: """This is a "dummy" Result object that is a convenience for constructing a - DataFrame that has the expected attrs set.""" + DataFrame that has the expected attrs set. + """ @classmethod def set( @@ -8985,7 +9108,6 @@ def set( lastrowid: int = None, exception: Exception = None, column_info: ColumnInfo = None, - row_backup: pd.Series = None, ): """Create a pandas DataFrame with the row data and expected attrs set. @@ -8993,13 +9115,13 @@ def set( row_data: A list of dicts of row data lastrowid: The inserted row ID from the last INSERT statement exception: Exceptions passed back from the SQLDriver - column_info: An optional ColumnInfo object + column_info: (optional) ColumnInfo object """ rows = pd.DataFrame(row_data) rows.attrs["lastrowid"] = lastrowid rows.attrs["exception"] = exception rows.attrs["column_info"] = column_info - rows.attrs["row_backup"] = row_backup + rows.attrs["row_backup"] = None rows.attrs["virtual"] = [] rows.attrs["sort_column"] = None rows.attrs["sort_reverse"] = None @@ -9010,30 +9132,35 @@ class ReservedKeywordError(Exception): pass -@dc.dataclass +@dataclass class SqlChar: - # Each database type expects their SQL prepared in a certain way. Below are - # defaults for how various elements in the SQL string should be quoted and - # represented as placeholders. Override these in the derived class as needed to - # satisfy SQL requirements + """Container for passing database-specific characters + + Each database type expects their SQL prepared in a certain way. Defaults in this + dataclass are set for how various elements in the SQL string should be quoted and + represented as placeholders. Override these in the derived class as needed to + satisfy SQL requirements + """ - # The placeholder for values in the query string. This is typically '?' or'%s' placeholder: str = "%s" # override this in derived subclass SqlChar + r"""The placeholder for values in the query string. This is typically '?' or'%s'""" # These are the quote characters for tables, columns and values. # It varies between different databases - # override this in derived subclass SqlChar (defaults to no quotes) + # override this in derived subclass SqlChar table_quote: str = "" - # override this in derived subclass SqlChar (defaults to no quotes) + """Character to quote table. (defaults to no quotes)""" + # override this in derived subclass SqlChar column_quote: str = "" - # override this in derived subclass SqlChar (defaults to single quotes) + """Chacter to quote column. (defaults to no quotes)""" + # override this in derived subclass SqlChar value_quote: str = "'" + """Character to quote value. (defaults to single quotes)""" -@dc.dataclass +@dataclass class SQLDriver(ABC): - """Abstract SQLDriver class. Derive from this class to create drivers that conform to PySimpleSQL. This ensures that the same code will work the same way regardless of which database is used. There are a few important things to note: The commented @@ -9053,10 +9180,21 @@ class SQLDriver(ABC): SQLDriver.insert_record() Args: + host: Host. + user: User. + password: Password. + database: Name of database. + sql_script: (optional) SQL script file to execute after opening the database. + sql_script_encoding: The encoding of the SQL script file. Defaults to + 'utf-8'. + sql_commands: (optional) SQL commands to execute after opening the database. + Note: sql_commands are executed after sql_script. update_cascade: (optional) Default:True. Requery and filter child table on selected parent primary key. (ON UPDATE CASCADE in SQL) delete_cascade: (optional) Default:True. Delete the dependent child records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + sql_char: (optional) `SqlChar` object, if non-default chars desired. + """ host: str = None @@ -9064,14 +9202,14 @@ class SQLDriver(ABC): password: str = None database: str = None - sql_commands: str = None sql_script: str = None sql_script_encoding: str = "utf-8" + sql_commands: str = None update_cascade: bool = True delete_cascade: bool = True - sql_char: dc.InitVar[SqlChar] = SqlChar() # noqa RUF009 + sql_char: InitVar[SqlChar] = SqlChar() # noqa RUF009 # --------------------------------------------------------------------- # MUST implement @@ -9089,7 +9227,7 @@ class SQLDriver(ABC): SQL_CONSTANTS: ClassVar[List[str]] = [] _CHECK_RESERVED_KEYWORDS: ClassVar[bool] = True - def __post_init__(self, sql_char): + def __post_init__(self, sql_char) -> None: # if derived subclass implements __init__, call `super()__post_init__()` # unpack quoting self.placeholder = sql_char.placeholder @@ -9098,9 +9236,9 @@ def __post_init__(self, sql_char): self.quote_value_char = sql_char.value_quote self.win_pb = ProgressBar( - lang.sqldriver_init.format_map(LangFormat(name=self.NAME)), 100 + lang.SQLDriver_init.format_map(LangFormat(name=self.NAME)), 100 ) - self.win_pb.update(lang.sqldriver_connecting, 0) + self.win_pb.update(lang.SQLDriver_connecting, 0) self._import_required_modules() self._init_db() self.relationships = RelationshipStore(self) @@ -9134,7 +9272,9 @@ def execute( column_info: ColumnInfo = None, auto_commit_rollback: bool = False, ): - """Implements the native SQL implementation's execute() command. + """Execute a query. + + Implements the native SQL implementation's execute() command. Args: query: The query string to execute @@ -9143,8 +9283,6 @@ def execute( auto_commit_rollback: Automatically commit or rollback depending on whether an exception was handled. Set to False by default. Set to True to have exceptions and commit/rollbacks happen automatically - - Returns: """ @abstractmethod @@ -9231,24 +9369,25 @@ def quote_column(self, column: str): def quote_value(self, value: str): return self.quote(value, self.quote_value_char) - def commit(self): + def commit(self) -> None: + """Commit a transaction.""" self.con.commit() - def rollback(self): + def rollback(self) -> None: self.con.rollback() - def close(self): + def close(self) -> None: self.con.close() - def default_query(self, table): + def default_query(self, table) -> str: table = self.quote_table(table) return f"SELECT {table}.* FROM {table}" - def default_order(self, description_column): + def default_order(self, description_column) -> str: description_column = self.quote_column(description_column) return f" ORDER BY {description_column} ASC" - def relationship_to_join_clause(self, r_obj: Relationship): + def relationship_to_join_clause(self, r_obj: Relationship) -> str: parent = self.quote_table(r_obj.parent_table) child = self.quote_table(r_obj.child_table) fk = self.quote_column(r_obj.fk_column) @@ -9328,9 +9467,9 @@ def generate_query( Args: dataset: A `DataSet` object - join_clause: True to auto-generate `join` clause, False to not - where_clause: True to auto-generate `where` clause, False to not - order_clause: True to auto-generate `order by` clause, False to not + join_clause: True to auto-generate 'join' clause, False to not + where_clause: True to auto-generate 'where' clause, False to not + order_clause: True to auto-generate 'order by' clause, False to not Returns: a query string for use with sqlite3 @@ -9342,7 +9481,7 @@ def generate_query( f' {dataset.order_clause if order_clause else ""}' ) - def delete_record(self, dataset: DataSet, cascade=True): + def delete_record(self, dataset: DataSet, cascade: bool = True): # Get data for query table = self.quote_table(dataset.table) pk_column = self.quote_column(dataset.pk_column) @@ -9423,11 +9562,11 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: """Duplicates a record in a database table and optionally duplicates its dependent records. - The function uses all columns found in `Dataset.column_info` and + The function uses all columns found in `DataSet.column_info` and select all except the primary key column, inserting a duplicate record with the same column values. - If the `children` parameter is set to `True`, the function duplicates the + If the 'children' parameter is set to 'True', the function duplicates the dependent records by setting the foreign key column of the child records to the primary key value of the newly duplicated record before inserting them. @@ -9435,11 +9574,10 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: that no columns are set to unique. Args: - dataset: The `Dataset` of the the record to be duplicated. + dataset: The `DataSet` of the the record to be duplicated. children: (optional) Whether to duplicate dependent records. Defaults to False. """ - # Get variables table = self.quote_table(dataset.table) columns = [ @@ -9615,12 +9753,13 @@ def add_relationship( update_cascade: bool, delete_cascade: bool, ) -> None: - """Add a foreign key relationship between two dataset of the database When you - attach a database, PySimpleSQL isn't aware of the relationships contained until - dataset are added via `Form.add_data`, and the relationship of various tables is - set with this function. Note that `SQLDriver.auto_add_relationships()` will do - this automatically from the schema of the database, which also happens - automatically when a `SQLDriver` is created. + """Add a foreign key relationship between two dataset of the database. + + When you attach a database, PySimpleSQL isn't aware of the relationships + contained until datasets are added via `Form.add_dataset`, and the relationship + of various tables is set with this function. Note that + `SQLDriver.auto_add_relationships()` will do this automatically from the schema + of the database, which also happens automatically when a `SQLDriver` is created. Args: join: The join type of the relationship ('LEFT JOIN', 'INNER JOIN', 'RIGHT @@ -9658,8 +9797,8 @@ def auto_add_relationships(self) -> None: requery the child table if the parent table changes (ON UPDATE CASCADE in sql is set) When you attach a database, PySimpleSQL isn't aware of the relationships contained until tables are added and the relationship of various tables is set. - This happens automatically during `Form` creation. Note that - `Form.add_relationship()` can do this manually. + This happens automatically during `SQLDriver` creation. Note that + `SQLDriver.add_relationship()` can do this manually. Returns: None @@ -9732,14 +9871,14 @@ def _get_column_class(self, domain) -> Union[ColumnClass, None]: # -------------------------------------------------------------------------------------- # SQLITE3 DRIVER # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class Sqlite(SQLDriver): """The SQLite driver supports SQLite3 databases.""" global sqlite3 # noqa PLW0603 import sqlite3 - sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + sql_char: InitVar[SqlChar] = SqlChar( # noqa RUF009 placeholder="?", table_quote='"', column_quote='"' ) @@ -9766,26 +9905,38 @@ def __init__( sqlite3.Connection, ] = None, *, - sql_commands=None, sql_script=None, sql_script_encoding: str = "utf-8", + sql_commands=None, update_cascade: bool = True, delete_cascade: bool = True, sql_char: SqlChar = sql_char, create_file: bool = True, skip_sql_if_db_exists: bool = True, - ): - """ + ) -> None: + """Initilize a Sqlite instance. + Args: + database: Path to database file, ':memory:' in-memory database, or existing + Sqlite3.Connection + sql_script: (optional) SQL script file to execute after opening the db. + sql_script_encoding: (optional) The encoding of the SQL script file. + Defaults to 'utf-8'. + sql_commands: (optional) SQL commands to execute after opening the database. + Note: sql_commands are executed after sql_script. update_cascade: (optional) Default:True. Requery and filter child table on selected parent primary key. (ON UPDATE CASCADE in SQL) delete_cascade: (optional) Default:True. Delete the dependent child records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + sql_char: (optional) `SqlChar` object, if non-default chars desired. + create_file: (optional) default True. Create file if it doesn't exist. + skip_sql_if_db_exists: (optional) Skip both 'sql_file' and 'sql_commands' if + database already exists. """ self._database = str(database) - self.sql_commands = sql_commands self.sql_script = sql_script self.sql_script_encoding = sql_script_encoding + self.sql_commands = sql_commands self.update_cascade = update_cascade self.delete_cascade = delete_cascade self.create_file = create_file @@ -9793,7 +9944,7 @@ def __init__( super().__post_init__(sql_char) - def _import_required_modules(self): + def _import_required_modules(self) -> None: # Sqlite needs Sqlite3.Connection for a type-hint, so we already imported pass @@ -9808,8 +9959,8 @@ def _init_db(self) -> None: if self._database != ":memory:" and new_database and not self.create_file: popup = Popup() popup.ok( - lang.sqldriver_file_not_found_title, - lang.sqldriver_file_not_found.format_map( + lang.SQLDriver_file_not_found_title, + lang.SQLDriver_file_not_found.format_map( LangFormat(file=self._database) ), ) @@ -9821,9 +9972,17 @@ def _init_db(self) -> None: self.con = self._database new_database = False - self.win_pb.update(lang.sqldriver_execute, 50) + self.win_pb.update(lang.SQLDriver_execute, 50) self.con.row_factory = sqlite3.Row + if ( + not self.skip_sql_if_db_exists + or self.sql_script is not None + and new_database + ): + # run SQL script from the file if the database does not yet exist + logger.info("Executing sql script from file passed in") + self.execute_script(self.sql_script, self.sql_script_encoding) # execute sql if ( not self.skip_sql_if_db_exists @@ -9835,20 +9994,12 @@ def _init_db(self) -> None: logger.debug(self.sql_commands) self.con.executescript(self.sql_commands) self.con.commit() - if ( - not self.skip_sql_if_db_exists - or self.sql_script is not None - and new_database - ): - # run SQL script from the file if the database does not yet exist - logger.info("Executing sql script from file passed in") - self.execute_script(self.sql_script, self.sql_script_encoding) @property def _imported_database(self): return isinstance(self._database, sqlite3.Connection) - def connect(self, database): + def connect(self, database) -> None: self.con = sqlite3.connect( database, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES ) @@ -9857,7 +10008,7 @@ def execute( self, query, values=None, - silent=False, + silent: bool = False, column_info=None, auto_commit_rollback: bool = False, ) -> pd.DataFrame: @@ -9890,12 +10041,12 @@ def execute( [dict(row) for row in rows], lastrowid, exception, column_info ) - def execute_script(self, script, encoding): + def execute_script(self, script, encoding) -> None: with open(script, "r", encoding=encoding) as file: logger.info(f"Loading script {script} into database.") self.con.executescript(file.read()) - def close(self): + def close(self) -> None: # Only do cleanup if this is not an imported database if not self._imported_database: # optimize the database for long-term benefits @@ -10003,7 +10154,7 @@ def _get_column_class(self, domain) -> Union[ColumnClass, None]: return Column return None - def _register_type_callables(self): + def _register_type_callables(self) -> None: # Register datetime adapters/converters # python 3.12 will depreciate dt.date/dt.datetime default adapters sqlite3.register_adapter(dt.date, lambda val: val.isoformat()) @@ -10021,9 +10172,8 @@ def _register_type_callables(self): # -------------------------------------------------------------------------------------- # The CSV driver uses SQlite3 in the background # to use pysimplesql directly with CSV files -@dc.dataclass +@dataclass class Flatfile(Sqlite): - """The Flatfile driver adds support for flatfile databases such as CSV files to pysimplesql. @@ -10042,7 +10192,7 @@ def __init__( table: str = None, pk_col: str = None, ) -> None: - """Create a new Flatfile driver instance. + r"""Create a new Flatfile driver instance. Args: file_path: The path to the flatfile @@ -10075,12 +10225,12 @@ def __init__( # First up the SQLite driver that we derived from super().__init__(":memory:") # use an in-memory database - # Change Sqlite Sqldriver init set values to Flatfile-specific + # Change Sqlite SQLDriver init set values to Flatfile-specific self.NAME = "Flatfile" self.REQUIRES = ["csv,sqlite3"] self.placeholder = "?" # update - def _init_db(self): + def _init_db(self) -> None: self.connect(":memory:") self.con.row_factory = sqlite3.Row @@ -10138,7 +10288,7 @@ def _init_db(self): self.commit() # commit them all at the end - def _import_required_modules(self): + def _import_required_modules(self) -> None: global csv # noqa PLW0603 global sqlite3 # noqa PLW0603 try: @@ -10190,11 +10340,17 @@ def save_record( # -------------------------------------------------------------------------------------- # MYSQL DRIVER # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class Mysql(SQLDriver): """The Mysql driver supports MySQL databases.""" tinyint1_is_boolean: bool = True + """Treat SQL column-type 'tinyint(1)' as Boolean + + MySQL does not have a true 'Boolean' column. Instead, a column is declared as + 'Boolean' will be stored as 'tinyint(1)'. Setting this arg as 'True' will map the + `ColumnClass` as a `BoolCol`. + """ NAME: ClassVar[str] = "MySQL" REQUIRES: ClassVar[List[str]] = ["mysql-connector-python"] @@ -10232,10 +10388,10 @@ class Mysql(SQLDriver): "CURRENT_TIMESTAMP", ] - def _init_db(self): + def _init_db(self) -> None: self.con = self.connect() - self.win_pb.update(lang.sqldriver_execute, 50) + self.win_pb.update(lang.SQLDriver_execute, 50) if self.sql_commands is not None: # run SQL script if the database does not yet exist logger.info("Executing sql commands passed in") @@ -10258,14 +10414,14 @@ def _init_db(self): logger.info("Executing sql script from file passed in") self.execute_script(self.sql_script, self.sql_script_encoding) - def _import_required_modules(self): + def _import_required_modules(self) -> None: global mysql try: import mysql.connector except ModuleNotFoundError as e: self._import_failed(e) - def connect(self, retries=3): + def connect(self, retries: int = 3): attempt = 0 while attempt < retries: try: @@ -10287,7 +10443,7 @@ def execute( self, query, values=None, - silent=False, + silent: bool = False, column_info=None, auto_commit_rollback: bool = False, ): @@ -10319,7 +10475,7 @@ def execute( [dict(row) for row in rows], lastrowid, exception, column_info ) - def execute_script(self, script, encoding): + def execute_script(self, script, encoding) -> None: with open(script, "r", encoding=encoding) as file: logger.info(f"Loading script {script} into database.") cursor = self.con.cursor() @@ -10427,7 +10583,7 @@ def get_relationships(self): return relationships # Not required for SQLDriver - def constraint(self, constraint_name): + def constraint(self, constraint_name: str): query = ( "SELECT UPDATE_RULE, DELETE_RULE FROM " "INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_NAME = " @@ -10482,7 +10638,7 @@ def _insert_duplicate_record( # MariaDB is a fork of MySQL and backward compatible. It technically does not need its # own driver, but that could change in the future, plus having its own named class makes # it more clear for the end user. -@dc.dataclass +@dataclass class Mariadb(Mysql): """The Mariadb driver supports MariaDB databases.""" @@ -10492,12 +10648,18 @@ class Mariadb(Mysql): # -------------------------------------------------------------------------------------- # POSTGRES DRIVER # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class Postgres(SQLDriver): """The Postgres driver supports PostgreSQL databases.""" - sql_char: dc.InitVar[SqlChar] = SqlChar(table_quote='"') # noqa RUF009 + sql_char: InitVar[SqlChar] = SqlChar(table_quote='"') # noqa RUF009 + sync_sequences: bool = False + """Synchronize the sequences with the max pk for each table on database connection. + + This is useful if manual records were inserted without calling nextval() to update + the sequencer. + """ NAME: ClassVar[str] = "Postgres" REQUIRES: ClassVar[List[str]] = ["psycopg2", "psycopg2.extras"] @@ -10535,7 +10697,7 @@ class Postgres(SQLDriver): "USER", ] - def _init_db(self): + def _init_db(self) -> None: self.con = self.connect() # experiment to see if I can make a nocase collation @@ -10543,9 +10705,6 @@ def _init_db(self): # self.execute(query) if self.sync_sequences: - # synchronize the sequences with the max pk for each table. This is useful - # if manual records were inserted without calling nextval() to update the - # sequencer q = "SELECT sequence_name FROM information_schema.sequences;" sequences = self.execute(q, silent=True) for s in sequences: @@ -10563,8 +10722,6 @@ def _init_db(self): max_pk = self.max_pk(table, pk_column) # update the sequence - # TODO: This needs fixed. pysimplesql_user does have permissions on the - # sequence, but this still bombs out seq = self.quote_table(seq) if max_pk > 0: q = f"SELECT setval('{seq}', {max_pk});" @@ -10572,7 +10729,7 @@ def _init_db(self): q = f"SELECT setval('{seq}', 1, false);" self.execute(q, silent=True, auto_commit_rollback=True) - self.win_pb.update(lang.sqldriver_execute, 50) + self.win_pb.update(lang.SQLDriver_execute, 50) if self.sql_script is not None: # run SQL script from the file if the database does not yet exist @@ -10588,7 +10745,7 @@ def _init_db(self): self.con.commit() cursor.close() - def _import_required_modules(self): + def _import_required_modules(self) -> None: global psycopg2 # noqa PLW0603 try: import psycopg2 @@ -10596,7 +10753,7 @@ def _import_required_modules(self): except ModuleNotFoundError as e: self._import_failed(e) - def connect(self, retries=3): + def connect(self, retries: int = 3): attempt = 0 while attempt < retries: try: @@ -10618,7 +10775,7 @@ def execute( self, query: str, values=None, - silent=False, + silent: bool = False, column_info=None, auto_commit_rollback: bool = False, ): @@ -10651,7 +10808,7 @@ def execute( [dict(row) for row in rows], exception=exception, column_info=column_info ) - def execute_script(self, script, encoding): + def execute_script(self, script, encoding) -> None: with open(script, "r", encoding=encoding) as file: logger.info(f"Loading script {script} into database.") cursor = self.con.cursor() @@ -10812,11 +10969,11 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- # MS SQLSERVER DRIVER # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class Sqlserver(SQLDriver): """The Sqlserver driver supports Microsoft SQL Server databases.""" - sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + sql_char: InitVar[SqlChar] = SqlChar( # noqa RUF009 placeholder="?", table_quote="[]" ) @@ -10859,7 +11016,7 @@ class Sqlserver(SQLDriver): "USER", ] - def _init_db(self): + def _init_db(self) -> None: self.con = self.connect() if self.sql_script is not None: @@ -10876,14 +11033,14 @@ def _init_db(self): self.con.commit() cursor.close() - def _import_required_modules(self): + def _import_required_modules(self) -> None: global pyodbc # noqa PLW0603 try: import pyodbc except ModuleNotFoundError as e: self._import_failed(e) - def connect(self, retries=3, timeout=3): + def connect(self, retries: int = 3, timeout: int = 3): attempt = 0 while attempt < retries: try: @@ -10906,7 +11063,7 @@ def execute( self, query, values=None, - silent=False, + silent: bool = False, column_info=None, auto_commit_rollback: bool = False, ): @@ -10944,7 +11101,7 @@ def execute( column_info, ) - def execute_script(self, script, encoding): + def execute_script(self, script, encoding) -> None: with open(script, "r", encoding=encoding) as file: logger.info(f"Loading script {script} into database.") cursor = self.con.cursor() @@ -11119,19 +11276,19 @@ def insert_record(self, table: str, pk: int, pk_column: str, row: dict): # -------------------------------------------------------------------------------------- # MS ACCESS DRIVER # -------------------------------------------------------------------------------------- -@dc.dataclass +@dataclass class MSAccess(SQLDriver): """The MSAccess driver supports Microsoft Access databases. Note that only database interactions are supported, including stored Queries, but not operations dealing with Forms, Reports, etc. - Note: `Jackcess` and `UCanAccess` libraries may not accurately report decimal places - for `Number` or `Currency` columns. Manual configuration of decimal places may + Note: Jackcess and UCanAccess libraries may not accurately report decimal places + for "Number" or "Currency" columns. Manual configuration of decimal places may be required by replacing the placeholders as follows: frm[DATASET KEY].column_info[COLUMN NAME].scale = 2 """ - sql_char: dc.InitVar[SqlChar] = SqlChar( # noqa RUF009 + sql_char: InitVar[SqlChar] = SqlChar( # noqa RUF009 placeholder="?", table_quote="[]" ) @@ -11160,7 +11317,7 @@ def __init__( sql_char: SqlChar = sql_char, infer_datetype_from_default_function: bool = True, use_newer_jackcess: bool = False, - ): + ) -> None: """Initialize the MSAccess class. Args: @@ -11168,14 +11325,16 @@ def __init__( overwrite_file: If True, prompts the user if the file already exists. If the user declines to overwrite the file, the provided SQL commands or script will not be executed. - sql_commands: Optional SQL commands to execute after opening the database. - sql_script: Optional SQL script file to execute after opening the database. + sql_script: (optional) SQL script file to execute after opening the db. sql_script_encoding: The encoding of the SQL script file. Defaults to 'utf-8'. + sql_commands: (optional) SQL commands to execute after opening the database. + Note: sql_commands are executed after sql_script. update_cascade: (optional) Default:True. Requery and filter child table on selected parent primary key. (ON UPDATE CASCADE in SQL) delete_cascade: (optional) Default:True. Delete the dependent child records if the parent table record is deleted. (ON UPDATE DELETE in SQL) + sql_char: (optional) `SqlChar` object, if non-default chars desired. infer_datetype_from_default_function: If True, specializes a DateTime column by examining the column's default function. A DateTime column with '=Date()' will be treated as a 'DateCol', and '=Time()' will be treated @@ -11184,7 +11343,6 @@ def __init__( for improved compatibility, specifically allowing handling of 'attachment' columns. Defaults to False. """ - self.database_file = str(database_file) self.overwrite_file = overwrite_file self.sql_script = sql_script @@ -11197,7 +11355,7 @@ def __init__( super().__post_init__(sql_char) - def _init_db(self): + def _init_db(self) -> None: if not self.start_jvm(): logger.debug("Failed to start jvm") exit() @@ -11220,7 +11378,7 @@ def _init_db(self): # then connect self.con = self.connect() - self.win_pb.update(lang.sqldriver_execute, 50) + self.win_pb.update(lang.SQLDriver_execute, 50) if self.sql_script is not None: # run SQL script from the file if the database does not yet exist @@ -11239,7 +11397,7 @@ def _init_db(self): import os import sys - def _import_required_modules(self): + def _import_required_modules(self) -> None: global jpype # noqa PLW0603 try: import jpype # pip install JPype1 @@ -11247,7 +11405,7 @@ def _import_required_modules(self): except ModuleNotFoundError as e: self._import_failed(e) - def start_jvm(self): + def start_jvm(self) -> bool: # Get the path to the 'lib' folder current_path = os.path.dirname(os.path.abspath(__file__)) lib_path = os.path.join(current_path, "lib", "UCanAccess-5.0.1.bin") @@ -11290,7 +11448,7 @@ def execute( self, query, values=None, - silent=False, + silent: bool = False, column_info=None, auto_commit_rollback: bool = False, ): @@ -11346,7 +11504,7 @@ def execute( stmt.getUpdateCount() return Result.set([], None, exception, column_info) - def execute_script(self, script, encoding): + def execute_script(self, script, encoding) -> None: with open(script, "r", encoding=encoding) as file: logger.info(f"Loading script {script} into the database.") script_content = file.read() # Read the entire script content @@ -11485,7 +11643,7 @@ def max_pk(self, table: str, pk_column: str) -> int: rows = self.execute(f"SELECT MAX({pk_column}) as max_pk FROM {table}") return rows.iloc[0]["MAX_PK"].tolist() # returned as upper case - def _get_column_definitions(self, table_name): + def _get_column_definitions(self, table_name: str): # Creates a comma separated list of column names and types to be used in a # CREATE TABLE statement columns = self.column_info(table_name) @@ -11510,7 +11668,7 @@ def _insert_duplicate_record( res.attrs["lastrowid"] = res.iloc[0]["ID"].tolist() return res - def _create_access_file(self): + def _create_access_file(self) -> bool: try: db_builder = jpype.JClass( "com.healthmarketscience.jackcess.DatabaseBuilder" @@ -11549,14 +11707,14 @@ def convert(self, value): return converter_fn(value) return value - def _register_default_adapters(self): + def _register_default_adapters(self) -> None: self.adapters = { dt.date: java.sql.Date, dt.datetime: java.sql.Timestamp, dt.time: java.sql.Time, } - def _register_default_converters(self): + def _register_default_converters(self) -> None: self.converters = { jpype.JPackage("java").lang.String: lambda value: str(value), jpype.JPackage("java").lang.Integer: lambda value: int(value), @@ -11591,20 +11749,17 @@ class Driver: the various `SQLDriver` classes. """ - sqlite: callable = Sqlite - flatfile: callable = Flatfile - mysql: callable = Mysql - mariadb: callable = Mariadb - postgres: callable = Postgres - sqlserver: callable = Sqlserver - msaccess: callable = MSAccess + sqlite: Callable = Sqlite + flatfile: Callable = Flatfile + mysql: Callable = Mysql + mariadb: Callable = Mariadb + postgres: Callable = Postgres + sqlserver: Callable = Sqlserver + msaccess: Callable = MSAccess SaveResultsDict = Dict[str, int] CallbacksDict = Dict[str, Callable[[Form, sg.Window], Union[None, bool]]] -PromptSaveValue = ( - int # Union[PROMPT_SAVE_PROCEED, PROMPT_SAVE_DISCARDED, PROMPT_SAVE_NONE] -) class SimpleTransform(TypedDict): diff --git a/pysimplesql/reserved_sql_keywords.py b/pysimplesql/reserved_sql_keywords.py index c1433b4f..79282f83 100644 --- a/pysimplesql/reserved_sql_keywords.py +++ b/pysimplesql/reserved_sql_keywords.py @@ -1,3 +1,4 @@ +"""Collection of Reserved SQL keywords.""" # encoding utf-8 __author__ = "Thadeus Burgess " diff --git a/pysimplesql/theme_pack.py b/pysimplesql/theme_pack.py index e224e036..91d26047 100644 --- a/pysimplesql/theme_pack.py +++ b/pysimplesql/theme_pack.py @@ -1,3 +1,4 @@ +"""Collection of additional Themepacks.""" # ====================================================================================================================== # THEMEPACKS # ====================================================================================================================== diff --git a/ruff.toml b/ruff.toml index 238836de..4556bc31 100644 --- a/ruff.toml +++ b/ruff.toml @@ -7,7 +7,7 @@ select = [ # "C90", #mccabe "I", #isort "N", #pep8-naming -# "D", #pydocstyle + "D", #pydocstyle # "UP", #pyupgrade "YTT", #flake8-2020 # "ANN", #flake8-annotations @@ -52,8 +52,10 @@ select = [ "RUF", #Ruff-specific rules ] ignore = [ - "D211", #No blank lines allowed before class docstring - "D212", #Multi-line docstring summary should start at the first line + "D101", + "D102", + "D105", + "D205", "PLC1901", #We compare to "" alot, and for good reason. "N813", # ignore Camelcase `PySimpleGUI` imported as lowercase `sg` "B905", # py310, `zip()` without an explicit `strict=` parameter @@ -67,9 +69,14 @@ ignore = [ "F405", "SIM102", "I", + "D", ] -"doc_examples/*" = ["F821"] +"doc_examples/*" = ["ALL"] +"doc_scripts/*" = ["ALL"] "tests/*" = ["BLE001", "F405", "PT011", "PT012", "PT015", "PT017", "SIM114"] "pysimplesql/language_pack.py" = ["E501"] "pysimplesql/theme_pack.py" = ["E501"] "pysimplesql/reserved_sql_keywords.py" = ["C405"] + +[pydocstyle] +convention = "google" diff --git a/setup.py b/setup.py index 460904eb..cebdd178 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -"Setup script for pysimplesql" +"""Setup script for pysimplesql.""" import os @@ -6,11 +6,17 @@ def read(fname): + """Utility function to read the README file. + + Used for the long_description. It's nice, because now 1) we have a top level + README file and 2) it's easier to type in the README file than to put a raw + string in below + """ return open(os.path.join(os.path.dirname(__file__), fname)).read() # noqa: SIM115 -def main(): - "Executes setup when this script is the top-level" +def main() -> None: + """Executes setup when this script is the top-level.""" import pysimplesql as app setup( diff --git a/tests/progressanimate_test.py b/tests/progressanimate_test.py index 85ce7a27..379abf3a 100644 --- a/tests/progressanimate_test.py +++ b/tests/progressanimate_test.py @@ -8,13 +8,13 @@ # Simulated process -def process(raise_error=False): +def process(raise_error: bool = False): if raise_error: raise ValueError("Oops! This process had an error!") sleep(5) -def test_successful_process(): +def test_successful_process() -> None: try: sa = ss.ProgressAnimate("Test ProgressAnimate") sa.run(process, False) @@ -22,14 +22,14 @@ def test_successful_process(): assert False, f"An exception was raised: {e}" -def test_exception_during_process(): +def test_exception_during_process() -> None: with pytest.raises(Exception): sa = ss.ProgressAnimate("Test ProgressAnimate") v = sa.run(process, True) print(v, type(v)) -def test_config(): +def test_config() -> None: # What if config was set with an int? with pytest.raises(ValueError): ss.ProgressAnimate("Test", config=1) @@ -66,7 +66,7 @@ def test_config(): ss.ProgressAnimate("Test", config=config) -def test_run(): +def test_run() -> None: with pytest.raises(ValueError): pa = ss.ProgressAnimate("Test") pa.run(True) diff --git a/tests/sqldriver_test.py b/tests/sqldriver_test.py index a8ea471f..315464c7 100644 --- a/tests/sqldriver_test.py +++ b/tests/sqldriver_test.py @@ -165,7 +165,7 @@ def test_connect(driver): ], indirect=True, ) -def test_close(driver): +def test_close(driver) -> None: # Close the driver driver.close() @@ -189,7 +189,7 @@ def test_close(driver): ], indirect=True, ) -def test_create_table(driver: ss.SQLDriver): +def test_create_table(driver: ss.SQLDriver) -> None: driver_class = driver.__class__ # Create table = "TestAaBb123" From 8f02b80321d2860f66108d9f29d9f187ae0364bb Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:41:17 -0400 Subject: [PATCH 113/121] nits --- pysimplesql/pysimplesql.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index e73337e4..7b7e8561 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -484,7 +484,7 @@ def get_instance(self): @dataclass class Relationship: - """Information from Foreign-Keys + """Information from Foreign-Keys. Args: join_type: The join type. I.e. "LEFT JOIN", "INNER JOIN", etc. @@ -3712,9 +3712,10 @@ def set_fk_column_cascade( rel.delete_cascade = delete_cascade def auto_add_datasets(self) -> None: - """Automatically add `DataSet` objects from the database by looping through the - tables available and creating a `DataSet` object for each. Each dataset key by default - name of the table. + """Automatically add `DataSet` objects from the database. + + Works by looping through the tables available and creating a `DataSet` object + for each. Each dataset key by default name of the table. This is called automatically when a `Form ` is created. Note that `Form.add_dataset()` can do this manually on a per-table basis. @@ -9134,7 +9135,7 @@ class ReservedKeywordError(Exception): @dataclass class SqlChar: - """Container for passing database-specific characters + """Container for passing database-specific characters. Each database type expects their SQL prepared in a certain way. Defaults in this dataclass are set for how various elements in the SQL string should be quoted and From 9736f0192d893b2c9bc4160e234d94ce6cf00bf3 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:01:08 -0400 Subject: [PATCH 114/121] Update .gitignore --- .gitignore | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitignore b/.gitignore index d08688fd..45a29b80 100644 --- a/.gitignore +++ b/.gitignore @@ -143,3 +143,17 @@ cython_debug/ # Ignore the lib folder for pysimplesql !**pysimplesql/lib/ !**pysimplesql/lib/** + +# Ignore vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix \ No newline at end of file From 744ebe701e31b3c8e4402c5df95bd109e4553dcf Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Sun, 6 Aug 2023 16:57:41 -0400 Subject: [PATCH 115/121] Move `current` functions under DataSet.current --- examples/SQLite_examples/orders.py | 2 +- examples/orders_multiple_databases.py | 2 +- pysimplesql/pysimplesql.py | 421 +++++++++++++------------- 3 files changed, 220 insertions(+), 205 deletions(-) diff --git a/examples/SQLite_examples/orders.py b/examples/SQLite_examples/orders.py index 3c64e341..902cdf85 100644 --- a/examples/SQLite_examples/orders.py +++ b/examples/SQLite_examples/orders.py @@ -353,7 +353,7 @@ def update_orders(frm_reference, window, data_key) -> bool: and values["after_record_edit"]["data_key"] == "order_details" ): dataset = frm["order_details"] - current_row = dataset.get_current_row() + current_row = dataset.current.get() # after a product and quantity is entered, grab price & save if ( dataset.row_count diff --git a/examples/orders_multiple_databases.py b/examples/orders_multiple_databases.py index f134fe70..caef16d5 100644 --- a/examples/orders_multiple_databases.py +++ b/examples/orders_multiple_databases.py @@ -600,7 +600,7 @@ def update_orders(frm_reference, window, data_key) -> bool: and values["after_record_edit"]["data_key"] == "order_details" ): dataset = frm["order_details"] - current_row = dataset.get_current_row() + current_row = dataset.current.get() # after a product and quantity is entered, grab price & save if ( dataset.row_count diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7b7e8561..db82af03 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -705,6 +705,165 @@ def __contains__(self, item) -> bool: return item in self.__dict__ +@dataclass +class CurrentRow: + dataset: DataSet + + def __post_init__(self): + self._index = 0 + + # Make current.index a property so that bounds can be respected + @property + def index(self): + return self._index + + @index.setter + # Keeps the current.index in bounds + def index(self, val: int) -> None: + if val > self.dataset.row_count - 1: + self._index = self.dataset.row_count - 1 + elif val < 0: + self._index = 0 + else: + self._index = val + + @property + def has_backup(self) -> bool: + """Returns True if the current_row has a backup row, and False otherwise. + + A pandas Series object is stored rows.attrs["row_backup"] before a 'CellEdit' or + 'LiveUpdate' operation is initiated, so that it can be compared in + `DataSet.records_changed` and `DataSet.save_record` or used to restore if + changes are discarded during a `DataSet.prompt_save` operations. + + Returns: + True if a backup row is present that matches, and False otherwise. + """ + rows = self.dataset.rows + if rows is None or rows.empty: + return False + if ( + isinstance(rows.attrs["row_backup"], pd.Series) + and rows.attrs["row_backup"][self.dataset.pk_column] + == self.get()[self.dataset.pk_column] + ): + return True + return False + + def backup(self) -> None: + """Creates a backup copy of the current row in `DataSet.rows`.""" + rows = self.dataset.rows + if not self.has_backup: + rows.attrs["row_backup"] = self.get().copy() + + def restore_backup(self) -> None: + """Restores the backup row to the current row in `DataSet.rows`. + + This method replaces the current row in the dataset with the backup row, if a + backup row is present. + """ + rows = self.dataset.rows + if self.has_backup: + rows.iloc[self.index] = rows.attrs["row_backup"].copy() + + def get(self) -> Union[pd.Series, None]: + """Get the row for the currently selected record of this table. + + Returns: + A pandas Series object + """ + rows = self.dataset.rows + if not rows.empty: + # force the current.index to be in bounds! + # For child reparenting + self.index = self.index + + # make sure to return as python type + return rows.astype("O").iloc[self.index] + return None + + def get_original(self) -> pd.Series: + """Returns a copy of current row as it was fetched in a query from `SQLDriver`. + + If a backup of the current row is present, this method returns a copy of that + row. Otherwise, it returns a copy of the current row. Returns None if + `DataSet.rows` is empty. + """ + rows = self.dataset.rows + if self.has_backup: + return rows.attrs["row_backup"].copy() + if not rows.empty: + return self.get().copy() + return None + + def get_pk(self) -> int: + """Get the primary key of the currently selected record. + + Returns: + the primary key + """ + return self.get_value(self.dataset.pk_column) + + def get_value(self, column: str, default: Union[str, int] = "") -> Union[str, int]: + """Get the value for the supplied column in the current row. + + You can also use indexing of the `Form` object to get the current value of a + column I.e. frm[{DataSet}].[{column}]. + + Args: + column: The column you want to get the value from + default: A value to return if the record is null + + Returns: + The value of the column requested + """ + logger.debug(f"Getting current record for {self.dataset.table}.{column}") + if self.dataset.row_count: + if self.get()[column] is not None: + return self.get()[column] + return default + return default + + def set_value( + self, column: str, value: Union[str, int], write_event: bool = False + ) -> None: + """Set the value for the supplied column in the current row, making a backup if + needed. + + You can also use indexing of the `Form` object to set the current value of a + column. I.e. frm[{DataSet}].[{column}] = 'New value'. + + Args: + column: The column you want to set the value for + value: A value to set the current record's column to + write_event: (optional) If True, writes an event to PySimpleGui as + `after_record_edit`. + + Returns: + None + """ + rows = self.dataset.rows + dataset = self.dataset + logger.debug(f"Setting current record for {dataset.key}.{column} = {value}") + self.backup() + rows.loc[rows.index[self.index], column] = value + if write_event: + self.dataset.frm.window.write_event_value( + "after_record_edit", + { + "frm_reference": dataset.frm, + "data_key": dataset.key, + "column": column, + "value": value, + }, + ) + # call callback + if "after_record_edit" in dataset.callbacks: + dataset.callbacks["after_record_edit"]( + dataset.frm, dataset.frm.window, dataset.key + ) + + @dataclass(eq=False) class DataSet: """`DataSet` objects are used for an internal representation of database tables. @@ -793,7 +952,7 @@ def __post_init__(self, data_key, frm_reference, prompt_save) -> None: self.driver = self.frm.driver self.relationships = self.driver.relationships self.rows: pd.DataFrame = Result.set() - self._current_index: int = 0 + self.current = CurrentRow(self) self.column_info: ColumnInfo = None self.selector: List[str] = [] @@ -840,7 +999,7 @@ def __getitem__(self, column: str) -> Union[str, int]: Returns: The current value of the specified column. """ - return self.get_current(column) + return self.current.get_value(column) # Override the [] operator to set current columns value def __setitem__(self, column, value: Union[str, int]) -> None: @@ -853,7 +1012,7 @@ def __setitem__(self, column, value: Union[str, int]) -> None: Returns: None """ - self.set_current(column, value) + self.current.set_value(column, value) @property def search_string(self): @@ -866,21 +1025,6 @@ def search_string(self, val: str) -> None: if self._search_string is not None: self._search_string.set(val) - # Make current_index a property so that bounds can be respected - @property - def current_index(self): - return self._current_index - - @current_index.setter - # Keeps the current_index in bounds - def current_index(self, val: int) -> None: - if val > self.row_count - 1: - self._current_index = self.row_count - 1 - elif val < 0: - self._current_index = 0 - else: - self._current_index = val - @classmethod def purge_form(cls, frm: Form, reset_keygen: bool) -> None: """Purge the tracked instances related to frm. @@ -1156,8 +1300,8 @@ def records_changed(self, column: str = None, recursive: bool = True) -> bool: if self.pk_is_virtual(): return True - if self.current_row_has_backup and not self.get_current_row().equals( - self.get_original_current_row() + if self.current.has_backup and not self.current.get().equals( + self.current.get_original() ): return True @@ -1296,7 +1440,7 @@ def prompt_save( `PromptSaveReturn.DISCARDED`, or `PromptSaveReturn.NONE`. """ # Return False if there is nothing to check or _prompt_save is False - if self.current_index is None or not self.row_count or not self._prompt_save: + if self.current.index is None or not self.row_count or not self._prompt_save: return PromptSaveReturn.NONE # See if any rows are virtual @@ -1326,7 +1470,7 @@ def prompt_save( return PromptSaveReturn.PROCEED # if no self.purge_virtual() - self.restore_current_row() + self.current.restore_backup() # set_by_index already takes care of this, but just in-case this method is # called another way. @@ -1497,7 +1641,7 @@ def first( ): return - self.current_index = 0 + self.current.index = 0 if update_elements: self.frm.update_elements(self.key) if requery_dependents: @@ -1536,7 +1680,7 @@ def last( ): return - self.current_index = self.row_count - 1 + self.current.index = self.row_count - 1 if update_elements: self.frm.update_elements(self.key) @@ -1567,7 +1711,7 @@ def next( Returns: None """ - if self.current_index < self.row_count - 1: + if self.current.index < self.row_count - 1: logger.debug(f"Moving to the next record of table {self.table}") # prompt_save if ( @@ -1577,7 +1721,7 @@ def next( ): return - self.current_index += 1 + self.current.index += 1 if update_elements: self.frm.update_elements(self.key) if requery_dependents: @@ -1607,7 +1751,7 @@ def previous( Returns: None """ - if self.current_index > 0: + if self.current.index > 0: logger.debug(f"Moving to the previous record of table {self.table}") # prompt_save if ( @@ -1617,7 +1761,7 @@ def previous( ): return - self.current_index -= 1 + self.current.index -= 1 if update_elements: self.frm.update_elements(self.key) if requery_dependents: @@ -1696,7 +1840,7 @@ def search( # reorder rows to be idx + 1, and wrap around back to the beginning rows = self.rows.copy().reset_index() - idx = self.current_index + 1 % len(rows) + idx = self.current.index + 1 % len(rows) rows = pd.concat([rows.loc[idx:], rows.loc[:idx]]) # fill in descriptions for cols in search_order @@ -1713,7 +1857,7 @@ def search( ] if not result.empty: # save index for later, if callback returns False - old_index = self.current_index + old_index = self.current.index # grab the first result pk = result.iloc[0][self.pk_column] @@ -1749,7 +1893,7 @@ def search( if "after_search" in self.callbacks and not self.callbacks["after_search"]( self.frm, self.frm.window, self.key ): - self.current_index = old_index + self.current.index = old_index self.frm.update_elements(self.key) self.requery_dependents() return SEARCH_ABORTED @@ -1794,7 +1938,7 @@ def set_by_index( None """ # if already there - if self.current_index == index: + if self.current.index == index: return logger.debug(f"Moving to the record at index {index} on {self.table}") @@ -1811,7 +1955,7 @@ def set_by_index( if self.prompt_save(update_elements=False) == SAVE_FAIL: return - self.current_index = index + self.current.index = index if update_elements: self.frm.update_elements(self.key, omit_elements=omit_elements) if requery_dependents: @@ -1868,63 +2012,6 @@ def set_by_pk( omit_elements=omit_elements, ) - def get_current( - self, column: str, default: Union[str, int] = "" - ) -> Union[str, int]: - """Get the value for the supplied column in the current row. - - You can also use indexing of the `Form` object to get the current value of a - column I.e. frm[{DataSet}].[{column}]. - - Args: - column: The column you want to get the value from - default: A value to return if the record is null - - Returns: - The value of the column requested - """ - logger.debug(f"Getting current record for {self.table}.{column}") - if self.row_count: - if self.get_current_row()[column] is not None: - return self.get_current_row()[column] - return default - return default - - def set_current( - self, column: str, value: Union[str, int], write_event: bool = False - ) -> None: - """Set the value for the supplied column in the current row, making a backup if - needed. - - You can also use indexing of the `Form` object to set the current value of a - column. I.e. frm[{DataSet}].[{column}] = 'New value'. - - Args: - column: The column you want to set the value for - value: A value to set the current record's column to - write_event: (optional) If True, writes an event to PySimpleGui as - `after_record_edit`. - - Returns: - None - """ - logger.debug(f"Setting current record for {self.key}.{column} = {value}") - self.backup_current_row() - self.rows.loc[self.rows.index[self.current_index], column] = value - if write_event: - self.frm.window.write_event_value( - "after_record_edit", - { - "frm_reference": self.frm, - "data_key": self.key, - "column": column, - "value": value, - }, - ) - # call callback - if "after_record_edit" in self.callbacks: - self.callbacks["after_record_edit"](self.frm, self.frm.window, self.key) - def get_keyed_value( self, value_column: str, key_column: str, key_value: Union[str, int] ) -> Union[str, int, None]: @@ -1945,29 +2032,6 @@ def get_keyed_value( return row[value_column] return None - def get_current_pk(self) -> int: - """Get the primary key of the currently selected record. - - Returns: - the primary key - """ - return self.get_current(self.pk_column) - - def get_current_row(self) -> Union[pd.Series, None]: - """Get the row for the currently selected record of this table. - - Returns: - A pandas Series object - """ - if not self.rows.empty: - # force the current_index to be in bounds! - # For child reparenting - self.current_index = self.current_index - - # make sure to return as python type - return self.rows.astype("O").iloc[self.current_index] - return None - def add_selector( self, element: sg.Element, @@ -2051,7 +2115,7 @@ def insert_record( # Make sure we take into account the foreign key relationships... for r in self.relationships: if self.table == r.child_table and r.on_update_cascade: - new_values[r.fk_column] = self.frm[r.parent_table].get_current_pk() + new_values[r.fk_column] = self.frm[r.parent_table].current.get_pk() # Update the pk to match the expected pk the driver would generate on insert. new_values[self.pk_column] = self.driver.next_pk(self.table, self.pk_column) @@ -2061,9 +2125,9 @@ def insert_record( self.insert_row(new_values) # and move to the new record - # do this in insert_record, because possibly current_index is already 0 + # do this in insert_record, because possibly current.index is already 0 # and set_by_index will return early before update/requery if so. - self.current_index = self.row_count + self.current.index = self.row_count self.frm.update_elements(self.key) self.requery_dependents() @@ -2125,7 +2189,7 @@ def save_record( # Work with a copy of the original row and transform it if needed # While saving, we are working with just the current row of data, # unless it's 'keyed' via ?/= - current_row = self.get_current_row().copy() + current_row = self.current.get().copy() # Track the keyed queries we have to run. # Set to None, so we can tell later if there were keyed elements @@ -2191,7 +2255,7 @@ def save_record( if self.pk_is_virtual(): changed_row_dict = new_dict else: - old_dict = self.get_original_current_row().fillna("").to_dict() + old_dict = self.current.get_original().fillna("").to_dict() changed_row_dict = { key: new_dict[key] for key in new_dict @@ -2211,8 +2275,8 @@ def save_record( # if user is not using liveupdate, they can change something using celledit # but then change it back in field element (which overrides the celledit) # this refreshes the selector/comboboxes so that gui is up-to-date. - if self.current_row_has_backup: - self.restore_current_row() + if self.current.has_backup: + self.current.restore_backup() self.frm.update_selectors(self.key) self.frm.update_fields(self.key) return SAVE_NONE + SHOW_MESSAGE @@ -2281,7 +2345,7 @@ def save_record( else: if self.pk_is_virtual(): result = self.driver.insert_record( - self.table, self.get_current_pk(), self.pk_column, changed_row_dict + self.table, self.current.get_pk(), self.pk_column, changed_row_dict ) else: result = self.driver.save_record(self, changed_row_dict) @@ -2302,12 +2366,12 @@ def save_record( pk = ( result.attrs["lastrowid"] if result.attrs["lastrowid"] is not None - else self.get_current_pk() + else self.current.get_pk() ) - self.set_current(self.pk_column, pk, write_event=False) + self.current.set_value(self.pk_column, pk, write_event=False) # then update the current row data - self.rows.iloc[self.current_index] = current_row + self.rows.iloc[self.current.index] = current_row # If child changes parent, move index back and requery/requery_dependents if ( @@ -2582,7 +2646,7 @@ def duplicate_record( if answer == "no": return True # Store our current pk, so we can move to it if the duplication fails - pk = self.get_current_pk() + pk = self.current.get_pk() # Have the driver duplicate the record result = self.driver.duplicate_record(self, children) @@ -2628,7 +2692,7 @@ def get_description_for_pk(self, pk: int) -> Union[str, int, None]: """ # We don't want to update other views comboboxes/tableviews until row is # actually saved. So first check their current - current_row = self.get_original_current_row() + current_row = self.current.get_original() if current_row[self.pk_column] == pk: return current_row[self.description_column] try: @@ -2654,7 +2718,7 @@ def pk_is_virtual(self, pk: int = None) -> bool: return False if pk is None: - pk = self.get_current_row()[self.pk_column] + pk = self.current.get()[self.pk_column] return bool(pk in self.virtual_pks) @@ -2670,28 +2734,6 @@ def row_count(self) -> int: return len(self.rows.index) return 0 - @property - def current_row_has_backup(self) -> bool: - """Returns True if the current_row has a backup row, and False otherwise. - - A pandas Series object is stored rows.attrs["row_backup"] before a 'CellEdit' or - 'LiveUpdate' operation is initiated, so that it can be compared in - `DataSet.records_changed` and `DataSet.save_record` or used to restore if - changes are discarded during a `DataSet.prompt_save` operations. - - Returns: - True if a backup row is present that matches, and False otherwise. - """ - if self.rows is None or self.rows.empty: - return False - if ( - isinstance(self.rows.attrs["row_backup"], pd.Series) - and self.rows.attrs["row_backup"][self.pk_column] - == self.get_current_row()[self.pk_column] - ): - return True - return False - def purge_row_backup(self) -> None: """Deletes the backup row from the dataset. @@ -2699,33 +2741,6 @@ def purge_row_backup(self) -> None: """ self.rows.attrs["row_backup"] = None - def restore_current_row(self) -> None: - """Restores the backup row to the current row in `DataSet.rows`. - - This method replaces the current row in the dataset with the backup row, if a - backup row is present. - """ - if self.current_row_has_backup: - self.rows.iloc[self.current_index] = self.rows.attrs["row_backup"].copy() - - def get_original_current_row(self) -> pd.Series: - """Returns a copy of current row as it was fetched in a query from `SQLDriver`. - - If a backup of the current row is present, this method returns a copy of that - row. Otherwise, it returns a copy of the current row. Returns None if - `DataSet.rows` is empty. - """ - if self.current_row_has_backup: - return self.rows.attrs["row_backup"].copy() - if not self.rows.empty: - return self.get_current_row().copy() - return None - - def backup_current_row(self) -> None: - """Creates a backup copy of the current row in `DataSet.rows`.""" - if not self.current_row_has_backup: - self.rows.attrs["row_backup"] = self.get_current_row().copy() - def table_values( self, columns: List[str] = None, @@ -2765,12 +2780,12 @@ def table_values( if mark_unsaved: virtual_row_pks = self.virtual_pks.copy() # add pk of current row if it has changes - if self.current_row_has_backup and not self.get_current_row().equals( - self.get_original_current_row() + if self.current.has_backup and not self.current.get().equals( + self.current.get_original() ): virtual_row_pks.append( self.rows.loc[ - self.rows[pk_column] == self.get_current_row()[pk_column], + self.rows[pk_column] == self.current.get()[pk_column], pk_column, ].to_numpy()[0] ) @@ -2882,8 +2897,8 @@ def combobox_values( description = self.frm[rel.parent_table].description_column # revert to original row (so unsaved changes don't show up in dropdowns) - parent_current_row = self.frm[rel.parent_table].get_original_current_row() - rows.iloc[self.frm[rel.parent_table].current_index] = parent_current_row + parent_current_row = self.frm[rel.parent_table].current.get_original() + rows.iloc[self.frm[rel.parent_table].current.index] = parent_current_row # fastest way yet to generate this list of _ElementRow combobox_values = [ @@ -2949,7 +2964,7 @@ def map_fk_descriptions( # get this before map(), to revert below parent_current_row = self.frm[ rel.parent_table - ].get_original_current_row() + ].current.get_original() condition = rows[col] == parent_current_row[parent_pk_column] # map descriptions to fk column @@ -3289,7 +3304,7 @@ def sort(self, table: str, update_elements: bool = True, sort_order=None) -> Non Returns: None """ - pk = self.get_current_pk() + pk = self.current.get_pk() if self.rows.attrs["sort_column"] is None: logger.debug("Sort column is None. Resetting sort.") self.sort_reset() @@ -4093,7 +4108,7 @@ def auto_map_events(self, win: sg.Window) -> None: table = self[table].get_related_table_for_column(column) funct = functools.partial( self[table].quick_editor, - self[referring_table].get_current, + self[referring_table].current.get_value, column, **quick_editor_kwargs if quick_editor_kwargs else {}, ) @@ -4168,7 +4183,7 @@ def prompt_save(self) -> Type[PromptSaveReturn]: # since we are choosing not to save for data_key_ in self.datasets: self[data_key_].purge_virtual() - self[data_key_].restore_current_row() + self[data_key_].current.restore_backup() self.update_elements() # We did have a change, regardless if the user chose not to save return PromptSaveReturn.DISCARDED @@ -4404,13 +4419,13 @@ def update_actions(self, target_data_key: str = None) -> None: # Disable first/prev if only 1 row, or first row elif ":table_first" in m["event"] or ":table_previous" in m["event"]: - disable = row_count < 2 or self[data_key].current_index == 0 + disable = row_count < 2 or self[data_key].current.index == 0 win[m["event"]].update(disabled=disable) # Disable next/last if only 1 row, or last row elif ":table_next" in m["event"] or ":table_last" in m["event"]: disable = row_count < 2 or ( - self[data_key].current_index == row_count - 1 + self[data_key].current.index == row_count - 1 ) win[m["event"]].update(disabled=disable) @@ -4576,7 +4591,7 @@ def update_fields( # can't be changed. values = mapped.dataset.table_values() # Select the current one - pk = mapped.dataset.get_current_pk() + pk = mapped.dataset.current.get_pk() if len(values): # noqa SIM108 # set index to pk @@ -4684,7 +4699,7 @@ def update_selectors( element.update( values=lst, - set_to_index=dataset.current_index, + set_to_index=dataset.current.index, ) # set vertical scroll bar to follow selected element @@ -4692,7 +4707,7 @@ def update_selectors( if isinstance(element, sg.Listbox): try: element.set_vscroll_position( - dataset.current_index / len(lst) + dataset.current.index / len(lst) ) except ZeroDivisionError: element.set_vscroll_position(0) @@ -4700,7 +4715,7 @@ def update_selectors( elif isinstance(element, sg.Slider): # Re-range the element depending on the number of records l = dataset.row_count # noqa: E741 - element.update(value=dataset._current_index + 1, range=(1, l)) + element.update(value=dataset._current.index + 1, range=(1, l)) elif isinstance(element, sg.Table): logger.debug("update_elements: Table selector found...") @@ -4727,7 +4742,7 @@ def update_selectors( # Get the primary key to select. # Use the list above instead of getting it directly # from the table, as the data has yet to be updated - pk = dataset.get_current_pk() + pk = dataset.current.get_pk() found = False if len(values): @@ -7855,13 +7870,13 @@ def accept( return # see if there was a change - old_value = dataset.get_current_row().copy()[column] + old_value = dataset.current.get().copy()[column] cast_new_value = dataset.value_changed( column, old_value, new_value, bool(widget_type == TK_CHECKBUTTON) ) if cast_new_value is not Boolean.FALSE: # push row to dataset and update - dataset.set_current(column, cast_new_value, write_event=True) + dataset.current.set_value(column, cast_new_value, write_event=True) # Update matching field self.frm.update_fields(data_key, columns=[column]) # TODO: make sure we actually want to set new_value to cast @@ -7885,8 +7900,8 @@ def accept( # set marker values[0] = ( themepack.marker_unsaved - if dataset.current_row_has_backup - and not dataset.get_current_row().equals(dataset.get_original_current_row()) + if dataset.current.has_backup + and not dataset.current.get().equals(dataset.current.get_original()) else " " ) @@ -8026,13 +8041,13 @@ def sync(self, widget, widget_type) -> None: return # see if there was a change - old_value = dataset.get_current_row()[column] + old_value = dataset.current.get()[column] new_value = dataset.value_changed( column, old_value, new_value, bool(widget_type == TK_CHECKBUTTON) ) if new_value is not Boolean.FALSE: # push row to dataset and update - dataset.set_current(column, new_value, write_event=True) + dataset.current.set_value(column, new_value, write_event=True) # Update tableview if uses column: if dataset.column_likely_in_selector(column): @@ -9436,7 +9451,7 @@ def generate_where_clause(self, dataset: DataSet) -> str: for r in self.relationships: if dataset.table == r.child_table and r.on_update_cascade: table = dataset.table - parent_pk = dataset.frm[r.parent_table].get_current(r.pk_column) + parent_pk = dataset.frm[r.parent_table].current.get_value(r.pk_column) # Children without cascade-filtering parent aren't displayed if not parent_pk: @@ -9486,7 +9501,7 @@ def delete_record(self, dataset: DataSet, cascade: bool = True): # Get data for query table = self.quote_table(dataset.table) pk_column = self.quote_column(dataset.pk_column) - pk = dataset.get_current(dataset.pk_column) + pk = dataset.current.get_value(dataset.pk_column) # Create clauses delete_clause = f"DELETE FROM {table} " # leave a space at end for joining @@ -9588,7 +9603,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: ] columns = ", ".join(columns) pk_column = dataset.pk_column - pk = dataset.get_current(dataset.pk_column) + pk = dataset.current.get_value(dataset.pk_column) # Insert new record res = self._insert_duplicate_record(table, columns, pk_column, pk) @@ -9692,7 +9707,7 @@ def _insert_duplicate_record( def save_record( self, dataset: DataSet, changed_row: dict, where_clause: str = None ) -> pd.DataFrame: - pk = dataset.get_current_pk() + pk = dataset.current.get_pk() pk_column = dataset.pk_column # quote columns @@ -10309,7 +10324,7 @@ def save_record( # Update the DataSet object's DataFra,e with the changes, so then # the entire DataFrame can be written back to file sequentially - dataset.rows.iloc[dataset.current_index] = pd.Series(changed_row) + dataset.rows.iloc[dataset.current.index] = pd.Series(changed_row) # open the CSV file for writing with open(self.file_path, "w", newline="\n") as csvfile: From 9d11bdf8799eacc4597680d4410d588ce287cc54 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:20:36 -0400 Subject: [PATCH 116/121] Refactor current.get_pk() -> `@property current.pk --- pysimplesql/pysimplesql.py | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index db82af03..76a05cdc 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -749,6 +749,15 @@ def has_backup(self) -> bool: ): return True return False + + @property + def pk(self) -> int: + """Get the primary key of the currently selected record. + + Returns: + the primary key + """ + return self.get_value(self.dataset.pk_column) def backup(self) -> None: """Creates a backup copy of the current row in `DataSet.rows`.""" @@ -796,14 +805,6 @@ def get_original(self) -> pd.Series: return self.get().copy() return None - def get_pk(self) -> int: - """Get the primary key of the currently selected record. - - Returns: - the primary key - """ - return self.get_value(self.dataset.pk_column) - def get_value(self, column: str, default: Union[str, int] = "") -> Union[str, int]: """Get the value for the supplied column in the current row. @@ -2115,7 +2116,7 @@ def insert_record( # Make sure we take into account the foreign key relationships... for r in self.relationships: if self.table == r.child_table and r.on_update_cascade: - new_values[r.fk_column] = self.frm[r.parent_table].current.get_pk() + new_values[r.fk_column] = self.frm[r.parent_table].current.pk # Update the pk to match the expected pk the driver would generate on insert. new_values[self.pk_column] = self.driver.next_pk(self.table, self.pk_column) @@ -2345,7 +2346,7 @@ def save_record( else: if self.pk_is_virtual(): result = self.driver.insert_record( - self.table, self.current.get_pk(), self.pk_column, changed_row_dict + self.table, self.current.pk, self.pk_column, changed_row_dict ) else: result = self.driver.save_record(self, changed_row_dict) @@ -2366,7 +2367,7 @@ def save_record( pk = ( result.attrs["lastrowid"] if result.attrs["lastrowid"] is not None - else self.current.get_pk() + else self.current.pk ) self.current.set_value(self.pk_column, pk, write_event=False) @@ -2646,7 +2647,7 @@ def duplicate_record( if answer == "no": return True # Store our current pk, so we can move to it if the duplication fails - pk = self.current.get_pk() + pk = self.current.pk # Have the driver duplicate the record result = self.driver.duplicate_record(self, children) @@ -3304,7 +3305,7 @@ def sort(self, table: str, update_elements: bool = True, sort_order=None) -> Non Returns: None """ - pk = self.current.get_pk() + pk = self.current.pk if self.rows.attrs["sort_column"] is None: logger.debug("Sort column is None. Resetting sort.") self.sort_reset() @@ -4591,7 +4592,7 @@ def update_fields( # can't be changed. values = mapped.dataset.table_values() # Select the current one - pk = mapped.dataset.current.get_pk() + pk = mapped.dataset.current.pk if len(values): # noqa SIM108 # set index to pk @@ -4715,7 +4716,7 @@ def update_selectors( elif isinstance(element, sg.Slider): # Re-range the element depending on the number of records l = dataset.row_count # noqa: E741 - element.update(value=dataset._current.index + 1, range=(1, l)) + element.update(value=dataset.current.index + 1, range=(1, l)) elif isinstance(element, sg.Table): logger.debug("update_elements: Table selector found...") @@ -4742,7 +4743,7 @@ def update_selectors( # Get the primary key to select. # Use the list above instead of getting it directly # from the table, as the data has yet to be updated - pk = dataset.current.get_pk() + pk = dataset.current.pk found = False if len(values): @@ -9451,7 +9452,7 @@ def generate_where_clause(self, dataset: DataSet) -> str: for r in self.relationships: if dataset.table == r.child_table and r.on_update_cascade: table = dataset.table - parent_pk = dataset.frm[r.parent_table].current.get_value(r.pk_column) + parent_pk = dataset.frm[r.parent_table].current.pk # Children without cascade-filtering parent aren't displayed if not parent_pk: @@ -9501,7 +9502,7 @@ def delete_record(self, dataset: DataSet, cascade: bool = True): # Get data for query table = self.quote_table(dataset.table) pk_column = self.quote_column(dataset.pk_column) - pk = dataset.current.get_value(dataset.pk_column) + pk = dataset.current.pk # Create clauses delete_clause = f"DELETE FROM {table} " # leave a space at end for joining @@ -9603,7 +9604,7 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame: ] columns = ", ".join(columns) pk_column = dataset.pk_column - pk = dataset.current.get_value(dataset.pk_column) + pk = dataset.current.pk # Insert new record res = self._insert_duplicate_record(table, columns, pk_column, pk) @@ -9707,7 +9708,7 @@ def _insert_duplicate_record( def save_record( self, dataset: DataSet, changed_row: dict, where_clause: str = None ) -> pd.DataFrame: - pk = dataset.current.get_pk() + pk = dataset.current.pk pk_column = dataset.pk_column # quote columns From 1045a5613fe52aa48122908b1ad2ba0094a09b96 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 7 Aug 2023 13:22:49 -0400 Subject: [PATCH 117/121] Update pysimplesql.py --- pysimplesql/pysimplesql.py | 54 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 76a05cdc..7a3bcdea 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -418,8 +418,8 @@ def decimal_places(val: Union[int, float, Decimal], decimal_places: int): # ------- # CLASSES # ------- -# TODO: Combine _TableRow and _ElementRow into one class for simplicity -class _TableRow(list): +# TODO: Combine TableRow and ElementRow into one class for simplicity +class TableRow(list): """Convenience class used by Tables to associate a primary key with a row of data. Note: This is typically not used by the end user. @@ -439,10 +439,10 @@ def __int__(self) -> int: def __repr__(self) -> str: # Add some extra information that could be useful for debugging - return f"_TableRow(pk={self.pk}): {super().__repr__()}" + return f"TableRow(pk={self.pk}): {super().__repr__()}" -class _ElementRow: +class ElementRow: """Convenience class used by listboxes and comboboxes to associate a primary key with a row of data. @@ -640,7 +640,7 @@ def get_delete_cascade_fk_column(self, table: str) -> Union[str, None]: def get_dependent_columns(self, frm_reference: Form, table: str) -> Dict[str, str]: """Returns a dictionary of the `DataSet.key` and column names that use the - description_column text of the given parent table in their `_ElementRow` + description_column text of the given parent table in their `ElementRow` objects. This method is used to determine which GUI field and selector elements to update @@ -2205,7 +2205,7 @@ def save_record( # convert the data into the correct type using the domain in ColumnInfo if isinstance(mapped.element, sg.Combo): - # try to get _ElementRow pk + # try to get ElementRow pk try: element_val = self.column_info[mapped.column].cast( mapped.element.get().get_pk_ignore_placeholder() @@ -2748,8 +2748,8 @@ def table_values( mark_unsaved: bool = False, apply_search_filter: bool = False, apply_cell_format_fn: bool = True, - ) -> List[_TableRow]: - """Create a values list of `_TableRows`s for use in a PySimpleGUI Table element. + ) -> List[TableRow]: + """Create a values list of `TableRows`s for use in a PySimpleGUI Table element. Args: columns: A list of column names to create table values for. Defaults to @@ -2762,7 +2762,7 @@ def table_values( `DataSet.column_info[col].cell_format_fn` to rows column Returns: - A list of `_TableRow`s suitable for using with PySimpleGUI Table element + A list of `TableRow`s suitable for using with PySimpleGUI Table element values. """ if not self.row_count: @@ -2833,9 +2833,9 @@ def table_values( # resort rows with requested columns rows = rows[columns] - # fastest way yet to generate list of _TableRows + # fastest way yet to generate list of TableRows return [ - _TableRow(pk, values.tolist()) + TableRow(pk, values.tolist()) for pk, values in zip( rows.index, np.vstack((rows.fillna("").astype("O").to_numpy().T, rows.index)).T, @@ -2870,8 +2870,8 @@ def column_likely_in_selector(self, column: str) -> bool: def combobox_values( self, column_name: str, insert_placeholder: bool = True - ) -> Union[List[_ElementRow], None]: - """Returns the values to use in a sg.Combobox as a list of _ElementRow objects. + ) -> Union[List[ElementRow], None]: + """Returns the values to use in a sg.Combobox as a list of ElementRow objects. Args: column_name: The name of the table column for which to get the values. @@ -2879,7 +2879,7 @@ def combobox_values( first value. Returns: - A list of _ElementRow objects representing the possible values for the + A list of ElementRow objects representing the possible values for the combobox column, or None if no matching relationship is found. """ if not self.row_count: @@ -2901,14 +2901,14 @@ def combobox_values( parent_current_row = self.frm[rel.parent_table].current.get_original() rows.iloc[self.frm[rel.parent_table].current.index] = parent_current_row - # fastest way yet to generate this list of _ElementRow + # fastest way yet to generate this list of ElementRow combobox_values = [ - _ElementRow(*values) + ElementRow(*values) for values in np.column_stack((rows[pk_column], rows[description])) ] if insert_placeholder: - combobox_values.insert(0, _ElementRow("Null", lang.combo_placeholder)) + combobox_values.insert(0, ElementRow("Null", lang.combo_placeholder)) return combobox_values def get_related_table_for_column(self, column: str) -> str: @@ -4689,13 +4689,13 @@ def update_selectors( # TODO: Kind of a hackish way to check for equality. if str(r[e["where_column"]]) == str(e["where_value"]): lst.append( - _ElementRow(r[pk_column], r[description_column]) + ElementRow(r[pk_column], r[description_column]) ) else: pass else: lst.append( - _ElementRow(r[pk_column], r[description_column]) + ElementRow(r[pk_column], r[description_column]) ) element.update( @@ -4995,7 +4995,7 @@ def simple_transform(dataset: DataSet, row, encode) -> None: def update_table_element( window: sg.Window, element: Type[sg.Table], - values: List[_TableRow], + values: List[TableRow], select_rows: List[int], ) -> None: """Updates a PySimpleGUI sg.Table with new data and suppresses extra events emitted. @@ -5635,7 +5635,7 @@ class LazyTable(sg.Table): To use, simply replace `sg.Table` with `LazyTable` as the 'element' argument in a `selector()` function call in your layout. - Expects values in the form of [_TableRow(pk, values)], and only becomes active after + Expects values in the form of [TableRow(pk, values)], and only becomes active after a update(values=, selected_rows=[int]) call. @@ -5847,7 +5847,7 @@ def _handle_start_scroll(self) -> None: self._start_index = new_start_index # Insert new_rows to beginning - # don't use data.insert(0, new_rows), it breaks _TableRow + # don't use data.insert(0, new_rows), it breaks TableRow self.data[:0] = new_rows # to avoid an infinite scroll, move scroll a little after 0.0 @@ -6207,7 +6207,7 @@ def _on_search_string_change(self, *args) -> None: class _AutoCompleteLogic: - _completion_list: List[Union[str, _ElementRow]] = field_(default_factory=list) + _completion_list: List[Union[str, ElementRow]] = field_(default_factory=list) _hits: List[int] = field_(default_factory=list) _hit_index: int = 0 position: int = 0 @@ -7368,7 +7368,7 @@ def add_column( ) -> None: """Add a new heading column to this TableBuilder object. Columns are added in the order that this method is called. Note that the primary key column does not - need to be included, as primary keys are stored internally in the `_TableRow` + need to be included, as primary keys are stored internally in the `TableRow` class. Args: @@ -7843,7 +7843,7 @@ def accept( row, column, col_idx, - combobox_values: _ElementRow, + combobox_values: ElementRow, widget_type, field_var, ) -> None: @@ -8019,7 +8019,7 @@ def sync(self, widget, widget_type) -> None: column = e["column"] element = e["element"] if widget_type == TK_COMBOBOX and isinstance( - element.get(), _ElementRow + element.get(), ElementRow ): new_value = element.get().get_pk_ignore_placeholder() else: @@ -8806,7 +8806,7 @@ def cast(self, value, truncate_decimals: bool = None): value_backup = value if isinstance(value, int): return value - if isinstance(value, _ElementRow): + if isinstance(value, ElementRow): return int(value) try: value = self.strip_locale(value) From 043ea1226844a73c6504196adb30c59e9f6fd104 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:35:39 -0400 Subject: [PATCH 118/121] Black fix --- pysimplesql/pysimplesql.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 7a3bcdea..9bee164b 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -749,7 +749,7 @@ def has_backup(self) -> bool: ): return True return False - + @property def pk(self) -> int: """Get the primary key of the currently selected record. @@ -8018,9 +8018,7 @@ def sync(self, widget, widget_type) -> None: data_key = e["table"] column = e["column"] element = e["element"] - if widget_type == TK_COMBOBOX and isinstance( - element.get(), ElementRow - ): + if widget_type == TK_COMBOBOX and isinstance(element.get(), ElementRow): new_value = element.get().get_pk_ignore_placeholder() else: new_value = element.get() From 91840fdbfb8f76365031ab33ee9d0f7eefd5cc4a Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:43:29 -0400 Subject: [PATCH 119/121] get ruff to pass --- pysimplesql/pysimplesql.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 9bee164b..3d810bcb 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -426,6 +426,7 @@ class TableRow(list): """ def __init__(self, pk: int, *args, **kwargs) -> None: + """Initilize TableRow.""" self.pk = pk super().__init__(*args, **kwargs) @@ -450,6 +451,7 @@ class ElementRow: """ def __init__(self, pk: int, val: Union[str, int]) -> None: + """Initilize ElementRow.""" self.pk = pk self.val = val From e599862761b6faa19876f3af2c04327665f9677b Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:54:31 -0400 Subject: [PATCH 120/121] More ruff fixes (new version disallows comparing with "is" --- pysimplesql/pysimplesql.py | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 3d810bcb..10188042 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -407,7 +407,7 @@ def bool_to_checkbox( ) @staticmethod - def decimal_places(val: Union[int, float, Decimal], decimal_places: int): + def decimal_places(val: Union[float, Decimal], decimal_places: int): """Format the value to specified decimal places using the system locale.""" format_string = f"%.{decimal_places}f" if val not in EMPTY: @@ -5389,7 +5389,7 @@ def __init__(self, title: str, config: dict = None) -> None: if config is None: config = {} - if type(config) is not dict: + if not isinstance(config, dict): raise ValueError("config must be a dictionary") if set(config.keys()) - set(default_config.keys()): @@ -5405,7 +5405,7 @@ def __init__(self, title: str, config: dict = None) -> None: raise ValueError(f"{k} must contain all of {required_keys}") if "phrases" in config: - if type(config["phrases"]) is not list: + if not isinstance(config["phrases"], list): raise ValueError("phrases must be a list") if not all(isinstance(v, str) for v in config["phrases"]): raise ValueError("phrases must be a list of strings") @@ -5739,7 +5739,7 @@ def update( return # update total list - self.values = values # noqa PD011 + self.values = values # PD011 # Update current_index with the selected index self.current_index = select_rows[0] if select_rows else 0 @@ -5835,7 +5835,7 @@ def _handle_start_scroll(self) -> None: # determine slice num_rows = min(self._start_index, self.insert_qty) new_start_index = max(0, self._start_index - num_rows) - new_rows = self.values[new_start_index : self._start_index] # noqa PD011 + new_rows = self.values[new_start_index : self._start_index] # PD011 # insert for row in reversed(new_rows): @@ -5862,7 +5862,7 @@ def _handle_end_scroll(self) -> None: # determine slice start_index = max(0, self._end_index) end_index = min(self._end_index + self.insert_qty, num_rows) - new_rows = self.values[start_index:end_index] # noqa PD011 + new_rows = self.values[start_index:end_index] # PD011 # insert for row in new_rows: @@ -6666,7 +6666,7 @@ def field( "filter": filter, "quick_editor_kwargs": quick_editor_kwargs, } - if type(themepack.quick_edit) is bytes: + if isinstance(themepack.quick_edit, bytes): layout[-1].append( sg.B( "", @@ -6790,7 +6790,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.edit_protect) is bytes: + if isinstance(themepack.edit_protect, bytes): layout.append( sg.B( "", @@ -6824,7 +6824,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.save) is bytes: + if isinstance(themepack.save, bytes): layout.append( sg.B( "", @@ -6853,7 +6853,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.first) is bytes: + if isinstance(themepack.first, bytes): layout.append( sg.B( "", @@ -6887,7 +6887,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.previous) is bytes: + if isinstance(themepack.previous, bytes): layout.append( sg.B( "", @@ -6921,7 +6921,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.next) is bytes: + if isinstance(themepack.next, bytes): layout.append( sg.B( "", @@ -6955,7 +6955,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.last) is bytes: + if isinstance(themepack.last, bytes): layout.append( sg.B( "", @@ -6989,7 +6989,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.duplicate) is bytes: + if isinstance(themepack.duplicate, bytes): layout.append( sg.B( "", @@ -7023,7 +7023,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.insert) is bytes: + if isinstance(themepack.insert, bytes): layout.append( sg.B( "", @@ -7057,7 +7057,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.delete) is bytes: + if isinstance(themepack.delete, bytes): layout.append( sg.B( "", @@ -7091,7 +7091,7 @@ def actions( "Form": None, "filter": filter, } - if type(themepack.search) is bytes: + if isinstance(themepack.search, bytes): layout += [ _SearchInput( "", key=keygen.get(f"{key}search_input"), size=search_size @@ -7758,7 +7758,7 @@ def edit(self, event) -> None: expand = True if widget_type == TK_DATEPICKER: - text = dt.date.today() if type(text) is str else text + text = dt.date.today() if isinstance(text, str) else text self.field = _DatePicker( frame, self.frm[data_key], @@ -8810,9 +8810,9 @@ def cast(self, value, truncate_decimals: bool = None): return int(value) try: value = self.strip_locale(value) - if type(value) is str: + if isinstance(value, str): value = float(value) - if type(value) is float: + if isinstance(value, float): int_value = int(value) if value == int_value or self.truncate_decimals: return int_value @@ -9918,7 +9918,7 @@ def __init__( database: Union[ str, Path, - Literal[":memory:"], + Literal[":memory:"], # noqa: PYI051 sqlite3.Connection, ] = None, *, From cc64643f4634354b8441e93b540ce2ad5d3f87c1 Mon Sep 17 00:00:00 2001 From: ssweber <57631333+ssweber@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:56:09 -0400 Subject: [PATCH 121/121] black fix --- pysimplesql/pysimplesql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pysimplesql/pysimplesql.py b/pysimplesql/pysimplesql.py index 10188042..8eb7b259 100644 --- a/pysimplesql/pysimplesql.py +++ b/pysimplesql/pysimplesql.py @@ -9918,7 +9918,7 @@ def __init__( database: Union[ str, Path, - Literal[":memory:"], # noqa: PYI051 + Literal[":memory:"], # noqa: PYI051 sqlite3.Connection, ] = None, *,