From daffcadd117347fdbaafa6a3e9e8bb2cc5c9bb38 Mon Sep 17 00:00:00 2001 From: Philipp Horstenkamp Date: Mon, 1 Jan 2024 14:26:05 +0100 Subject: [PATCH] Seminararbeit DevOps & CI/CD (#486) Co-authored-by: Sebastian <94404394+SeZett@users.noreply.github.com> Co-authored-by: Tristan Nolde --- .gitignore | 1 + README.md | 16 +- .../PhHo/DevOps Loop image.png | Bin 0 -> 57975 bytes .../PhHo/costOfChangeTraditional.gif | Bin 0 -> 7067 bytes .../PhHo/dev-ops.md | 1097 +++++++++++++++++ .../PhHo/presentation}/Action-Summary.PNG | Bin .../PhHo/presentation}/Action.PNG | Bin .../PhHo/presentation}/Coverage.PNG | Bin .../PhHo/presentation}/Lint-error.PNG | Bin .../PhHo/presentation}/Pre-commit.PNG | Bin .../PhHo/presentation}/Pull_request.PNG | Bin .../presentation}/Seminarpraesentation.ipynb | 0 .../PhHo/presentation}/bohems-law.png | Bin .../project-organisation-and-dev-ops.md | 0 .../PhHo/prompts.md | 11 + .../PhHo/request-review.png | Bin 0 -> 37814 bytes .../PhHo/test-and-build.PNG | Bin 0 -> 9249 bytes documentations/index.rst | 9 +- 18 files changed, 1115 insertions(+), 19 deletions(-) create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/DevOps Loop image.png create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/costOfChangeTraditional.gif create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/dev-ops.md rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Action-Summary.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Action.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Coverage.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Lint-error.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Pre-commit.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Pull_request.PNG (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/Seminarpraesentation.ipynb (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/bohems-law.png (100%) rename documentations/{seminararbeiten/DevOps => Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation}/project-organisation-and-dev-ops.md (100%) create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/prompts.md create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/request-review.png create mode 100644 documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/test-and-build.PNG diff --git a/.gitignore b/.gitignore index 8701434..693392d 100644 --- a/.gitignore +++ b/.gitignore @@ -243,3 +243,4 @@ secrets*.json *.db remote.json requirements.txt +/documentations/seminararbeiten/DevOps/seminararbeit/dev-ops.pdf diff --git a/README.md b/README.md index e1ee93e..60fce14 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,5 @@ # aki_prj23_transparenzregister -[![python](https://img.shields.io/badge/Python-3.11-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org) -[![Actions status](https://github.com/astral-sh/ruff/workflows/CI/badge.svg)](https://github.com/astral-sh/ruff/actions) -[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) -[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) -[![Documentation Status](https://readthedocs.org/projects/mypy/badge/?version=stable)](https://mypy.readthedocs.io/en/stable/?badge=stable) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) - ## Contributions See the [CONTRIBUTING.md](CONTRIBUTING.md) about how code should be formatted and what kind of rules we set ourselves. @@ -53,7 +45,7 @@ The sqlite db is alternative to the postgres section. Alternatively, the secrets can be provided as environment variables. One option to do so is to add a `.env` file with the following layout: -``` +```dotenv PYTHON_POSTGRES_USERNAME=postgres PYTHON_POSTGRES_PASSWORD=postgres PYTHON_POSTGRES_HOST=postgres @@ -66,12 +58,14 @@ PYTHON_MONGO_PASSWORD=password PYTHON_MONGO_PORT=27017 PYTHON_MONGO_DATABASE=transparenzregister -PYTHON_SQLITE_PATH=PathToSQLite3.db # An overwrite path to an sqllite db +# An overwrite path to an sqlite db +PYTHON_SQLITE_PATH=PathToSQLite3.db PYTHON_DASH_LOGIN_USERNAME=some-login-to-webgui PYTHON_DASH_LOGIN_PW=some-pw-to-login-to-webgui -PYTHON_INGEST_SCHEDULE=12 # Every x hours +# Every x hours +PYTHON_INGEST_SCHEDULE=12 CR=ghcr.io/fhswf/aki_prj23_transparenzregister TAG=latest diff --git a/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/DevOps Loop image.png b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/DevOps Loop image.png new file mode 100644 index 0000000000000000000000000000000000000000..44f92adae4a513edb7a9d3dcca3014082929d082 GIT binary patch literal 57975 zcmbSxRajf!6KxU_9E!VJ@dPVUptwu1ws_Fu?(Xii5L&D_1&S9a7QB?;v{0-Rx8iQO z{QmdpzTWe&zkE4+X4b4(v(G*|iGQi3On^&+3jhEJR8B({5W{&4(`}H&DbV5!Xmta$zJK|@~uT-irYucPDI z(cgZdcTVmP&(VWR=(DQ_!9?`I+5P4bx@H26T0#5PpkEiDg`&`IHR$6Dv~4jurw2X1 zgTA`CSIqVz_piT48pHXOq6!eP>G@~CH`5Vm=fZjiSxVXN9hNHFLqgM~maV_Yu z1~hjF+NumK7K=vxx&Jhbo>)W6B%uuu=%6MvyZ{|sk9My_xBtAS^FbF5q8GOB>c-L1 z2@kn__ruHRYSevhKico}<+HG3r!q9L!!d`?VR|R}L-*y^X>`#LdSL$c`{FIJ-9I9y zE4zXdQi}yryIp?oRqnux?9L0btiAZ=tJhfysxCJ1pW8%|L=ld{9_u@p^5`>uCnl^ zsa>Q%0@GU2c^uXP!xe!f%_>u0{SOKGgV7DZ|JVPROY(D1EIPrVyk?9QJB^&IM1jMv z5An>0S&3xC^hb&cB^xYC5C^*Kys-biCYaMA%vWZEl1HlsW)NG4 zZPug{Vr8$1D>2GD{u0YFNf|Mm#%Z!!&K&tgX*IG-^?^vw{wdEjBewA00FnKXcgl@f z4x{O~(nbkdg=XyIv(M$tN~m@PhcaTj{9LeQye&UlcBu*ImuKvI2)&sFc~=zsT>pY` zL}o=O(p3H6_igBdkU7VRCACcV5VU6jzu1B}{QP{{1V3 zos!<#d97^RCxBz8G3#q^p>;Y9w^zDQ%({8z1ZvX)!&jwaW!}9 z1ZMq5{L}GJEVEpoYh(VE!&NP_F~_~dp_vD5)F;lcBA0#)HgF>$BC`MCa;8LAk>q_4 zw@Sf3&kOio@R#t;WRU}#P*JLW!0k>`%)y&i-y*F_e>G5~aY_Ctim`a&X?}0{%7H;_ zn#M)1k0@z(ppaBdX3N8%uTHnKr zPB83@KvMC%CXF?|PboR_SwEMHann65MM3euwHsk%CnTP^awT}1K?!ovQl z6H-W7dIuTo5L;2ne1>6wIt|?W89e#{mAxi&Y(IMsw*3W9!}KjMliD2NQn!LR{xWW& z5|@sDy|6=BCh+Fyqqjo%5QNO&k8GSt%ET7feeYDy*zw073h9cKi9gv9k60n8)jse# zegb1I9JBFR>L{4^)>lX0Q9BK>jT_?+FF`L3Ttt##*cf$&(0&zddfs-5HiB#nDk$Nm{=7iw&bo~n+XAp1H}`Xevm!Kp{sk-56`Ye zL-M%PZTcDzr)<$4M05U$8j6SPppIhg0yFn2oI7>!ViIG@5u1aEEm%nY8=3Hr*{O&* zX#N{9<%rBdBo-`>w`rsZ!W!bCvhgsTIH+u8ugqkBHc|nB{ohE%nRP|7NEv8>+d^h; zNorB3vZ@~RYJq%o{u}u5=Gb~E3y`?%&8j*HR2l|gKOQZX?BLVkIA(wHM5;7*`7FCK-k&pkmgR){8f>bwxvDNOVa;-VIf9I13F{!Sl@>1-6x z?50S1%r4|;@nw89dqxR2Z{p*RX9UR2ZjjQKf~HkgTD5zHcrcHqQ@8YHvgL z(N}NAKL?SuX-^kQS^diYymaA8_|tBV`gOcvC;{U(!}(O!8=o!L*@w#mA4VHpa$B;t zCm#J;4pt)P|O;wv!|GGb%9T6FI(fRY5i_;@?C63(ljc5mjf#ck)w$zP@08Nz-vvh6?K_549BeR0yGEZL^ZnFY&YS>PX*5C_VSTeO@H^J;W) zJWYG5y(;*6NI>AQI9i%ShW^>C!KlXzwSj-7H($=_z8sZ&!_+QM?U;Mgb!6vk!%Uhy z^?m$ztiYTjR-pxt-FZkU(Kr2y@2T`z6Uq{@mnw<(D)9%x+Q-r#3z*@zA^?xR&@%r9 zcZiNXvY|LidWv?hUAeI?wWCmaU?Hnr)uXHLw89hgM!I89C&lzLC11BL{=w@#p{Ul_ zzMrsx1?hyphKW5esLaz44-~;6MLJ1P33X#2#bwn1bwu{t%5B*-GS@(EBe$S3uT(Z5 zvg=nzmzih;^I?d`__py{eboE$+k}-;^UN5oWuIq@x?uc|z&Y%7x~_0IZqvQ2gy$e|sD8?9#jnx5&j$gnb<`gom46W3yTm zL6hvF*gKS#FQb4kUh!P|xO>`_(EbKt-``91b3rc4-Qp>~dtRp1XGOp9;*&}*~`%-5;HwqmsQuiL3 zbf)!5xL64#((C!ah@h%W3IN&G>TG#dbglcWEZf93Kxf1DPr)dLFRYLn*?;(p^IyEh zLPngZ3!kj(R%*||Wu(K1Ph+g6 z7|^6K+)t70;Bwz`tm2DK6T_ZcUTMzYPj9xBh+}lF2Le@x)_}19j;!eB?c}1l>iY)* zz;4Lo$7O=#t5Kce8eYxCak6h#v2>46-An%IUU60@=Itxg8HabE0(FWf5t@L;jOe!QY}%KO?97S)rjEM-p-ykFzmW6!LOhD( zfRi{){ZQb$&U8&&aqh~Tii|2E;9E3(^Huq|HLl*WgF>=Acc31W^#&mQTBv2xP~|W!TkJ?ITD?EY2%56Z?~0(Pf9mDE^(IK#_DVpzO)@bm=kOmvYOp+!tcmjc?(fS58c?Z z4varvD?O>DnVGeXWP2JvKHejhJRhJeMGlKC^V_%|m{Vez?=f`U@`N`6jlJO9k^~mnbrvtllV-2$75REf3Xi;YW zSH?-)I4N_~&$AS>I)mMc>czvVRc&6B-^9xowuXlg)J$U1z*`*4UxIc@UJ(L}n+=SP zi^qJ+m)=XujN3nVX4XnZ5(08;Kw#9kS6@F9oRZ46W|jWwB%Z>ZqMq~o+RzuwW~5|o zSMO6T|8IT)-vx<5gN>#-F$_sYB9Sl!T63b+=+e7cnik7cJ!Sg`Sxa-dg?!rnJk5-b z_fR=-(y`tlr_B0jS0yb`oC}e3-KE+_qQLBJ?h5%URb7J$B`)o)z2ahxlrUMfV+q?! zRj(!jYz0xkaki1U$E@SaLzk$8cd&w=CQCQC8OzQaiJZX5_p9E#K$cVdULukH%PsPe z?AfP&uWflq*psq&p(+6_@1eb5e4_+PwO$yuycf}nha$r+D!tN_c##ESE(>96f7z+_ znjqq*aDZNi3`vYERZ;l%G3C<9K+vB{aIFjuE1DlV_;2Y|7d9CzjmwM|$N&*Hc_R84}#5m>~9~ z_4W>;7GL9Q76W9pj9v$fMCmF>ris*HLa)mU#oZ*!r`7x2)Js|PE$zwqmCBbeLu8qs z4jX=PhXTL*T6_6B=%(NuxK4I4ep93%}WH>0Uch zA_$LvI~*AQj3g{rUs5W`29Ii=3_9p-b}j2)l!>4{p#71Z^(fBj-?a85O8LoTf26td zU6ieMKxHStvcIF`mQ_{@5{cFce7~Fy4?0sw_hZ}tIh;PV0Zz1F>@G~IbcGrEQ!>@# zTsrJbl7Oz_`8H#kOiCE^ND~SxKEY_Yx`Or5)XBowi0IiB`KNA^!^dGn$eT&}#YvRP z9JjaruPD~R^6ciF6c@((8!5V7B3P%&u}ht=Dibd&F-U}^A>gc6_`DT1nX{qFnM?Y~ z=i~T#a?m6TJRoB=*<2-~X@zYbr%GF>eQ(>(51h!9Po_$B_X9MFIZn3s_how6j(UP< zz=v#a2bSnJJO>fKO=G3B=u3N6pn+QpQ_skh+%t`_hiPBP+L5@wmlGhWNz&Ss*d3I( zP%fV!8jlNUBdCoO|GxN;ia=2|PhZ~OhL-BfMnN_w2@MZmlb-L7Nl5Q?=W8z`nU@il*{_00wVAbuaQuTz>^nN#?x@+LRuPnw|Az1vO>FFdGpzFK@e zy$}vi_SmBoo;6T6@5Eg;pc_&COn}nUD=XJ_ksP-J_Wc^*`RRxS!N8AYTDzuLDE7wL zO)nLTs>KVEoQL-i=iRfnol8zb@Qo>56AGqLiDe)->^l1lcKB-K)AzWa*?v)hCAJ+s zVTiGZ6So~XB+TcyD=SA7`+2gmQNgzlMyRlznOgHNGicGwBfWF*IcVs_pDl8^Jj7K` zpv~PBo9Y+;^6NG>u!WTt4}Kv$%xC=<)X(Rer8GA4+-OJQdOFIl;KRYco%LmJW;lcY z(ENaLR}<5&NdA!nC{4-xu=m(i;Cae&zbb*=F_L4#ShH;D2&Rjd=fdKfMHw z&+RpXLUNo`h2-9Q+2%tOZ^pujB%w(SfdQ;ZFzbu#mBmNbx`YOYQPm!L3og73cn}O> z7Hw6?Ahh}Dy>^6A{1jhv@_#ei9VtOdtdy?D%qX9#`Y2O^s;sce^$93d_e>-sUe9II zd%oz&4&s>V`wi>;Z$z+>fcKV(q9^i*`?f$|QT*q>DR6Kx?Ro?5BaYQglMwnxpKUh{ z>WO1Y?%72S<&f2G$AWidY6KR&{#Tr%tfQEM#ANsr5gj*>C8+K@?MFGseE;b09ZXZ{ zV;*F?93AGbgt{%}J`lk(stn25d(TNf?Bx|yhk;P$zmGVeH2m@UDBn^yf|0VQ;HLegiji6uZPTC#5J+fo|0$Lc+r)mj`&(vRt80G-H!r_4_@? zlL@(xIP%iG(o7dki%LADesZ8^44UKh`+0vZQT5DmY4!^Ks4dJbGsx?@ATgGY0Zj(1Gc=&27CRX z)*vAmg-5r&v6^=xKFkI0vUe8%tZ=#Rn*`ZM`xvsc`j(Q+u&O_?j)8r6<8Qy;?wf)E zJWcIvw0EY(j;uSkxW`~je$D;aC_KFXU@o%->oY~AnOuE>Vj zECa{ctY1}2{w_LS(gkl1P#v*XC5S{{#aSz%V~))o6-y{a7!3h+B6x6nxh7AP!lp23 zd5yZ4(_(|%@lWX*B{rhZ#EABtf7>?kCt;<$57Ac#!e7_A_EaBU0o^)6{4JCAia!`u zWE5sSo@>0_aPA1cw7&xZ^OV0;YH4SZWC`LP)@;;pU~YllM;zn8E=W|K29U;DZ6*uCN##2aH1 zn$PYjmdn|_H66v-Wp1|Sj~a;?A6O95Ut1B;wXVg!n8i8bp~CU)!niL~#bJO%?iGJ) z)#aDv>ml0gTuJ>mK)tG*s{SY}S4!7wktwCmZ2Zpsi1}M^{yNSJKt;%30~n61zp0cL zR#vH{=77%L)(09T3Hdv_t66s@xR>uS18?)l6XR9bRw&MZb&6ih|oZ#GZ> zI^t5){B+bV$Tj|0Z#4&qjxHX(K3b;2_5AnINti#0O*-W4emOksujQw@#1^l?9pwd% z?1|+}9yY^d__}{NfOD1FGvM1{fd9zrs57q(6t9}L`_G|Y4zo?LsYixF=^klK<#}(~ zu~D1-LVMrqQfBwSm>wfu3j)|bjnL_?R@vP7@`N|yzSL5>!l=jiyRxO=WC<+e>S}WT zG45l=v)tOKB&Kercxin0(<~se#(ePNncnQ5SFpNn{Sbcr1=Cjb%Q5R>0SmU{E^G~MDrQVIwKLOYvi_JNw-*F=Z_Mzlc{|w*de8ND5@7g*kk+j~& z#gsF{Q&yC)$_?%3QjOp$>0$1g9GHB7sMC(28gn~SnYvdnyU@xMT6&Th@h?)D$sGNa z(L?1g{yZmwDc4V|l^iAOAlcf=bD&Bo^&Ei3n`jz1E{@YP$mtJ2b%$PO#A_0$>zL2n z%GD&}P3Do@|8V_|b@NcNOoLFF6a=!%7=l+rGBb2` zqDEg%L0`b|u`x$4y-C!c` zk@}e8suMM;9P%W)`;)qT86pv%WmB+-ISFX;EeA@|to-C#{{7z5K`6kV#gz=a9$KpIFK$N(cDnPV* zz2ANM3m@VapJRBWNM}0s=-CZm5$7Y9HngBkuK=S?H^x{KQ);ipgDEU*Jq(=Im1!u} zFGl7Ned48XMSm;15z$66oVA&VwdvG<&%hdx#a$cm_6u<`{P#SQzaj+#DERtEF9c? z-hgJTgGoQMvIXkbJzyUfOOVV2e2hhWNw?;XA~rzF)u#SegBr;sg|drho{ew1nKTHZ z`d%FRUvehfLl{{tapd3aTRf~P6(rS~%80o_WR4SB={_-sVD}VQ$lsj!gBGo#x(lUzEe*B} zBZC@fn4M9Ylwq!Kch`+~68KB6VP+mMURzd#+9$$1mKffD;5nJJFFTc^n!m}e?RpY^ zjiK8yh-x^5x(2UD3HI$IuJ+T^>m)+E1^k!mFN!1tGjE}hN3;dtx zb#zQ=EtuCOfbAy#Aifih`PApsJs(|x{uA&OXMYH%|8T|xF-;n%V(UmZgubKkbd6qNiXM#!L|2&K^YRLr1{ z_?WV0NM@X)6Kq%JV`lgq$SZsSW+EmuJZPF^PR zp>aZMoQRGvKxg`8M&M-+cFf-(K>0mxdpQjGJvM` z7$zu_jN^t-Hbd^kFO3zb^XcNhEtYb^RGdiILB6QkpfT$hg=LH029Y`ji(srQFM6{b~ow}}~qh>EzxuU#0ycKWOz`ve5?vR$rs zn(OTlIm+g%02!>^ES0AWKD!=KB$9z^^y+nN6vo~~_Gol=li9692wp<`23pnm{cRnu z)b}L&XxRqvysQ;UrM6PLu!yLrRgN=gk`~P^g&!RYKOcX)_$8VJLG>^taak{4vlyZ^ zsHr}NwfI0G17BwvP@}aq0$EwGqBME#Has3#)F90Y z);fMhgN@O$d){xsk(O?)6)1<>t#r@VCSqHKkOvi~&~PM? z+rdRZve7WSLBXUTq`#>P4*(YgWLozckM=&d#tr?Ap=e}!bW|fhP@x`reZm)pBqGXk z85imp#msy<_QBq#PCA(iAErfK$)GjP#vs<-6*Cmjb1Ru%kJSDp0?Et1VTmZSgq@t* z_rt^d$7bezMaiDO>KoQmR+9jrN9Yb$YW_8~8~no;bQRYRVw8iYL;{njbwxslzZ$AM zn$X=~BJ6#rpxW7u=T8j<+}BH5^Y74$;mElK`<9CBaqT}3&!UiYSQu3DQI&E(!m!wh zBY|N3^kRjKVT!xyVe1Sr!n+M3y3FZK37>gBZimn|69Q&fIT8>fZv(5Q=>Efki9y4J zi*yPpJ5Q%(zEcE)rp5o7#>Ocsu0tie@WRCjF=3z*%sy7kxE=tm%d;DGdi4FjbThTY zd)vbhv&S`9EX+0E^LzbDn;10?#sJADjxP$w%D3@9n47Mv+E#yp_&1GmqJJ&azG1EVWHbCY}WP0C0f=8813 zgRrpJ*k9_(js6k2JWy#|&(Wvs%Z7G{GeY>iX^z#O-I7LWIb@6DUoq8kg9Y6+sdAC@VVVczfK7VpO9&X8Tq)GjUq%^6SE)@g@Co;#U?_I z;%32gC%ptiha*8*diAelnGNpW1!O~xL7^(Q0!cHNA|X-#^ZfL5P@5oUa%%g}nQk2| z&ZF;eF6xp6%EN?QfhjAtLS3dEQO=e$^q70(>Q?EoJ* z7~axJr}QBRhZw&9Na^p{G95NWClfVpx1YpDu1xQLu9=#_md;u6Uy90y?c__R=xKkE z15>GUBCnpgnlW>{wQA1F9x=ke(PXpphCmg$d6knY{daBM0X*0+`k8KvlK-(uhzkoO z5)BGV5@Pvu5uJ>ddJ!4jv2qtvEST{#w<)b+C7kkp`` zUGB90IlCfY@Q7zoG>uG};=^q+WC)Pq>q-QVSiv5ug#ITq3S8K7<=5)t24qDD_9GUz zc^Scv_1zw_3mpW7_{FYoQ^yOXBZlOEFT0k6N5C~ zp4;XI2*`xh$AVD#xkjUrcC;!ca1xwc;WX5${*wG-RID4osYKYC3M>%_N|GXjU)Z{s z?R-J6bC#~#v4m%m``Aia@K9r>Nd|`0p74iRQI+SL5Ilwr2ceexzv6Hd{RnuW9PaJ2 z(iBc+vv?FK#!8<8ETLdV(i#5Wyr0VeZBt`%KWu!!oKk6cq4tPT6+`tOqZ+Gh0Lxq6 z0YC;h7(sYDckI49vIhjATwaF#E;x<<363koEvP3KPKQ5*R^phywaKwIxkX!1uK{Zx z`>WN97tew}l#oyuiQmo$e;MX}S6$t)Btx^|G(4dY;18-($EbuSbP=(tY#9Tbbwl;W zL2edTNfLXf<2|yNh`+46vJgxN;`x=*V57>8lB&|98OTv@FJ9CjPJk5Hv53#q0!>XsjcSWYI zxz8jIFDmE%ne$5792uh_Jps_*V1rSm?Uk6`E->SuE?xqaaFd?wu~xDg*Grnh%U_5X zd*R2-$;QYi^L`zTHiQFB>lQ#HRCM31VgFoPev%-1t}~ElZ2w_=cWL4gEHxUBLoEH` zt#Ul5UqrrQ;78ZnS{L@~w9!hXcjMFpI@trBq-=kGwk)XOXmk(Ex#_@nGw|@q;a>*0 zs^9LHe(@|}u^;>|q)SALAO4__qWVsMv&3XB`D0whayA%Gh0nXok=X@q9Mtvmi);#x z@J}EO`^$ZYfQZAB26evkMB)iLsE!|~458`(pVeBNYyKufJQKJsf`bC#(x6c0`XmVE zkEi_otT6pBE+|3*=S9P+E;^p;B10=IrH3e*v||84FI7Yw`a7I%{!oO4mV`?3I~*ny zs-p(y?xr6UCyYi4ms0$voR63PA`nu4G?!uoGCLifgl+o8_u?#DIzjV)J8u__#fAbf zFTd!k!SfwRB5o&tCUr7yblw>mXu!F91lDBIur`?d% zb`>zqTPuI{8F&xS00a{VJTdHb)Rfz8WnAtY!ZmqS^#ezP-AN#Yw2@(b=BDt|rybbaLS`kGaB8un^OrBWZbc9ypA$fOl`~e6_Ll!M zj(T#f$u>?d>QCuwVxN-DnZLp}xX+6fUDOTGOARB?-Us@+%vT0rHkbxBX~-8kzIzJrUvvG)H%3Xz0wU3WZ%F`o{(I&v3Om7lyOp z#;<0h2>gO%?ph?nO*9IgM&i;86dQ1qwR~n?6BenK%(xn89uwQXRS6V8*c;%4x4FGZ zNVWK!yk1Cz5as>LP&WGvM*wxlkufVWL|?!5jar*94*kBJ)+L)2WJgA|MU1}QN@j|ah1a7!Hd6HcEzs33scq)`%Mi!`KS^>gKd|RJr@#f zwBP7&N;Ida*WoIn-)~BYygA?oZ+3A|KTGU!i5H5r?|V@yRr8q%_E&xI6@gNT&g^eG zd80>_p!oeiE+~++rp3IfjOR_kC8-d9RmLSs7EctnH#bD1#h^;796TnrB~OmO`6ouN zrQ5)Oi0UttOPT{DRD1{|$dW5UbUn#KkyxOQdD>>;)fbrMQv$&n#TRY8%e zw}~X6(?mtF7p@9XL#Ky*?~se18{^9U=aK#t=hx)<*~XL6wfGme%LOxb??24(gNM!A zU94-N$G>SX{nRmH+~tc0#5if`8gNlz?z@S35~$by>Y|ScJ78-#E{kap{vIVL%cnr3 zq2Gq({s%PAs!zh{1^pg&8T&W2JQ_t7V&m$UvmQlFyh}pNiO*unTPI0Rqr6#fa6(N7 za=6?$?CF|2g^kEopgr%LVZG)vM2Iv+lQ{JbPeb=9Q06;$3E6+WM^{YXSkE`&5l*_3 z2$y^0V3TJi8D(+QCYd_uCsYw>l~p#IL=t$EVwd}a3#K`^<2Wyuho_wE0{xFz;_&MM zXLfq_qvtsh#2_I!@zyg|3tP9m)*{T^kuF!uhWNhjJ7o{fR1aa2NMxh`e05tYH|=&3)I* z*PjcL%^aGMj{?$ko!cQ^``Lqt!2$Tus$j&{tl(q$2|?D$5T6h$$ve>dbl_l2p6xrE z$JF0ZAYSa}saH+QP{riAns9Nuki82R<}mv(_aRPszW7|O^9*jVs}}!C;V%=z5pTjn zkR!*i#s_<&=9LcT{JiYw3g`TeKYZ^Rig1#W;EVOiQm`} zu|{9O>xxU%UeuT2PM=mtNfV{KoC&K>n>7kK0p28Tj8381H{yl?H{HV%dYtCXzZ%0+ z{1P}|o{O!nTO4Ms0NaU3vE^gLuKUcZ_O~^Wk=eJ`7c;i+zV&qUjdjfZMvO@I@iDz8 z6jQ3~Ep$!n9Kth$8GrYhs7Wyhym?~UPgG_N`*@knM}ly8f}+cxng!dA{ce1Pi*EuD zx@gsc|I0RX-B?qeI#+*jT>aa2wh)B)iH@Y3c4?<@0dleP_hxmCh{(9&zTt$W+wGg{hpZ>{B={~)eGBF|T%o7wGV!z;emXnpRsSD8O6zcOwRcDrm0xN2AWux$|9Rwm z8PYL0y_KT(Tm6^w*WV&2e;FRTk6OCl0h=^lz)BCKjso!e+mKRZhouip_dV1C(iAMrIlw-fipr-yc390{DR zDDktlgz292pp_b~%GccRU||X7S;71FBM^%I+a=;0@L6hGLrt`@L-#C1U^S z4K@ga0?C{2MJoG{-k3R5<>Mz-_D#1+oO9wR?bS5D?IFFI`1iZgL^Bzq|2W}aXP~+T z#}Jt0%dedsKHJP#enyt|uE*(@c^YIBQEq~MLxEt03L%R1;kH2SoLu1li1B;x+JxN2 zjyc~gkP;o{gpJCE$M7gPD#9}Evz@y;eNU}NIUoLsqFlf{72+#m*u+nCdBWR9oGdid zECMY43gyowfHDLTnm2e&$lQ{~zw$ zumqgilT-XKWEceYI}2nf5xyk8g=G{`Z+@x80n-FG?#*Dq&KGjqt&v4oTT=s2n`q#R zl{c&L$)LVBLmP=vo1iK9w#PVqZ%)aP=r(j41^%2a$y%L5Of}Ca##Q z0_8UC1cUo;SpM3!q&@p$uPals%-_O9hx_$xMd~HyE2f8}R^pgoNs0_mV(O=9Gp?SK zo83{}0--&W2+y3#el>U!=Gyr<6E?mPKxpj3L>>pFCpi6C+Ud^mb7M=}PV4mIo5}Xu zDZ_xt#h~xUB8}9E+0dP$G#9F}f+WIiEWAQ>XN$;PeZj%Ud#P5BRu|WPD z)o`7lEtSS#8K9|jr#8QOu-5|q@G<4NMlhfe-F`L4EotM5E?G57 z)DU~sT^JYU_)^uv^3DPwu3asuoX4YaF{}LvJ_yo{Rfp~Q_x_3NhU232eKt};Y(M%5 z>3r&&wi#m_Fp>K^ne!Mf^1pB^+`R|lyW+jftftpUcdp|1lo<00kK8XTzFEUQf5^Yd zdEVCL6)bp?YnXJF4Ug{yBiemOB)`6aZ^ngCU2K@wRferw*2+>mVBSIW;2~yP)S33& zFuhf-;UXj|hh5d3x=Nq0wU_-rHNz*0M@>KKgNown-voMgi77nta1*l<*PL25175 zH$>YNXy8sCuiQh_;rhVJ86at*7D^ztDjJ>o0OEoT&9;rHm=%we*m_7{0iMd_*3 zmKoc@7{A9A%Kc{$TyP=2jau$mo+ha_M2g*G|fzH}+b)1p^zIZyh-Q4&}SPJ}@<7-K@>OGQiO?pb}En1^|q zixMmKK#pGY27djc0DJzOz$miQ@mue{4naZAecIv+9l9NzG;hYue-wz0u=Q6Nt|W$f zBAfc{N9ZYY0A$n3f}^0UT&t42V_F}6k_f6F5>h(+3&s=(%OO4D4uAPzS=AF%#@>V% zz4MjFCgSmlwa@U{o-a4+3+xuFDQN-w@F}r2HCdo?m}q$o5#lwAsj5?vYLx2fNT9S>W+Ub-4s_i1 zuwy~cuVdE4PuKMOa=`M@Y>eqQrylBGuvJ`oI-n`%pK7&+9ai_1-P-pZ2+EetnIM}p zoa(O<^gz1yBwR2`WfLn8FB)yQ3#^eex|GP7Cu~xu$shWd z;{4RigZm?$PbhsDw{BmZB|J8G)QyLMh;(bDiZ`a-A+!*5ny;Q=0?)`mqWr>rgKmWL zk%gGBDYSQhWskX}I>F#4k>ia-??+NNUfYRxnLkfkxAHmKB~CKFQvZI_B=r!%21`n| zPkX%|xN$is;G^Aqgq|>e2`RH%4KBbzsX&=cJKiL{8PA?$#>IC6G{VYctiEnT^WxNb z1J(VyzIJVUeJq}7ALzTG_c0BDj)zM^9Z^FeKWp9o%zMsM zJu6TyRWh@A$HK;sowo%$JS1J*Mk#DU8lG*1(#`U6p(Vm#DKX#>4P6As>C z04h~OfU2`Qj1&QNp=ygWRnggBJrGD~uf;_zc;A5x;B^pKLbn?)>uY^u%C8l{_PX<1 z&?>@26FVuusF=w zQZVL}#O@3#A)efv*?-Y#N-PfF_8)WtY}-u4(MGmV(JdIAT}49?E=z#~Q!_FTsf06o z-~SG1%qt01AxE8Cu~)SE1$^X7DV8ps$Yx?cdZ`(r>hajhDv|C^#^UT_Zzu>6K5Q;9 zqtI{;*zv*OwVDa@v6+KFM$4-{7TEVkoNGiY)HfyH9ZOvf*{E~~Q{hI!sna&#NW5%^ zIWr55&T4E9f8wNc459~|C$hL20WK9*TtC%PMA_C1kyJtr+b^@D9j{yQG~t9MgTL3D zXNdJIL~cbSL6JORID(caW^@>}7-)@Vlp}=ic=B`bkH3`a&L3g}tHGq@JAdc4QmSFT z@#%g^ka_Gujw=8+j4SfEU+6X0BK;GjbIrG+OG-HCbkl~eNuLDN2o0!o^aEqQ29Wl@ z1s>nFZVA|K5|A+ao)e?mI9})ItU6StGxpH&$VS~hZ6q{KmZ;{N2?;59+|}O>-^YSH zGs7%<pn z+;K7X&T=_Zr0yY$Sa|m!AKeDu#Ba}MeT^tRn)u3WY9sIjhc`x8yx?tyyTy7W?H^C} zKjvXLC{fWb(=~1v6b3zV`0E2KuvQS2T+jh@Rhxtcy=RM={Qf(w&C=!#OG&9sB8yok za#8wKL`=;uv3$pWDmQMnbIJ6k(+L}eHLKA)u&66x7bPRo;7Rq$a;q^jYU1sAL9ZnP=>&cwp3hL2njdTaG#R5$4-P7xYmHYv zH2x-i(LiK16VaC_2Y(0Hq?V0fN0Y%)-o_u|YgDC%$~7N7E1A(pcoYBv#0dK zqDH0iQFMcJ^{Y-R28n|y4JLl~yeD;ZtI8C+EnZ28m zY5qlLkBBwM3WafAOQ3X483*!Q#al!16fxR&eOAj#s^1||CcqyADBYF_yG=B^fMpw- z8)dU^wZu_`&u7tMVc{(b@I?#ay!wh|#WN$`DCEjESz~2yl_>&5I(y9DKbP$gA@w!Z znh^g6U@KJh%wwWiNwtV2Hfukvl=u%m`2kMh@u(MV&JVNT;&6O1bOBl zm0q0M?EiizegRfeto>OcP#Y>ojv&PwB%a>?%v@l8^2gEKQ3YJ~2ADJiwk67#SqgossbXxuxE)OfnQ`zl=I@hRM))~ z0z%Tu3^fPLk+`UjT!}deHYo)PqTV$z3h)GSK5fB5Xt)>e2R=v~Gf zFc4?oC8S#7((z;Ed4fBRRN!;OYoTsf$;hju>F4_>J6h6!Jp!dGJd<$odasRN_R{gb zB2U^Yb#PG4AP6k0h7B5S2t4QeFxs}At2zBHZ>DvUpx)6|b1AxIz@6@><%70Z7w?A1 zzl>}>Tn$2cidB475=Uf)7|}uc6+98|g|93Q>SN?UuI5(Y?c}pvjKLh`DsWcON1Ak` zB=1WA>j)Slkw_3$rEv7&Lepj%|A&Q7g?MqnxAxNB#iyHp1;Ur~)b|N^8&kO;9GMJ> zN4l1*urJqoeM@+bq5j=?s_^9w>j=dP*!v$(2gR`=91P>_3T!Y4E_}UFK&449Pvvq z6`a&cu8eCDd(;LQreLPP+dl}72>WTxA6gD)xn*9C(cxrB)EuVNHHUDO^uO+7qD*9( z>h7-Veq0ugd%WwLy{Hsirf~nKX9^;SA0MDeLf(qcy~#6?WKY9z5DL;XZ+Jy;Qe zZO`u*Bsrf~rhDWwLU%xye;JJxpQ0+qUvN7L> zp=&GlG;#G*6&9S$^H3M=T8dAyBdQqY!qd- z`zDsFR!}@NQ#uy2@ugml0r`6*8GD#&6Qf~D04BCZW!R(w?5w3RcjWi;MdII8r>=7CrtQp^=D#7;jjx}d6sg2EMz`h| zK<=!Iu$d85?D0(PW0Hkd1Nq^7|IqW0L{pbwiVZQUzaSn8;CAU-90W2buC4GlhU7Jg zdXRFK(MzCVIvRWD>(WevI|Xe6%6AykYd)IP`*VEtXtA8yLr{wuJk+MĕY9z> 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≦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 literal 0 HcmV?d00001 diff --git a/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/costOfChangeTraditional.gif b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/costOfChangeTraditional.gif new file mode 100644 index 0000000000000000000000000000000000000000..a9330c9506da2b9825aa4bf131f689dded9bb932 GIT binary patch literal 7067 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 requirements.txt` aufgezeichnet. +Dabei gibt es aber keinerlei garantien das die so aufgezeichneten Abhängigkeiten auch zueinander passen. +Indirekte Abhängigkeiten sind die Abhängigkeiten von Abhängigkeiten. + +Trotz ihrer Einfachheit wird die `requirements.txt` oft als Export- oder Zwischenprodukt genutzt. + +Eine beispielhafte `requirements.txt` für die beliebte Bibliothek `pandas` könnte wie folgt aussehen: + +```requirements +meson-python==0.13.1 +meson==1.2.1 +wheel +Cython==3.0.5 # Note: sync with setup.py, environment.yml and asv.conf.json +numpy>1.22.4,<=2.0.0.dev0 +versioneer[toml] +``` + +Es ist wichtig zu wissen, dass eine `requirements.txt` noch komplexer ausfallen kann, beispielsweise mit Hashwerten der + Abhängigkeiten und Bedingungen für deren Gültigkeit sowie Verweisen auf zusätzliche Paketquellen. +Diese Faktoren erschweren die Wartung der `requirements.txt`. +Der Name `requirements.txt` ist dabei eine starke Konvention, aber nicht verpflichtend. + +#### `pip-tools` + +`pip-tools` ist ein Python-Paket, das das manuelle Erstellen und Pflegen von Abhängigkeiten vereinfacht. +Es nutzt eine `requirements.in`, in der nur die direkten Abhängigkeiten aufgeführt werden. +Mit dem Befehl `pip-compile` kann aus der `requirements.in` eine aktuelle `requirements.txt` generiert werden, die alle Abhängigkeiten erfüllt. +Diese ist einfacher zu warten als eine rohe `requirements.txt`. +`pip-tools` bietet zudem einen Befehl, der installierte Pakete mit einer `requirements.txt` synchronisiert. +Dies ermöglicht ein einfacheres Arbeiten mit Paketen als mit `pip` allein. +Der Vorteil zeigt sich besonders in Teamumgebungen oder bei der Entwicklung auf mehreren Geräten. + +Da `requirements.in` und `requirements.txt` nur starke Konventionen sind, können zusätzlich zu den Anwendungsabhängigkeiten + auch Entwicklungsabhängigkeiten gepflegt werden. + +Indirekte Abhängigkeiten können bei mehreren direkten Abhängigkeiten anders ausfallen, was zu unerwartetem Verhalten der Software führen kann. +Ein Lösungsansatz hierfür ist die Erweiterung von `pip-tools` namens `pip-tools-multi`. +Üblicherweise werden die `requirements.txt` Dateien mit den gelösten Abhängigkeiten im Git verwaltet. + +#### `pip-compile-multi` + +`pip-tools-multi` ermöglicht es, Lösungen für mehrere Gruppen direkter Abhängigkeiten zu definieren. +So kann eine `base.in` die direkten Laufzeitabhängigkeiten einer Software auflisten. +Inhalte anderer `*.in`-Dateien können vererbt werden. +Ein Bündel von `*.in`-Dateien erhält eine Gesamtlösung, aus der diverse `*.txt`-Dateien im `requirements.txt`-Stil automatisch generiert werden. +Damit lassen sich zusätzliche Pakete für Testen, Linten und Entwickeln pflegen, ohne die Laufzeitumgebungspakete zu beeinflussen. +Die generierten `*.txt`-Dateien mit den gelösten Abhängigkeiten werden üblicherweise im Git verwaltet. + +#### `poetry` + +Das neueste Werkzeug zur Verwaltung von Python-Abhängigkeiten ist `poetry`. +Aktuell ist Poetry "Stand der Technik" und wird in vielen aktiv entwickelten Projekten genutzt. + +##### `pyproject.toml` + +Anders als die bisher vorgestellten Lösungen nutzt `poetry` die `pyproject.toml` zur Verwaltung von Abhängigkeiten. +Die `pyproject.toml` wurde in [PEP 518](https://peps.python.org/pep-0518/) vorgestellt und in den PEPs [517](https://peps.python.org/pep-0517/), + [621](https://peps.python.org/pep-0621/) und [660](https://peps.python.org/pep-0660/) weiter ausgeführt. +Ziel der `pyproject.toml` ist es, eine einzelne Datei zu definieren, in der alle Konfigurationen eines Python-Projekts enthalten sind, + einschließlich Build- und Abhängigkeitssystem. + +#### Abhängigkeitsverwaltung mit `poetry` + +Der Befehl `poetry new project-name` erstellt eine `Poetry`-Projektstruktur. +Diese umfasst `pyproject.toml`, `README.md`, ein Paket und einen Testordner. + +Mit `poetry init` lässt sich ein bestehendes Projekt um eine `poetry`-Sektion erweitern. + +Dadurch entsteht in der `pyproject.toml` eine grundlegende Konfiguration. +Für Ergänzungen und detaillierte Optionen verweise ich auf die umfangreiche Dokumentation unter [python-poetry.org](https://python-poetry.org/docs). + +Die `pyproject.toml` enthält nun Sektionen, die Laufzeit-, Test- und Entwicklungsabhängigkeiten in frei definierbaren Gruppen deklarieren. + +Ein Beispiel hierfür könnte wie folgt aussehen: + +```toml +[tool.poetry] +authors = ["AKI Projektgruppe 23"] +classifiers = [] +description = "Some describing Text!" +documentation = "https://some-url.eu/" +homepage = "https://some-url.eu/" +keywords = ["deutschland", "economy", "transparenzregister", "dataintegration", "handelsregister"] +maintainers = [] +name = "aki-prj23-transparenzregister" +packages = [{include = "aki_prj23_transparenzregister", from = "src"}] +readme = "README.md" +repository = "https://github.com/fhswf/aki_prj23_transparenzregister" +version = "0.1.0" + +[tool.poetry.dependencies] +pandas = "^2.0.0" + +[tool.poetry.group.develop.dependencies] +black = {extras = ["jupyter"], version = "*"} +jupyterlab = "*" +pre-commit = "*" + + +[tool.poetry.group.lint.dependencies] +black = "*" +mypy = "*" +pandas-stubs = "*" +pip-audit = "*" +pip-licenses = "*" +ruff = "*" + +[tool.poetry.group.test.dependencies] +pytest = "^7.4.2" +``` + +Mit den Befehlen `poetry update` oder `poetry lock` wird aus der `pyproject.toml` eine `poetry.lock`-Datei generiert. +Diese Datei erfüllt eine ähnliche Funktion wie die `requirements.txt`, zeichnet aber zusätzlich den Abhängigkeitsbaum auf welcher in der `poetry.lock` mit aufgezeichnet wird. +So bietet sie eine übersichtliche Verwaltung der direkten und indirekten Abhängigkeiten. + +[Die vollständige Dokumentation der Poetry Sektion über Abhängigkeiten der Pyproject.toml kann auf https://python-poetry.org/docs/managing-dependencies/ gefunden werden.](https://python-poetry.org/docs/managing-dependencies/) + +#### Poetry cheat sheet + +1. `poetry install` (`--with`, `--without`, `--only`)\ + **Funktionalität**: Installiert die Abhängigkeiten, die in der pyproject.toml Datei aufgelistet sind. + - `--with`: Ermöglicht das explizite Hinzufügen von optionalen Gruppen von Abhängigkeiten zur Installation. + - `--without`: Schließt bestimmte Gruppen von Abhängigkeiten von der Installation aus. + - `--only`: Installiert ausschließlich die angegebenen Abhängigkeitsgruppen und ignoriert alle anderen. + +2. `poetry update`\ + **Funktionalität**: Aktualisiert die Abhängigkeiten eines Projekts auf ihre neuesten verfügbaren Versionen, basierend auf den Einschränkungen, die in der pyproject.toml-Datei definiert sind. + Details: + - Bei der Ausführung überprüft poetry update, ob es neuere Versionen der in pyproject.toml definierten Paketabhängigkeiten gibt, die mit den spezifizierten Versionseinschränkungen kompatibel sind. + - Es aktualisiert die `poetry.lock`-Datei, um diese neuen Versionen widerzuspiegeln, wodurch sichergestellt wird, dass bei zukünftigen Installationen mit poetry install genau diese Versionen verwendet werden. + - Man kann spezifische Pakete für die Aktualisierung auswählen, indem man sie als Argumente hinzufügt (z.B. poetry update flask aktualisiert nur Flask und seine Abhängigkeiten). + Keine zusätzlichen Optionen: Der Befehl hat keine weiteren Modifikatoren oder Optionen. + - Je nachdem wie die Version festgelegt wurde, werden Updates auf [*patches* (`~1.2.3` => `>=1.2.3 < 1.3.0`)](https://python-poetry.org/docs/dependency-specification/#tilde-requirements), oder [*minor* (`^1.2.3` => `>=1.2.3 <2.0.0`)](https://python-poetry.org/docs/dependency-specification/#caret-requirements) Versions begrenzt . + +3. `poetry add` (`--group`)\ + **Funktionalität**: Fügt eine neue Abhängigkeit zum Projekt hinzu. + - Option --group: Mit dieser Option kann die Abhängigkeit einer spezifischen Gruppe zugeordnet werden, z.B. development oder testing. Dies ist nützlich, um Abhängigkeiten, die nur in bestimmten Umgebungen benötigt werden, separat zu verwalten. + - Versionsrestriktionen können mit einem `@` an die Abhängigkeit angefügt werden. + Diese sind Standardmäßig die aktuelle Version bis zur nächsten Hauptversion in der Zirkumflex-Schreibweise (`^1.2.3`). + +4. `poetry remove`\ + **Funktionalität**: Entfernt eine bestehende Abhängigkeit aus dem Projekt. + +5. `poetry build`\ + **Funktionalität**: Erstellt das Paket für das Projekt. + Dies umfasst typischerweise die Erstellung von wheel- und sdist-Archiven. + Keine zusätzlichen Optionen: Dieser Befehl kompiliert das Projekt in ein Format, + das auf PyPI oder anderen Paket-Indizes veröffentlicht werden kann. + +6. `poetry publish`\ + **Funktionalität**: Veröffentlicht das Paket auf einem Paket-Index wie PyPI. + Wichtige Optionen: + - `--repository`: Gibt das Repository an, auf das das Paket hochgeladen werden soll. + - `--username`, `--password`: Für die Authentifizierung bei einem privaten Repository. + - `--build`: Führt automatisch poetry build vor dem Veröffentlichen aus. + +7. `poetry lock`\ + **Funktionalität**: Locked die dependencies wie `poetry update` aber ohne diese Direkt zu installieren. + +[Die vollständige Dokumentation aller Befehle kann auf https://python-poetry.org/docs/cli/ gefunden werden.](https://python-poetry.org/docs/cli/) + +#### Cython + +Poetry unterstützt das Bauen von Cython-Modulen, + obwohl diese Unterstützung nur inoffiziell ist und keine offizielle Dokumentation existiert. +In unserem Projekt ist der Einsatz von Cython jedoch nicht vorgesehen. + +#### Extras + +`poetry` bietet die Möglichkeit, Extras zu definieren. +Extras sind Abhängigkeitsgruppen, die bei Bedarf installiert werden. +Dabei wird die Gesamtlösung der Abhängigkeiten über alle Gruppen hinweg erstellt. +Die direkten und indirekten Abhängigkeiten werden nur installiert, wenn eine spezifische Extragruppe bei der Installation eines Python Wheels angefordert wird. +Bei der untenstehenden Konfiguration würde `pip install ` lediglich `pandas` als Abhängigkeit installieren. +Der Befehl `pip install [ai]` würde zusätzlich `tensorflow` und alle dessen indirekten Abhängigkeiten installieren. + +```toml +[tool.poetry.dependencies] +pandas = "^2.0.0" +tensorflow = "^2.10.0" + +[tool.poetry.extras] +ai = ["tensorflow"] +``` + +Natürlich können mehrere Extragruppen konfiguriert werden. + +#### Script Entry Points + +In der `pyproject.toml` können Einstiegspunkte für das Skript definiert werden. +Diese werden bei der nächsten Ausführung von `poetry install` erstellt. +Auch ein kompiliertes Python-Wheel erstellt diese Einstiegspunkte während der Installation. +Die Definitionen für Einstiegspunkte können wie folgt aussehen: + +```toml +[tool.poetry.scripts] +start-program = "package.module:method" +``` + +Mehrere Einstiegspunkte können in der Konfiguration definiert werden. +Nach der Installation über `poetry install` oder die Installation eines fertigen `wheels` steht der `start-program`-Einstiegspunkt zur Verfügung. +Dieser führt die Methode `method` aus dem Paket `package` und dem Modul `module` aus. +Ist das Python-`script`-Verzeichnis im PATH gelistet, steht das Kommando auch global zur Verfügung. + +### Linter + +Der Begriff `Lint` leitet sich vom englischen Wort für "Fussel" ab und bezeichnet die statische Codeanalyse. +Hierbei wird der Programmcode auf Schwachstellen, Fehler, Sicherheitslücken oder Verstöße gegen Codekonventionen analysiert, ohne ihn auszuführen. + +In Python, einer nicht kompilierten und schwach typisierten Sprache, werden viele Fehler erst bei der Ausführung sichtbar. +In Compilierten und streng typisierten Programmiersprachen werden viele Probleme schon in der Compilezeit sichtbar. +Einige Fehler treten nur unter seltenen Umständen auf. +Daher ist die strukturelle Analyse des Codes in Python besonders wichtig. + +Es gibt verschiedene Programme für Python, die unterschiedliche Analysen durchführen. +Im Folgenden werden die aktuell wichtigsten Werkzeuge vorgestellt. + +#### `mypy` + +`mypy` ist ein von der Python Foundation gewartetes Werkzeug, das die Typisierung in Python statisch überprüft. +Obwohl Python eine schwach typisierte Programmiersprache ist, überwiegen in größeren Projekten oft die Nachteile dieser Eigenschaft. +Die Einführung einer Typisierung wird dann sinnvoller, insbesondere wenn Teammitglieder mit Funktionen und Klassen arbeiten, die von anderen entwickelt wurden. +Das Fehlerpotenzial durch unzulässige oder ungetestete Zuweisungen kann durch optionale Typisierung in Python verringert werden, besonders effektiv in Kombination mit einem Linter wie `mypy`. +`mypy` führt eine statische Code-Analyse durch und identifiziert widersprüchliche Zuweisungen sowie unzulässige Argumente, wobei Lösungshinweise oder Fehlerbeschreibungen bereitgestellt werden. + +##### Typisierung in Python + +In Mypy erhalten Variablenzuweisungen, wenn bestimmbar, den Typ der ersten Zuweisung. +Der Stil der Typisierung hat sich in fast jeder der letzten Python-Versionen geändert, daher wird hier nur auf die Typisierung wie in Python 3.11 implementiert eingegangen. +Die folgenden beiden Zuweisungen sind sowohl in der Zuweisung als auch im Typ für Mypy identisch: + +**Variablenzuweisung**: +```python +u_number = 5 +t_number: int = 5 +``` + +Ein Funktionskopf kann beispielhaft wie folgt typisiert werden: + +**Funktionskopf**: +```python +def some_funtion_name( + arg1: int, + arg2: list[int | SomeClass | None] = None + ) -> OtherClass: + # ... + # do something + # ... + return OtherClass() +``` + +Typisierung bietet neben der Dokumentation weitere Vorteile. +Sie unterstützt die Autovervollständigung in IDEs wie PyCharm, Visual Studio Code oder Jupyter. +Zudem enthalten Typisierungen Informationen, die von Language Models (LLMs) genutzt werden können. +Diese Modelle bearbeiten, vervollständigen, dokumentieren oder erklären Code unter Berücksichtigung der Typisierung. +Vor allem aber gibt die Typisierung werkzeugen wie `mypy` anhaltspunkte darüber welche Typen für einen Wert bzw. Rückgabewert erwartet werden. +Dies kann dan über statische Analysen abgeglichen werden. + +`mypy` ist nicht immer in der Lage, Typen korrekt zu inferieren. +Dies kann an unvollständigem Code liegen. +Ist der Code jedoch korrekt, kann der Kommentar `# type: ignore` verwendet werden, um die Typüberprüfung für einzelne Zeilen zu unterdrücken. +Die Unterdrückung der Typisierungsprüfung sollte nur erfolgen, wenn man sich sicher ist, dass dies notwendig ist. + +#### `ruff` + +`ruff` ist eine Neuentwicklung in Rust, basierend auf verschiedenen anderen Werkzeugen. +Es folgt eine unvollständige Liste wichtiger Linter, die in `ruff` reimplementiert wurden: +- `flake8`: Dieses Werkzeug überprüft Quellcode auf die Einhaltung des PEP 8-Stils, Programmierfehler und Komplexität. Es kombiniert verschiedene Tools wie PyFlakes, pycodestyle und McCabe. +- `isort`: Sortiert Importe in Python-Dateien. Isort automatisiert die Sortierung und Trennung von Importen in Abschnitte sowie deren alphabetische Anordnung zur Verbesserung der Lesbarkeit. +- `pylint`: Ein umfassendes Werkzeug, das nach Programmierfehlern sucht, den Code-Stil zu standardisieren versucht und die Refaktorisierung von Code vorschlägt. Pylint bietet detaillierte Berichte über potenzielle Codeprobleme. +- `bandit`: Speziell für die Sicherheitsprüfung von Python-Code entwickelt. Es durchsucht den Code nach häufigen Sicherheitslücken und warnt bei potenziell unsicheren Konstrukten. +- `flake8`-Plugins: `ruff` implementiert auch einige gängige flake8-Plugins. Diese Plugins erweitern flake8 um zusätzliche Prüfungen und Funktionen, die über die Standardfunktionen hinausgehen. + +Im Gegensatz zu `flake8`, `bandit` und `pylint` kann `ruff` einfache Korrekturen selbstständig durchführen. +Dies führt dazu, dass Nutzern manche Regeln möglicherweise nicht bewusst werden, aber sie werden nicht von unwichtigen Problemen aufgehalten. +`ruff` wird in der `pyproject.toml` konfiguriert, wo einzelne Regelsätze und individuelle Regeln aktiviert oder deaktiviert werden können. +Die Beschleunigung gegenüber bekannten Lintern beträgt etwa 1000%, was auch das Problem der Pluginhölle und der Mehrfachausführung verschiedener Tools beseitigt. + +Wichtig zu wissen ist, dass `ruff` Dateien isoliert analysiert, im Gegensatz zu anderen Programmen. +Dies führt manchmal zu Fehlern, wie dem Nichterkennen vererbter Docstrings über Dateigrenzen hinweg, was zu fehlerhaften Erkennungen führen kann. + +Tools wie `ruff` sind besonders nützlich für Python-Einsteiger, da sie viele stilistische Details im Code verfeinern und Verbesserungspotentiale hervorheben. +So werden Styleguides wie PEP-8 nach und nach erschlossen, ohne Nutzer mit umfangreichen Informationen zu überfordern. + +`ruff` Fehlercodes können durch den `# noqa` Kommentar lokal unterdrückt werden. +Es ist jedoch besser, die Unterdrückung spezifisch mit `# noqa: F1, F2` durchzuführen, um z. B. die Fehlercodes F1 und F2 zu unterdrücken. +Die alleinige Verwendung von `# noqa` unterdrückt alles und ist daher **nicht** empfohlen. +Die Fehlercodes werden bei der Ausführung von `ruff` angezeigt. + +#### `black` + +`black` ist ein Linter, der sich ausschließlich um die menschliche Lesbarkeit des Codes kümmert. +Er formatiert Whitespace nach immer gleichen Mustern. +Dadurch sieht der Code konsistent aus, was mehrere Ziele erfüllt: + +1. Der Programmierer muss sich nicht um die Formatierung kümmern. +2. Andere Programmierer müssen sich nicht an unterschiedliche Formatierungen gewöhnen. +3. `black` lässt sich kaum konfigurieren, sodass alle damit formatierten Projekte gleich aussehen. Die somit erreichte konsistenz reduziert den mentalen Overhead beim Lesen und erhöht somit das Verständnis. + +Für einen tieferen Einblick in die Philosophie von `black` empfiehlt sich [der Vortrag des Autors auf der PyCon](https://www.youtube.com/watch?v=esZLCuWs_2Y). +Black hat sich als inoffizieller Standard für die Python-Codeformatierung etabliert. + +#### pip-audit + +`pip-audit` ist ein OWASP-Abhängigkeitsscanner für Python. +Er scannt installierte oder in einer `requirements.txt` festgelegte Abhängigkeiten auf bekannte Sicherheitslücken. +Es ist redundant, wenn GitHubs Dependabot verwendet wird. +Eine kostenpflichtige Alternative ist das `safety`-Tool, das schneller Sicherheitslücken aktualisiert und möglichkeiten zur Korrektur durch automatische Updates bietet. +Auch `safety` ist redundant, wenn Dependabot genutzt wird. + +#### pip-licenses + +`pip-licenses` listet und analysiert die Lizenzen der verwendeten Abhängigkeiten. +Es kann überprüfen, ob nur Software mit erlaubten oder nicht verbotenen Lizenzen verwendet wurde. +Die Liste der erlaubten und verbotenen Lizenzen ist frei konfigurierbar. +Für akademische Projekte ist dies weniger wichtig und daher nicht implementiert, wird aber der Vollständigkeit halber erwähnt. +In einem Corporate/Compliance Kontext ist dies aber natürlich von Bedeutung. + +### pytest + +Um die anfängliche Funktionalität und Stabilität von Programmteilen sicherzustellen, ist es sinnvoll, Tests zu schreiben, auch im Hinblick auf spätere Modifikationen! +Diese vergleichen die Rückgabewerte von Funktionen mit Erwartungswerten. +Python wird mit einem integrierten Test-Framework ausgeliefert, oft wird jedoch `pytest` aufgrund der einfacher Syntax verwendet. + +Hier ein einfaches Beispiel für Code und Test: + +```python + +def addition(a: int, b: int) -> int: + """Die Funktion welche zu testen ist.""" + return a + b + + +def test_addition() -> None: + """Ein Test für die Funktion addition.""" + assert addition(1, 5) == 6 +``` + +Tests werden üblicherweise im `tests`-Verzeichnis angelegt. +`pytest` durchsucht dieses Verzeichnis nach Dateien, die auf `*_test.py` enden, und registriert darin alle Funktionen, die mit `test` beginnen, als Tests. +Diese Tests werden dann nacheinander ausgeführt. +Nicht alle Tests sind so einfach wie das oben dargestellte Beispiel. +Oft müssen mehrere Schritte ausgeführt werden. +Es ist jedoch ratsam, Funktionen unabhängig und kleinschrittig in einzelnen Tests zu überprüfen. +Das führt zu besserer Übersichtlichkeit, besonders wenn Tests fehlschlagen da so eine Gesamtübersicht über das Fehlerverhalten vorliegt. + +Einige Abhängigkeiten bieten eigene assert-Funktionen. +Zum Beispiel enthält `pandas` Funktionen wie `pandas.testing.assert_frame_equal`, `pandas.testing.assert_series_equal` und `pandas.testing.assert_index_equal`, die `pandas`-Objekte vergleichen und verschiedene Assertions durchführen. +Meine persönliche Präferenz ist es jedoch, wenn möglich, `pandas`-Objekte in Dictionaries zu konvertieren und diese dann mit `assert` zu vergleichen, da dabei fehlschlagende Vergleiche aussagekräftiger sind. +Dies ist jedoch eine persönliche Präferenz. +Auch `numpy` enthält ein `testing`-Unterpaket. + +Globale Konfigurationen oder Code, der vor dem ersten Test ausgeführt werden soll, können in der `conftest.py` im `tests`-Ordner abgelegt werden. + +#### Setup und Teardown + +Manche Tests erfordern einen Aufbau (`setup`) und ein nachfolgendes Aufräumen (`teardown`). +Dies wird auch als "setup and teardown" bezeichnet. +In `pytest` erfolgt dies über Fixtures. + +Fixtures sind Funktionen, die mit dem `pytest.fixture`-Decorator versehen werden. +Dieser wandelt eine Funktion in eine Fixture um. +Zusätzlich können Einstellungen vorgenommen werden, wie beispielsweise die Nutzungsdauer und der Geltungsbereich der Fixture. + +Beispiele für die Nutzung von Fixtures sind: +- Erstellung und Löschung von Dateisystemen. +- Erstellung von Datenbank-Sessions oder Datenbanken (SQLite). +- Überschreiben von Funktionen und Umgebungsvariablen. +- *Mocken* von Netzwerkverbindungen/Sessions. + +Eine Fixture teilt sich in einen Setup-, einen Teardown-Teil und `yield` auf. +Im Setup-Teil werden Vorbereitungen für ein Testelement durchgeführt. +Im Teardown-Teil wird dieses Element aufgeräumt, falls notwendig. +`yield` trennt Setup und Teardown. +`yield` übergibt ein Objekt oder einen Wert an den Test und pausiert die Ausführung der Fixture, bis die Tests im Scope abgeschlossen sind. + + +Der Teardown wird nach Abschluss der Tests durchgeführt. +Das garantiert, dass auch bei fehlschlagenden oder nicht durchführbaren Tests aufgeräumt wird. + +Wird kein Teardown benötigt, wird konventionell `return` anstelle von `yield` verwendet. + +Fixtures können auf anderen Fixtures aufbauen. + +Eine Fixture wird verwendet, indem der Methodenname als Argument einem Test hinzugefügt wird oder `autouse=True` im Decorator gesetzt wird. +Alternativ kann der `pytest.mark.usefixtures("fixturename")`-Decorator genutzt werden. + +```python +from typing import Generator + +import pytest + +from sqlalchemy.orm import Session + +@pytest.fixture(autouse=False, scope="function") +def create_test_sql() -> Generator[Session, None, None]: + # create_test_sql_table + # create sql connection + yield sql_session + # delete sql connection + # delete sql tables + +def test_sql_table(create_test_sql: Session) -> None: + # executes a test on the test db + assert create_test_sql.query(HelloWorldTable).get("hello") == "world" +``` + +Ein Test kann mehrere unabhängige Fixtures nutzen. +Fixtures, die für alle Tests verfügbar sein sollen, werden in der `conftest.py` im `tests`-Ordner abgelegt. +Eine in der `conftest.py` definierte Fixture muss in Tests nicht importiert werden. + +Für schnelle und performante Datenbanktests wird empfohlen, diese als In-Memory-Datei anzulegen. +Dies geschieht bei SQLite mit dem Pfad `sqlite:\\\:memory:`. + +Fixtures, die den `yield`-Operator verwenden, haben den Rückgabetyp `Generator[SomethingYielded, None, None]`. +Fixtures ähneln Kontextmanagern. + +#### Mock + +In manchen Situationen müssen Softwareteile für effiziente Tests überschrieben werden. +Beispiele hierfür sind Netzwerkverbindungen und redundante, zeitaufwendige Berechnungen. + +Mocks werden in Fixtures oder innerhalb von Kontextmanagern verwendet. +Dies kan auch in Fixtures kombiniert werden, um Wiederverwendbarkeit zu fördern. +Natürlich ist es sinnvoll mehrere abhängige Mocks in einer Fixture zusammenzufassen. + +##### Beispiel `pytest-mock` + +In manchen Fällen ist es nicht ausreichend, nur das Ergebnis einer Anfrage zu überprüfen. +Stattdessen muss untersucht werden, welche internen Funktionen aufgerufen werden. +`pytest-mock` kann hierbei verwendet werden. +Mit `pytest-mock` lassen sich Funktionsaufrufe und deren Ergebnisse aufzeichnen und anschließend analysieren. +Dies ermöglicht beispielsweise Aussagen darüber, ob Caching-Operationen erfolgreich waren. + +```python +class Foo(object): + def bar(self, v): + return v * 2 + +def test_spy_method(mocker) -> None: + foo = Foo() + spy = mocker.spy(foo, 'bar') + assert foo.bar(21) == 42 + + spy.assert_called_once_with(21) + assert spy.spy_return == 42 +``` + +##### Beispiel `requests-mock` + +Bei der Verwendung einer Anfrage (`request`) innerhalb eines Tests hängt der Test von der korrekten Serverantwort ab. +Es ist meist sinnvoller, solche Anfragen zu überschreiben. +Dieser Ansatz ist zuverlässiger, deterministischer und schneller. +Interaktionen mit anderen Komponenten eines Softwarekomplexes werden in Integrationstests behandelt nicht in Unit-Tests. + +```python +import requests +import requests_mock + +def test_mock_requests() -> None: + with requests_mock.Mocker() as m: + m.get('http://test.com', text='data') + assert requests.get('http://test.com').status_code == 200 +``` + +##### Beispiel `MonkeyPatch` + +Pytest's `MonkeyPatch` kann genutzt werden, um viele Werte temporär zu überschreiben, wie zum Beispiel Umgebungsvariablen und Funktionen. +Das folgende Beispiel zeigt, wie eine Umgebungsvariable für die Dauer eines Tests überschrieben wird. + +```python +import os + +def test_partial(monkeypatch): + assert not os.getenv("SOME_ENV_VAR") + monkeypatch.setenv("SOME_ENV_VAR", "SOME_VALUE") + assert os.getenv("SOME_ENV_VAR") == "SOME_VALUE" +``` + +#### `parametrize` + +Oft ist es sinnvoll, einen Test mit verschiedenen Werten durchzuführen. +Dafür ist eine For-Schleife ungeeignet, da ein Test bei der ersten fehlschlagenden Assertion abbricht. +Eine Möglichkeit, die Code-Duplikation vermeidet, ist die Auslagerung der Schleife mit dem `pytest.mark.parametrize`-Decorator. + +```python +import pytest + +@pytest.mark.parametrize( + ("a", "b"), + [(2, 4), (3, 9), (4, 6)], +) +def test_sqr(a: int, b: int) -> None: + assert a**2 == b + + +def test_sqr_wrong() -> None: + for a, b in [(2, 4), (3, 9), (4, 6)]: + assert a**2 == b +``` + +Der `pytest`-Decorator erwartet zuerst ein Tupel der Argumentnamen und als Zweites eine Liste der Wertetupel welche später Entpackt werden. +Bei nur einem Parameter kann dieser direkt als String benannt werden. +Mehrere `parametrize`-Decorator können verwendet werden, um ein mehrdimensionales, unabhängiges Parameterfeld aufzuspannen. +`parametrize` kann zusammen mit Fixtures verwendet werden. + +#### `pytest.raises` + +Manchmal ist es notwendig zu prüfen, ob ein Fehler korrekt ausgelöst wird, anstatt auf einen Rückgabewert zu achten. +`pytest` bietet dafür den `pytest.raises` Kontext an. +Dieser prüft, ob ein bestimmter Fehler ausgelöst wird und ermöglicht das druchführen weiterer Validierungen in diesem Kontext. +```python +import pytest + +def i_raise_an_error() -> None: + raise ValueError("Happy to provide an example!") + +def test_value_error_raise(): + with pytest.raises(ValueError, match="Happy"): + i_raise_an_error() +``` + +Der Beispielcode überprüft, ob eine Funktion einen ValueError auslöst. +Zusätzlich wird geprüft, ob der Fehler die Zeichenkette "Happy" enthält. +Schlägt der Test fehl, liegt es daran, dass der Fehler nicht wie erwartet ausgelöst wird oder das Wort "Happy" nicht enthält. + +#### Code-Coverage + +Beim Testen ist es wichtig, einen Überblick darüber zu haben, welche Codebereiche bereits getestet sind und welche noch nicht. +Eine Möglichkeit, dies zu tun, ist die Verwendung von Code-Coverage. +Dieses Tool zählt, wie oft jede Codezeile beim Testen ausgeführt wird und überprüft, welcher Verzweigung im Code gefolgt wird. +Das Coverage lässt sich als Metrik (meist in Prozent) darüber welcher anteil des Codes beim Testen ausgeführt wurde darstellen oder visuell aufbereiten, um aufzuzeigen, wo noch Tests fehlen. +In einigen IDEs wird dies durch farbliche Hervorhebungen umgesetzt. +Es ist nicht unüblich, die Code-Coverage über Tools wie SonarQube anzuzeigen und Änderungen zu verfolgen. + +Es ist wichtig, die Grenzen dieser Metriken und Markierungen zu kennen: +1. Code-Coverage zählt die Ausführungen pro Codezeile, nicht die sinnvolle Prüfung von Rückgabewerten oder Funktionsergebnissen. +2. Code-Coverage existiert auch, wenn nur die äußerste Methode getestet wird, aber es ist ökonomischer, erst innere Funktionen einzeln zu testen. +3. Je kleiner der zu testende Codeabschnitt, desto schneller der Test. +4. Jede Abzweigung benötigt mindestens einen weiteren Test. Verschachtelte Abzweigungen sind multiplikativ. + Im Englischen spricht man dabei vom *Condition Coverage* oder *Branch Coverage*. +5. Eine Code-Zeile ist ein Befehl. Ein Befehl über mehrere Zeilen gilt als eine Zeile für die Coverage-Analyse. + +Verzerrte Metriken erschweren ein aussagekräftiges Bild über die Testqualität. + +Im Test-Driven Development werden Tests vor oder während der Funktionsentwicklung geschrieben. +Obwohl dies die Lehrbuchvariante ist, wird es selten so praktiziert. +Wichtig ist jedoch, dass Tests so schnell wie möglich erstellt werden. +Das Erstellen von Tests zur Sicherstellung der Funktionalität bei Änderungen wird empfohlen. +Je früher ein Test erstellt wird, desto früher bring dieser nutzen. +Wird ein Test erst nachträglich erstellt, ist der Nutzen am geringsten. + +Wird eine Funktion oder Klasse in einem Jupyter-Notebook entwickelt empfiehlt sich das `ipytest` package. +Dieses erlaubt die Nutzung von pytest innerhalb von Jupyter-Notebooks. + +Die meisten Softwareprojekte haben eine untere Grenze für das Code-Coverage für neuen oder modifizierten Code. +Ein allgemeiner Standardwert liegt bei 80 %. +Das bedeutet, dass Code nur dann dem `main`-Branch hinzugefügt werden darf, wenn mindestens 80 % des Codes beim Testen ausgeführt werden. + +Normalerweise wird die Code-Coverage in einem Werkzeug wie SonarQube mitgeschrieben und visualisiert. +GitHub bietet dazu keine native Möglichkeit an. +Es gibt über den Marketspace dazu Werkzeuge, welche aber meist auf Organisationsebene eingebunden werden müssen. +Und das Aufsetzen von SonarQube für die Dauer des Projektes wurde hier als nicht sinnvoll angesehen. + +### Sphinx + +Sphinx ist ein weit verbreitetes Werkzeug zur Erstellung von Python-Dokumentationen. +Viele Werkzeuge verwenden `sphinx` mit einem ReadTheDocs-Layout für ihre Projektdokumentationen. +`sphinx` unterstützt zahlreiche andere Layouts. +Dies ist auch für unsere Projektdokumentation sinnvoll. + +Sphinx-Konfigurationen sind nicht Teil der `pyproject.toml`. + +Zum Erstellen von Sphinx-Projekten existiert ein CLI-Interface. +Dieses kann mit dem Befehl `sphinx-quickstart` aufgerufen werden. + +Es wird ein Verzeichnis für die Dokumentation erstellt, bei uns ist dies das `documentations`-Verzeichnis. +Dort befinden sich eine `makefile` für Linux und eine `make.bat` für Windows. +Beide erzeugen verschiedene Ausgabeformate. +Relevant für uns sind die Formate `html` und `latexpdf`. + +Neben diesen liegt die `index.rst`, die als Startseite der Dokumentation dient und eine `conf.py`. + +#### `index.rst` + +In der `index.rst` können Inhaltsverzeichnisse und API-Dokumentationen referenziert werden. +Inhaltsverzeichnisse listen Dokumente auf, optional unter Verwendung von `glob`. +Das erste Inhaltsverzeichnis wird durchnummeriert, das zweite nicht. + + +```rst +Fancy Dockumentation +==================== + +Welcome. +Below you will find two Table of Content on of the generell documentation. +a second Table of Contents (TOC) containing the API docs for `my_fancy_package` + +.. toctree:: + :glob: + :maxdepth: 3 + :caption: Main Content + :numbered: + + some-other-file + a-folder-full-of-files/* + +.. toctree:: + :maxdepth: 6 + :caption: Modules + + modules + +.. automodule:: my_fancy_package + :members: + :undoc-members: + :show-inheritance: + :inherited-members: + :autodoc_member_order: +``` + +#### Plugins + +Sphinx ist eine Pluginhölle. +Sogar plugins können plugins haben. So zum Beispiel der `myst_parser`. +Hier ist eine Liste der verwendeten Plugins. + +1. **sphinx.ext.autodoc**: Dieses Plugin automatisiert die Dokumentation von Python-Quellcode. Es extrahiert Dokumentation aus den Docstrings der Module, Klassen, Funktionen und Methoden. +2. **nbsphinx**: Ermöglicht die Integration von Jupyter Notebooks in Sphinx-Dokumentation. Es konvertiert Notebooks in Sphinx-Dokumente, wobei die Inhalte der Notebooks direkt gerendert werden. +3. **myst_parser**: Ein Parser, der Markdown-Dateien in Sphinx verarbeitet. Er unterstützt erweiterte Markdown-Features und ermöglicht die Verwendung von Sphinx-spezifischen Direktiven in Markdown. +4. **sphinx.ext.napoleon**: Dieses Plugin ermöglicht es Sphinx, Docstrings zu parsen, die im NumPy- oder Google-Stil verfasst sind. Es macht die Integration solcher Docstrings in die generierte Dokumentation einfacher und übersichtlicher. +5. **sphinx_copybutton**: Fügt einen "Kopieren"-Button zu Codeblöcken in der Sphinx-Dokumentation hinzu. Dies erleichtert es dem Benutzer, Code-Snippets zu kopieren. +6. **sphinx_autodoc_typehints**: Dieses Plugin fügt automatisch Typenhinweise aus Python-Funktionen und -Methoden in die Sphinx-Dokumentation ein. Es hilft, die Dokumentation klarer und informativer zu gestalten. +7. **sphinx.ext.intersphinx**: Ermöglicht das Verlinken zu Dokumentationen anderer Projekte, die mit Sphinx erstellt wurden. Dieses Plugin unterstützt das Erstellen von Querverweisen zwischen verschiedenen Sphinx-Dokumentationen. +8. **sphinx.ext.autosectionlabel**: Generiert automatisch Labels für jede Sektionstitel in der Dokumentation. Dies vereinfacht das Verlinken innerhalb der Dokumentation. +9. **sphinx.ext.viewcode**: Fügt Links zu den Quellcode-Dateien neben den dokumentierten Python-Objekten hinzu. Dies ermöglicht es dem Benutzer, direkt vom Dokument zum entsprechenden Quellcode zu springen. +10. **IPython.sphinxext.ipython_console_highlighting**: Bietet Syntax-Highlighting für IPython-Interaktionskonsolen in Sphinx-Dokumentationen. Dies verbessert die Lesbarkeit von IPython-Codebeispielen. +11. **sphinxcontrib.mermaid**: Erlaubt die Integration von Mermaid-Diagrammen in Sphinx-Dokumentationen. Mermaid ist ein Tool zur Erstellung von Diagrammen und Flussdiagrammen mittels Textbeschreibungen. +12. **notfound.extension**: Zeigt eine benutzerdefinierte 404-Seite in der HTML-Dokumentation, wenn eine Seite nicht gefunden wird. Dies verbessert die Benutzererfahrung bei fehlenden Seiten. +13. **sphinxcontrib.drawio**: Integriert draw.io-Diagramme in Sphinx-Dokumentation. Dies ermöglicht es, komplexe Diagramme direkt in die Dokumentation einzubinden. +14. **sphinx_git**: Ein Plugin für Sphinx, das es ermöglicht, Informationen aus einem Git-Repository in Dokumentationen einzufügen. Dies kann zum Anzeigen von Versionsinformationen oder zur Verfolgung von Änderungen verwendet werden. + +Besonders hervorzuheben finde ich die Interaktionen der verschiedenen Plugins. +`sphinx_autodoc_typehints` integriert wunderbar die Links welche von `sphinx.ext.intersphinx` bereitgestellt werden. +Dies macht einen Einstieg in Sphinx lieder sehr mühsam. + +#### `conf.py` + +Die `conf.py` ist eine Datei im `documentation`-Verzeichnis. +Sie konfiguriert Elemente wie Titel, Versionsnummern und Plugins. + +Einstellungen wie Typisierungsstil und Verlinkungen zu Fremddokumentationen sind hier definierbar. +So können beispielsweise Rückgabewerte vom Typ `pd.DataFrame` mit der `pandas`-Dokumentation verlinkt werden. + +Einmal erstellte Konfigurationen können oft größtenteils von Projekt zu Projekt übernommen werden. +Auch die Pfade für `custom.css` und das Standard-HTML-Theme werden hier festgelegt. + +#### API-DOCS + +Sphinx kann aus Docstrings eine API-Dokumentation erstellen. +Vor dem Bauen mit `make` muss dazu der Befehl `sphinx-apidoc` im `documentation`-Verzeichnis ausgeführt werden. + +#### Docstrings - Google-Style +In diesem Projekt wurde sich zu Anfang geeinigt, das Google-Style docstrings verwendet werden sollen. +[Die volle Dokumentation dazu findet sich hier.](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) + + +### GitHub + +Die FH-SWF hat uns ein GitHub-Repository bereitgestellt. +Dies dient als zentraler Ort zur Versionierung des Projekts und zum Abgleich der Entwicklungsstände des Teams. +Eine detaillierte Erklärung von Git würde hier zu weit führen. + +#### Pull Requests + +Git-Repositories ermöglichen die Entwicklung einzelner Entwicklungsstränge (Branches) lokal, die dann zentral synchronisiert werden. +Ein abgeschlossener Entwicklungsstrang wird auf den Default-Branch zurückgeführt. +Die richtige Größe solcher Entwicklungen zu finden, ist herausfordernd, da große und langwierige Entwicklungen schwer zu integrieren sind. + +Bei Git-Projekten wird in der Regel ein formeller Antrag, ein sogenannter Pull Request (PR), gestellt. +In anderen Systemen wird dies als Merge Request bezeichnet. +Es wurde festgelegt, dass Änderungen, die dem `main`-Branch hinzugefügt werden sollen, von mindestens einer Person korrektur gelesen werden müssen. +Die Genehmigung eines Pull Requests hängt von der positiven Bewertung ab. +Änderungsvorschläge sind normal und wünschenswert, um die Qualität des Projekts zu steigern. +Es ist üblich, ein Teammitglied, das mit dem speziellen Thema vertraut ist, um ein Code Review zu bitten. +Dies erfolgt über den entsprechenden Button im Verlauf des Pull Requests. +![request-review.png](request-review.png) + +Es wurde auch festgelegt, dass nur Pull Requests gemerged werden können, für die alle Tests erfolgreich waren. +Dies lässt sich durch Branch-Protection-Regeln in den Repository-Einstellungen unter dem Tab Branches festlegen. +![Pull_request.PNG](presentation/Pull_request.PNG) + +#### Actions + +GitHub Actions ist die Lösung von GitHub für das Erstellen und Durchführen von CI/CD-Pipelines. +Arbeitsflüsse (`workflows`) werden definiert und bei Bedarf ausgeführt. +`workflows` werden im `.github\workflows`-Verzeichnis als `*.yaml`-Dateien definiert. + +Hier ein Beispiel für einen einfachen `workflow`: + +```yaml +name: Python-Lint # the name of the workflow + +on: # defines when an actin should be triggered (more detailed specifications are possible) + push: + pull_request: + +jobs: # lists the jobs that should be run in the workflow + ruff: # the id of a job + runs-on: ubuntu-latest # defines on what kind of maschine the job should be executed. latest can only be used if the specific version does not matter + # needs: something # defines if another job needs to be executed before (is needed) + steps: # defines the steps of the workflow + - uses: actions/checkout@v4 # uses an action (dokumentation on: github.com/actions/checkout) + - uses: chartboost/ruff-action@v1 # uses an action (dokumentation on: github.com/chartboost/ruff-action) + with: # Defines arguments for an action + version: 0.1.9 # An argument as requested by an action + - name: Finishline # name of the step + run: | # Runs a script with multiple lines + echo All done! + echo Really everything is done! +``` + +Ein komplexerer Workflow kümmert sich um das Ausführen von `pytest` und das Mitschreiben des Code-Coverages: + +```yaml +name: Test & Build + +on: + push: + pull_request: + types: [reopened, opened] + +jobs: + test: + name: Pytest + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + - name: Install poetry + run: pipx install poetry + - name: Set up python + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: poetry + - run: poetry install --without develop,doc,lint --all-extras + - name: Run test suite + run: | + poetry run pytest --junit-xml=unit-test-results.xml \ + --cov-report "xml:coverage.xml" --cov=src --disable-warnings tests/ + - name: Archive code coverage results + uses: actions/upload-artifact@v4 + with: + name: code-coverage-report + path: | + coverage.xml + .coverage + if-no-files-found: error + - name: Archive unit test results + uses: actions/upload-artifact@v4 + with: + name: test-report + path: | + unit-test-results.xml + if-no-files-found: error +``` + +Der obenstehende Ausschnitt aus dem Workflow, der sich auf das Bauen und Testen des Projekts konzentriert, illustriert ausschließlich das Testen mit `pytest`. +Dieser Abschnitt veranschaulicht einige Feinheiten bei der Erstellung von `workflows` für Python. +Eine Schritt-für-Schritt-Beschreibung verdeutlicht den Prozess: + +- Der `workflow` wird bei jedem Push nach GitHub sowie bei der Erstellung und Wiedereröffnung geschlossener PRs ausgeführt. +- Der hier gezeigte einzige `job` trägt den Namen Pytest. +- Er wird auf der neuesten Ubuntu-Version ausgeführt. +- Nach 10 Minuten erreicht der Pytest `job` ein Timeout. +- Der gepushte oder PR-Branch wird ausgecheckt, was überschrieben werden kann. +- Anschließend wird `poetry` mittels `pipx` installiert. + `pipx` ist eine Variante von `pip`, die Python-Software nicht als Python-Abhängigkeit, sondern als ausführbares Python-Skript installiert. +- Python wird eingerichtet. + Die `PATH`-Variable wird auf die erforderliche Python-Installation gesetzt. + Falls die notwendige Python-Version nicht vorinstalliert ist, wird sie von der Aktion nachinstalliert. + Es ist zu beachten, dass keine Linux-ARM-Distributionen unterstützt werden. + Nur wenn die Python-Installation konfiguriert wird, können die zugehörigen Caches genutzt werden. + Hier kommt der Poetry-Cache zum Einsatz. + Für jedes `poetry`-Projekt im Repository wird ein separater Cache angelegt, was den Prozess erheblich beschleunigt. + Caches sind auch für andere Abhängigkeitsverwaltungen als Poetry verfügbar, jedoch nur, wenn die Python-Version explizit definiert wird. +- `poetry` installiert selektiv die erforderlichen Abhängigkeitsgruppen für das Testen. +- `pytest` führt die Tests durch und erstellt Protokolle in verschiedenen Formaten, darunter ein Unittest-Protokoll und ein Code-Coverage-Protokoll. +- Die Berichte werden für nachfolgende Aktionen bereitgestellt und können von den Nutzern heruntergeladen werden. + +Die Workflows werden für Pushes und PRs aus dem entsprechenden Branch heraus ausgeführt. + +Bei Pushen und PRs werden die Workflows aus dem entsprechenden Branch verwendet. +GitHub Actions führen alle `jobs` parallel aus, im Gegensatz zu sequentiellen Abläufen bei anderen CI/CD-Tools wie z.B. Jenkins. +GitHub Actions ermöglichen parametrisierte Matrix Builds. + +Sie müssen in den Repository-Einstellungen unter `Actions` aktiviert werden. +Eine vollständige Schemabeschreibung für GitHub `workflows` findet sich [hier](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions). + +#### Implementierte Workflows + +Für dieses Projekt wurden folgende Workflows implementiert: + +- **Python-Lint**: + - Führt verschiedene Linter aus, um die aktuelle Version des Projekts auf Konformität zu prüfen. + - Das Linting wird bei jedem Push und bei jedem Öffnen eines Pull Requests ausgeführt. + - Der Bericht listet Fehler auf. + ![Lint-error.PNG](presentation\Lint-error.PNG) + - Zu den Lintern gehören `black`, `ruff` und `mypy`. + - Die Abhängigkeitsvalidierung umfasst: + - Export der `requirements.txt`. + - `pip-audit` für Laufzeit-Abhängigkeiten. + - `pip-licenses`, nur als Bericht für Laufzeit-Abhängigkeiten. + +- **Test & Build**: + - Testet und baut die Software. + - Alle Berichte werden als Artefakte bereitgestellt. + ![test-and-build.PNG](test-and-build.PNG) + - Pull Requests werden mit Angaben zum Code-Coverage kommentiert. + ![Coverage.PNG](presentation\Coverage.PNG) + - `pytest` wird für Tests ausgeführt. + - Das Code-Coverage wird analysiert, um das Mergen bei unzureichender Abdeckung zu verweigern (nur bei Pull Requests). + - Das Code-Coverage wird visualisiert, um eine Übersicht über die Testabdeckung auch ohne passende IDE zu bieten. + - Parallel zur Analyse werden eine Python-Wheel (`*.whl`) und ein Python-Projekt (`*.tar.gz`) erstellt. + - Mit dem Python Wheel wird das Docker-Image gebaut. + - Das Image wird unter dem Tag `main` bereitgestellt, wenn es vom `main`-Branch gebaut wird. + - Für verschiedene Docker-Container gibt es spezifische Abhängigkeitsdefinitionen in unserem Poetry-Python-Projekt, die pro `target` in der Dockerfile nachinstalliert werden. + +- **Documentation-Action**: + - Baut die Dokumentation mit `sphinx` und lädt sie als GitHub Pages hoch. + - Der Dokumentationsbau erstellt eine HTML-Webseite mit Sphinx. + - Die Dokumentation wird nur in Pull Requests oder auf dem `main`-Branch bereitgestellt und kann heruntergeladen werden. + - Die Bereitstellung der Dokumentation erfolgt nur auf dem `main`-Branch und wird als externe Webseite auf den GitHub Pages veröffentlicht. + + +#### Self-Hosted Act-Runner + +Zu Beginn des Projekts erhielten wir die Information, dass die FH-SWF keine Cloud-Ressourcen zur Verfügung stellen kann. +Deshalb wurde anfänglich versucht, einen Runner lokal auf einem Raspberry Pi 4 zu installieren. +Die Einrichtung gemäß der GitHub-Anleitung war relativ einfach, stieß jedoch aufgrund der nicht unterstützten ARM32/ARM64-Architektur bei der Nutzung von Python auf Probleme. +1. Die `actions/setup-python`-Aktion unterstützt ARM für Linux nicht. Ein Fallback auf eine installierte Python-Version ist jedoch möglich. +2. Viele Python-Wheels sind nicht für ARM gebaut und müssen lokal kompiliert werden, besonders wenn sie Cython- oder Rust-Komponenten enthalten. +3. Ohne Angabe der Python-Version unterstützt die `actions/setup-python`-Aktion keinen Python-Poetry-Cache. +4. Einige KI-Modelle lassen sich auf dem Raspberry Pi nicht ausführen, da er nicht genügend RAM hat. +5. Geringe Parallelisierung und langsame Prozessoren führen zu langen Wartezeiten bei Tests und Linting-Ergebnissen. +6. Das Risiko, von GitHub aufgrund zu vieler Downloads gesperrt zu werden, ist geringer. +7. GitHub Actions sind für Open-Source-Projekte kostenlos, weshalb es relativ wenig Dokumentation zum Hosting eigener Runner gibt. + +Letztendlich wurde uns jedoch die Nutzung von GitHub Actions über den FH-SWF-Account ermöglicht, sodass der self-hosted Runner nicht zum Einsatz kommen musste. +Zur Zeit Nutze ich persönlich den Gitea's Act Runner welcher ein Fork von GitHubs self-hosted Runner ist. +Es ist also möglich diesen zu Nutzen. +Dies hat aber eindeutige Grenzen und ist bei weitem nicht so komfortabel, ist aber mit nicht ARM32/64 Geräten gut möglich. + +Die Anleitung von GitHub zu self-hosted Runners ist [hier zu finden](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). + +#### Erwartete Artefakte + +Die derzeit konzipierten Pipelines erstellen folgende Artefakte: +- Ein Python Wheel. +- Test Coverage. +- Einen Bericht über die verwendeten Laufzeitabhängigkeiten. + - Eine `requirements.txt`. + - Einen Bericht über die genutzten Lizenzen, erstellt von `pip-license`. + - Einen Bericht über potenzielle Sicherheitslücken in den Abhängigkeiten, erstellt von `pip-audit`. + +#### Dependabot + +Dependabot, der GitHub OWASP-Scanner, ermöglicht es, den Standard-Branch eines Projekts regelmäßig auf Sicherheitslücken in Abhängigkeiten zu überprüfen. +Abhängig von der Konfiguration kann Dependabot Handlungsempfehlungen aussprechen oder automatisch Pull Requests erstellen, um Sicherheitslücken zu schließen. +Für unser Projekt ist der Einsatz von Dependabot aktuell nicht geplant. +Dies liegt daran, dass es mit `pip-audit` redundant wäre und erst nach Abschluss der aktiven Entwicklungsphase des Projekts Vorteile bietet. +Dependabot kann Routineaufgaben übernehmen, insbesondere, wenn GitHub Actions implementiert sind und vorgeschlagene Änderungen sofort testen. +Dependabot unterstützt eine Vielzahl von Programmiersprachen und Tools zur Abhängigkeitsverwaltung. +Es ermöglicht nicht nur Scans von Python-Abhängigkeiten, sondern auch das Scannen und Aktualisieren von GitHub Actions. + +Natürlich wäre es einfach möglich nur Dependabot zu nutzen. Da ich aber im beruflichen Kontext `pip-audit` verwende, war dies einfacher. + +### pre-commit + +Oft in der Softwareentwicklung gezeigt wird das Kostendiagramm für Fehler, das die zunehmenden Kosten von Fehlern über die Zeit illustriert. +![costOfChangeTraditional.gif](costOfChangeTraditional.gif) + +Es verdeutlicht, dass die Kosten für das Beheben eines Fehlers mit der Zeit ansteigen. +`pre-commit`, ein Python-Tool, adressiert dieses Problem. +Es hängt sich in die `git-hooks`-Mechanik ein und validiert geänderte Dateien vor dem Committen. +Durch den selektiven Einsatz von Lintern und Formatierern wird sichergestellt, dass nur qualitativ hochwertiger Code committet wird. +Nur die Hooks, die auf die aktuell geänderten Dateiformate zutreffen, werden ausgeführt. +Hilfestellungen und automatische Fixes bieten bei Bedarf schnelle Lösungen und schlagen eine überarbeitete Version vor. +Diese überarbeitete Version kann dann mit der `gestagten` Version abgeglichen und übernommen werden. +Meist garantieren die Hooks eine unveränderte Funktionalität. +Ich persönlich hatte an dieser Stelle bisher noch nie Probleme. + +Programmierer müssen sich mit Themen wie Importreihenfolge und Whitespace-Formatierung auseinandersetzen. +Dadurch ist auch bei diesen Details die Qualität gewährleistet öhne die Kosten zu erhöhen. +Typisierungsfehler können dem Programmierer schon während des Committens aufgezeigt werden. +Dies trägt dazu bei, dass potenzielle Fehler schon vor dem Testing, aber direkt beim oder nach dem Codieren auffallen. +Das beschleunigt den Programmierprozess und senkt damit wieder die Kosten. +Auch wenn wir in einem akademischen Projekt keine Kosten in EUR haben ist die benötigte Zeit natürlich immer noch ein Faktor. + +Um `pre-commit` zu nutzen, müssen neben Git und Python auch das `pre-commit`-Paket installiert werden. +Nach der Installation wird das `pre-commit`-Skript im `.git/hooks/`-Verzeichnis eingehängt, was durch den Befehl `pre-commit install` geschieht. +Das `pre-commit`-Skript im `.git/hooks/`-Verzeichnis wird dann vor jedem Commit ausgeführt. +Scheitert einer der Tests oder werden Änderungen vorgeschlagen, wird der Commit abgebrochen. +`pre-commit` wird durch eine `.pre-commit-config.yaml` im Root-Verzeichnis eines Projekts konfiguriert. + +Es gibt im git-workflow viele verschiedene Stellen um `hooks` einzuhängen. +In dieser Dokumentation wird nur auf die Validierung/Formattierung vor dem Committen eingegangen. + +Hier ein Beispiel einer `.pre-commit-config.yaml`: +```yaml +default_language_version: + python: python3.11 + +default_stages: +- pre-commit + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + exclude: (.txt$|.ipynb$) +``` + +In diesem Beispiel wird Python 3.11 als Standardversion festgelegt und die Hooks werden standardmäßig nur im `pre-commit`-Stadium verwendet. +Es wird ein Git-Repository als Quelle für eine Reihe von Hooks definiert. +Der Hook `end-of-file-fixer` wird ausgeführt und dabei werden `*.txt` und `*.ipynb`-Dateien ausgeschlossen. +Mehrere Repos und Hooks pro Repo können definiert werden. +Der Befehl `pre-commit autoupdate` aktualisiert die Versionsdefinitionen. +Neue Repos oder andere Versionen werden bei der ersten Ausführung einmalig installiert. + + +Hier eine übersicht der für dieses Projekt verwenden hooks: + +1. **end-of-file-fixer**: Stellt sicher, dass Dateien mit einer leeren Zeile am Ende enden. +2. **trailing-whitespace**: Entfernt überflüssige Leerzeichen am Ende von Zeilen. +3. **check-yaml**: Überprüft die syntaktische Korrektheit von YAML-Dateien. +4. **check-json**: Überprüft die syntaktische Korrektheit von JSON-Dateien. +5. **check-toml**: Überprüft die syntaktische Korrektheit von TOML-Dateien. +6. **check-xml**: Überprüft die syntaktische Korrektheit von XML-Dateien. +7. **check-ast**: Überprüft Python-Dateien auf Syntaxfehler. +8. **check-added-large-files**: Verhindert das versehentliche Hinzufügen großer Dateien zum Repository. +9. **name-tests-test**: Stellt sicher, dass Testdateien mit `test` beginnen. +10. **detect-private-key**: Erkennt und verhindert das Hinzufügen von privaten Schlüsseln zum Repository. +11. **check-case-conflict**: Verhindert Probleme mit Dateinamen, die sich nur in der Groß-/Kleinschreibung unterscheiden. +12. **check-symlinks**: Überprüft symbolische Verknüpfungen auf Gültigkeit. +13. **check-docstring-first**: Stellt sicher, dass Dateien mit einem Docstring beginnen. +14. **mixed-line-ending**: Vereinheitlicht die Zeilenendungen in Dateien. +15. **destroyed-symlinks**: Erkennt zerstörte symbolische Verknüpfungen. +16. **debug-statements**: Erkennt und meldet Debug-Anweisungen in Python-Code. +17. **pretty-format-json**: Formatiert JSON-Dateien in einem einheitlichen Stil. +18. **no-commit-to-branch**: Verhindert das direkte Commiten in geschützte Branches. Bei uns `main`. +19. **ruff**: Ein schneller Python-Linter und Code-Formatter (hier vorgestellt). +20. **black**: Ein Python-Code-Formatter (hier vorgestellt). +21. **black-jupyter**: Formatiert Jupyter Notebooks mit Black (hier vorgestellt). +22. **pretty-format-ini**: Formatiert INI-Dateien in einem einheitlichen Stil. +23. **pretty-format-yaml**: Formatiert YAML-Dateien in einem einheitlichen Stil. +24. **pretty-format-toml**: Formatiert TOML-Dateien in einem einheitlichen Stil. +25. **mypy**: Ein statischer Typenprüfer für Python (hier vorgestellt). +26. **md-toc**: Erzeugt automatisch ein Inhaltsverzeichnis für Markdown-Dateien. +27. **poetry-check**: Überprüft die Konsistenz einer Poetry-Projektdatei. +28. **poetry-install**: Installiert Abhängigkeiten mit Poetry. +29. **validate-html**: Überprüft die Korrektheit von HTML-Dateien. +30. **check-github-workflows**: Überprüft GitHub Workflow-Dateien auf Korrektheit. +31. **auto-walrus**: Automatisiert die Verwendung des Walrus-Operators in Python. Um ein Bewusstsein zu schaffen, wann dieser Verwendet werden kann. + +Auf `prettier` wurde verzichtet, da es sich um ein Node.js-Paket handelt, das die Installation von Node.js bei allen Teammitgliedern erfordert. +`prettier` könnte für das Projekt benötigte `*.css`-Dateien formatieren. + +Einige der Hooks kamen nicht oder nur selten zum Einsatz. +Sie waren Teil der verwendeten Repositories und wurden nur ausgeführt, wenn sie für eine spezifische Datei benötigt wurden. +Somit gab es keinen Nachteil darin, selten verwendete Hooks zu unterstützen. +Langsame Hooks wie `mypy` sind hinsichtlich der Ausführungszeit kritischer. +Allerdings bietet `mypy` aufgrund seiner statischen Typ-Prüfung einen besonders hohen Mehrwert. + +Bei der Nutzung von `pre-commit` neben `poetry` empfehle ich das `poetry-pre-commit-plugin`. +Dieses Plugin führt `pre-commit install` aus, wenn `pre-commit` über `poetry` installiert wird. +So wird verhindert, dass dieser einfache und schnelle Schritt nach dem `git clone` vergessen wird. + +`pre-commit`-Konfigurationen lassen sich fast vollständig von einem Projekt auf ein anderes übertragen. +Die einzige Ausnahme bildet die `mypy`-Definition, da hier auch `*-stubs`/`*-typing`-Abhängigkeiten hinterlegt werden müssen. +`pre-commit` installiert jedes Hook-Repository in einer eigenen virtuellen Umgebung. + +Das Commit-Log unter Verwendung von `pre-commit` kann folgendermaßen aussehen: +![Pre-commit.PNG](presentation\Pre-commit.PNG) + +[Die vollständige Dokumentation von pre-commit finden Sie hier.](https://pre-commit.com) + +[Den offiziellen Katalog über die verwendbaren Hooks finden Sie hier.](https://pre-commit.com/hooks.html) + +Pre-commit Hooks haben oft eine große Überschneidung mit der zentralen Pipeline auf einem Build-Server. +Dies ist beabsichtigt. +Ein Pre-commit Hook ermöglicht schnelle Rückmeldungen, die nicht direkt in der IDE angezeigt werden. +CI/CD-Pipelines benötigen in der Regel mehr Zeit, da sie das gesamte Projekt bearbeiten. +Sie lassen sich nicht so einfach umgehen, da Pre-Commit Pipelines eher suggestiv wirken. +Der Overhead für `autofixes`, die von einer `pre-commit` Hook-Kollektion stammen, ist in einer zentralen CI/CD-Pipeline unerwünscht. +Die durch `pre-commit` Hooks durchgesetzten Regeln sind oft weichere Konventionen, die automatische Fixes bereitstellen. +Diese können durch einfaches und oft nur einmaliges Deaktivieren bewusst umgangen werden. +Andererseits vermeidet man aufwendige Verifikationen, wie Unit-Testing, in Pre-Commit Hooks, da diese zu langsam sind und Entwickler unnötig aufhalten könnten. + +Diese Unterschiede lassen sich auch zusammen fassen als: +pre-commit Hooks erleichtern Entwicklern das Einhalten bestimmter Standards, während CI/CD-Pipelines die Einhaltung von Projektstandards garantieren. + +### pre-commit ci + +[`pre-commit.ci`](https://pre-commit.ci/) ist eine CI/CD-Lösung, die in GitHub integriert werden kann und dort `pre-commit` als Linter ausführt. +Da die Ergebnisse in hohem Maße gecacht werden, ist dies extrem schnell. +Diese Lösung ist jedoch bei nicht öffentlichen Projekten kostenpflichtig und kam daher in diesem Projekt nicht zum Einsatz. + +Ich weiß es zu schätzen, dass einige der in `pre-commit` verwendeten Linter in `pre-commit.ci` explizit umgangen werden können, ohne eine formelle Ausnahme zu definieren. +Leider ist [`pre-commit.ci`](https://pre-commit.ci/) derzeit nur für GitHub verfügbar. + +### Zustimmung vom Team +Da das DevOps- und CI/CD-Setup zumindest initial für ein Projekt vorhanden sein muss, um Mehrwert zu erzeugen und nicht nur Mehrarbeit, +wurden alle Werkzeuge in einem Development-Branch zusammengestellt und konfiguriert. + +Obwohl CI/CD stark formalisiertes maschinelles Testen beinhaltet, funktioniert es nicht ohne das Buy-In des Entwicklungsteams. +Daher wurde bei der Präsentation dieser Werkzeuge die Zustimmung des Teams für dieses Vorgehen eingeholt. + +Die Zustimmung wurde erteilt, und ein erster Entwurf der Pipelines und Tools wurde in Betrieb genommen. + +### Fazit + +Es gibt zahlreiche hilfreiche Werkzeuge für die Softwareentwicklung. +Allein im Bereich CI/CD für Python den Überblick zu behalten und zu entscheiden, was nützlich ist oder sein könnte, ist eine Frage der Erfahrung. +Die Einschätzung der damit verbundenen Kosten gehört ebenfalls dazu. +Es erscheinen ständig neue Werkzeuge. +In anderen Projekten habe ich beispielsweise bisher mit `flake8` gearbeitet. +`ruff`, das weniger als ein Jahr alt und noch in den Versionen `0.0.*` ist, übertrifft ältere Linter deutlich. +Ich bin zuversichtlich, dass die aktuelle Konfiguration der Werkzeuge prinzipiell funktionieren wird. +Sie muss jedoch kontinuierlich mit neueren Werkzeugen abgeglichen und deren praktischer Nutzen evaluiert werden. +Die Evaluation wird sehr von der Wahrnehmung dieser Werkzeuge abhängen. +Entscheidend ist, ob sie als Hilfestellungen oder als Einschränkungen empfunden werden. +Ich habe versucht möglichst selbstreparierende Werkzeuge auszuwählen, sodass ich dabei aber zuversichtlich bin. diff --git a/documentations/seminararbeiten/DevOps/Action-Summary.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Action-Summary.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Action-Summary.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Action-Summary.PNG diff --git a/documentations/seminararbeiten/DevOps/Action.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Action.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Action.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Action.PNG diff --git a/documentations/seminararbeiten/DevOps/Coverage.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Coverage.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Coverage.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Coverage.PNG diff --git a/documentations/seminararbeiten/DevOps/Lint-error.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Lint-error.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Lint-error.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Lint-error.PNG diff --git a/documentations/seminararbeiten/DevOps/Pre-commit.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Pre-commit.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Pre-commit.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Pre-commit.PNG diff --git a/documentations/seminararbeiten/DevOps/Pull_request.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Pull_request.PNG similarity index 100% rename from documentations/seminararbeiten/DevOps/Pull_request.PNG rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Pull_request.PNG diff --git a/documentations/seminararbeiten/DevOps/Seminarpraesentation.ipynb b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Seminarpraesentation.ipynb similarity index 100% rename from documentations/seminararbeiten/DevOps/Seminarpraesentation.ipynb rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/Seminarpraesentation.ipynb diff --git a/documentations/seminararbeiten/DevOps/bohems-law.png b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/bohems-law.png similarity index 100% rename from documentations/seminararbeiten/DevOps/bohems-law.png rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/bohems-law.png diff --git a/documentations/seminararbeiten/DevOps/project-organisation-and-dev-ops.md b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/project-organisation-and-dev-ops.md similarity index 100% rename from documentations/seminararbeiten/DevOps/project-organisation-and-dev-ops.md rename to documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/presentation/project-organisation-and-dev-ops.md diff --git a/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/prompts.md b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/prompts.md new file mode 100644 index 0000000..80b26ec --- /dev/null +++ b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/prompts.md @@ -0,0 +1,11 @@ +Bitte überarbeite den folgenden Text. +Halte dich inhaltlich an die Vorgaben. +Korrigiere aber bitte, Grammatik, Zeichensetzung und Stil. +Bitte kürze / teile die Sätze wenn möglich. + + +Bitte gib mir die überarbeitete version in Markdown (md) aus. +Dieses sollte in einem md codeblock angezeigt werden. + +Bitte fange für jeden Satz eine neue Zeile an. +Sollten Fragen offen bleiben schreibe mir bitte einen kurzen Verweise auf den md block. diff --git a/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/request-review.png b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/request-review.png new file mode 100644 index 0000000000000000000000000000000000000000..98dc41a65d7430dd7dd1438dcc632727e23c6814 GIT binary patch literal 37814 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 literal 0 HcmV?d00001 diff --git a/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/test-and-build.PNG b/documentations/Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/test-and-build.PNG new file mode 100644 index 0000000000000000000000000000000000000000..29c7ac50125307a9adf0824edec167d90ed2eea3 GIT binary patch literal 9249 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 literal 0 HcmV?d00001 diff --git a/documentations/index.rst b/documentations/index.rst index b6b1768..5ee24b6 100644 --- a/documentations/index.rst +++ b/documentations/index.rst @@ -38,7 +38,7 @@ Diese sind, um Industriestandards zu entsprechen, auf Englisch gehalten. :caption: Seminararbeiten :numbered: - seminararbeiten/DevOps/Seminarpraesentation.ipynb + Ergebnisse/Zwischenbericht_und_Praesentation/PhHo/dev-ops seminararbeiten/Datenspeicherung/00_Datenspeicherung .. toctree:: @@ -50,13 +50,6 @@ Diese sind, um Industriestandards zu entsprechen, auf Englisch gehalten. Ergebnisse/Abschlussbericht_und_Praesentation/PhHo/4-4-2-database-generator -.. .. toctree:: - :glob: - :maxdepth: 1 - :caption: UI Mock Ups - -.. mock_up/**/* - .. toctree:: :glob: :maxdepth: 1