From a9b53d547457706b0e94689032bc49cf2bb5ec0a Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Sat, 29 Nov 2025 08:55:13 -0800 Subject: [PATCH 1/8] Adding artifacts upload through S3/Minio --- Project.toml | 6 ++++++ src/MLFlowClient.jl | 5 ++++- src/services/logger.jl | 45 ++++++++++++++++++++++++++++++++++++++++++ src/services/run.jl | 3 +++ src/types/run.jl | 2 +- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 203dd20..8ae69c9 100644 --- a/Project.toml +++ b/Project.toml @@ -4,18 +4,24 @@ authors = ["@deyandyankov, @pebeto, and contributors"] version = "0.7.0" [deps] +AWSS3 = "1c724243-ef5b-51ab-93f4-b0a88ac62a95" Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +FileTypes = "b58e86d0-4a47-4fce-a54d-8006a143ed90" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Minio = "4281f0d9-7ae0-406e-9172-b7277c1efa20" ShowCases = "605ecd9f-84a6-4c9e-81e2-4798472b76a3" URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] +AWSS3 = "0.11.4" Base64 = "1.0" +FileTypes = "0.1.1" HTTP = "1.0" JSON = "0.21" +Minio = "0.2.2" ShowCases = "0.1" URIs = "1.0" julia = "1.0" diff --git a/src/MLFlowClient.jl b/src/MLFlowClient.jl index 1e50aac..1fd1f5e 100644 --- a/src/MLFlowClient.jl +++ b/src/MLFlowClient.jl @@ -11,13 +11,16 @@ and artifacts. If you are not familiar with `MLFlow` and its concepts, please re """ module MLFlowClient +using AWSS3 using Dates +using FileTypes using UUIDs using HTTP using Base64 using URIs using JSON using ShowCases +using Minio include("types/mlflow.jl") export MLFlow @@ -65,7 +68,7 @@ export getrun, createrun, deleterun, setruntag, updaterun, restorerun, searchrun deleteruntag include("services/logger.jl") -export logbatch, loginputs, logmetric, logmodel, logparam +export logbatch, loginputs, logmetric, logmodel, logparam, logartifact include("services/artifact.jl") export listartifacts diff --git a/src/services/logger.jl b/src/services/logger.jl index 45c2a82..6ed3efc 100644 --- a/src/services/logger.jl +++ b/src/services/logger.jl @@ -139,3 +139,48 @@ function logmodel(instance::MLFlow, run_id::String, model_json::String)::Bool end logmodel(instance::MLFlow, run::Run, model_json::String)::Bool = logmodel(instance, run.info.run_id, model_json) + +""" + logartifact(s3_cfg::MinioConfig, run::Run, s3_cfg::MinioConfig, path::String="", artifact_name::String="") + +Log artifact for a run. Supports only S3 buckets. + +# Arguments +- `s3_cfg`: Minio configuration +- `Run`: ['Run'](@ref) instance +- `path`: Path to the artifact +- `artifact_name`: Name of the artifact in the bucket + +# Returns +`true` if successful. Otherwise, raises exception. + +""" +function logartifact(s3_cfg::MinioConfig, run::Run, path::String="", artifact_name::String="") + # Parse the URI + u = URI(run.info.artifact_uri) + u.scheme == "s3" || ArgumentError("The artifact URI for the run has to be a S3 bucket. Got: $(run.info.artifact_uri)") + isfile(path) || ArgumentError("Can not read file $(path).") + bucket_name = u.host + artifacts_base_path = u.path + + + # Determine MIME type to use + kind = matcher(path) + mime_type_str = if isnothing(kind) + @warn "FileTypes.jl could not determing the specific MIME type for $(path). Defaulting to application/octet-stream" + "application/octet-stream" + else + string(kind.mime) + end + + # Read the bytes of the file + content = read(path) + + # Create the artifact path on the bucket + artifact_path = isempty(artifact_name) ? joinpath(artifacts_base_path, path) : joinpath(artifacts_base_path, artifact_name) + + s3_put(s3_cfg, bucket_name, artifact_path, content, mime_type_str) + return true +end + + diff --git a/src/services/run.jl b/src/services/run.jl index c66c440..8332aa5 100644 --- a/src/services/run.jl +++ b/src/services/run.jl @@ -20,6 +20,9 @@ An instance of type [`Run`](@ref). function createrun(instance::MLFlow, experiment_id::String; run_name::Union{String,Missing}=missing, start_time::Union{Int64,Missing}=missing, tags::MLFlowUpsertData{Tag}=Tag[])::Run + # Time returns system time in seconds, convert to ms. + start_time = ismissing(start_time) ? Int(floor(1e3 * time())) : start_time + result = mlfpost(instance, "runs/create"; experiment_id=experiment_id, run_name=run_name, start_time=start_time, tags=parse(Tag, tags)) return result["run"] |> Run diff --git a/src/types/run.jl b/src/types/run.jl index 38143a6..32e7aa6 100644 --- a/src/types/run.jl +++ b/src/types/run.jl @@ -140,5 +140,5 @@ struct Run outputs::RunOutputs end Run(data::Dict{String,Any}) = Run(RunInfo(data["info"]), RunData(data["data"]), - RunInputs(data["inputs"]), RunOutputs(data["outputs"])) + RunInputs(data["inputs"]), RunOutputs(get(data, "outputs", Dict{String, Any}()))) Base.show(io::IO, t::Run) = show(io, ShowCase(t, new_lines=true)) From 532250431741617f4af3831ea9db715a5aeb6a8f Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Sun, 30 Nov 2025 19:33:30 -0800 Subject: [PATCH 2/8] Added unit testing with containerized mlflow/minio --- .github/workflows/CI.yml | 39 ++-- docker-compose.test.yaml | 43 ++++ src/services/logger.jl | 1 + test/assets/julia.png | Bin 0 -> 84469 bytes test/base.jl | 45 ++++- test/runtests.jl | 29 ++- test/services/experiment.jl | 326 +++++++++++++++--------------- test/services/logger.jl | 80 ++++++++ test/services/registered_model.jl | 131 ++++++------ test/setup.jl | 25 +++ 10 files changed, 465 insertions(+), 254 deletions(-) create mode 100644 docker-compose.test.yaml create mode 100644 test/assets/julia.png create mode 100644 test/setup.jl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8321a06..bec72fe 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -22,21 +22,23 @@ jobs: - x64 steps: - uses: actions/checkout@v2 - - name: Setup custom python requirements - if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' - run: | - touch ./requirements.txt - echo "mlflow[auth]==3.2.0" > ./requirements.txt - - uses: actions/setup-python@v4 - with: - python-version: '3.12.3' - cache: 'pip' - - name: Setup mlflow locally - run: | - export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' - pip install -r ./requirements.txt - python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 & - sleep 5 + - name: Start services + - run: docker-compose -f docker-compose.test.yaml up -d +# - name: Setup custom python requirements +# if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' +# run: | +# touch ./requirements.txt +# echo "mlflow[auth]==3.2.0" > ./requirements.txt +# - uses: actions/setup-python@v4 +# with: +# python-version: '3.12.3' +# cache: 'pip' +# - name: Setup mlflow locally +# run: | +# export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' +# pip install -r ./requirements.txt +# python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 & +# sleep 5 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} @@ -54,8 +56,11 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: - JULIA_NUM_THREADS: '2' - MLFLOW_TRACKING_URI: "http://localhost:5000/api" + JULIA_NUM_THREADS: '1' + MLFLOW_TRACKING_URI: "http://localhost:5050/api" + MLFLOW_S3_ENDPOINT_URL: "http://minio:9000" + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v3 with: diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml new file mode 100644 index 0000000..b2b4ca8 --- /dev/null +++ b/docker-compose.test.yaml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + minio: + image: minio/minio + ports: + - "9000:9000" # S3 API + - "9001:9001" # Minio Console + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=minioadmin + command: server /data --console-address ":9001" + + create-buckets: + image: minio/mc + depends_on: + - minio + entrypoint: > + /bin/sh -c " + sleep 10; + /usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/mlflow; + exit 0; + " + + mlflow: + image: ghcr.io/mlflow/mlflow:v2.14.1 + depends_on: + - create-buckets + ports: + - "5050:5050" + environment: + - AWS_ACCESS_KEY_ID=minioadmin + - AWS_SECRET_ACCESS_KEY=minioadmin + - MLFLOW_S3_ENDPOINT_URL=http://minio:9000 + command: > + /bin/sh -c " + pip install boto3 && + mlflow server + --host 0.0.0.0 + --port 5050 + --default-artifact-root s3://mlflow + " diff --git a/src/services/logger.jl b/src/services/logger.jl index 6ed3efc..0d7ebe0 100644 --- a/src/services/logger.jl +++ b/src/services/logger.jl @@ -178,6 +178,7 @@ function logartifact(s3_cfg::MinioConfig, run::Run, path::String="", artifact_na # Create the artifact path on the bucket artifact_path = isempty(artifact_name) ? joinpath(artifacts_base_path, path) : joinpath(artifacts_base_path, artifact_name) + @info "logartifact: putting $(path) into $(artifact_path)" s3_put(s3_cfg, bucket_name, artifact_path, content, mime_type_str) return true diff --git a/test/assets/julia.png b/test/assets/julia.png new file mode 100644 index 0000000000000000000000000000000000000000..3219471c694da215cda755ea26b55a92947d5fdb GIT binary patch literal 84469 zcmZU5cR*8D`|xE5ML_BV6v0-85rHTMWMiEP1T-kfj+G%{%3gt3TB(YnNdTEaSw^NH zdj#A88A1TrC`~*|*$>4(U_Wj!-2ogqKJd1%K_FwsK z{lW+S@V{685&Xexe;R!nf?kFTu3g^-{{PU8ix@O`D+@t{zaeM^{6zQ;L0)hO8o@%4 z`V$Beb&D&xtO;K5{i%21EW~2}msp-g0>AKkBGKpghq<@&YN$x`-3C7qAmrK8SA5&Y zy8MbPy{=EP>U}0Xh;R|E6aU`3DfjsJ?>4$gcd&maUVW2%%|>!hy0=Sts6b|y+J zr8ukq}=vK{HCV%M%|ufRsY6lRkJx} zQOz`9JYOdua~Hsc{rFd}-5)YEEevXOq0T!XY||PU@r}d!nfV6e(ngN846-@`W<+$F z|4wsGmnKQBw`ypv=(NiDO??ht?@eL_pl14FG8ABoh?L^&lLHlIBO)l+Vgy-gM`YD% z<1lS#VN$})HB!-e?JNY{NxitaDFmOEc;)oDj)%mza1`z5jrP`qmCAS(N|(w+eoXrIP?=7Put8_eJI1HZx)!dvEzn9G4?&fOg&?j?0Pi2K*W;Jx zTzs&sCAtDy!TL)>yys|)Q5OEAD<9NwpYx%a(?W5mW}0_i zBE!xAGibNg)8<;_e6d#4$lVd6L#r@i-KvGDGOz4|5=F(f(0%oBY_YEUnL(Ski+b%} zn7mW4`m44ip~!w~VxWJ{V0(w5`TRTb6;p_C_apS{CWhFsTD75p`V9O5tLS!9GDB1* z`dM|(vWvpJateKZwA6@M)%O<{;qF_`2SO54x6hS&^YL57n?*`T@22!TypxY~(TB;) ziDZ?I3@W<>>F$ECnjYK&n_Iw9b_x_)Q?mN<0CoP0v=XxMvh63$xPA|q?Ldfmi#%;+ z<*EpZ8wyk9BW!*F9llAwC)4G+K4q_@7#|mB7OaY-9L=0b7h~rgRD+N^AZ!^N z;@$i}j4Q8Rt6wd}$U3TjDMr)s@$^$^M~uhyXw8A7!Z^3u^?MMcpyTs{E7((?tkr3* z<8#PtMTDH3->pDdjf5_1Qh3D4p;GxnP=m37%8#WgO`Rf=73jRLU;B(^Ba=0a9t#Xb zn$0A17?{ovldq^jgo6K&c(%c8W?dx}p{7jVaZlgZ+K@Ul_>^DKV!fqVq=pL$L-6Qs zVoX4v3|Rh4ORs6UFGk{Vvi0MH+47jUyr)fZd4uEw!AJQaxG_f>R2s(y1cNou>v_o% zq~!FA^`*2(|L!{&R{O8NLSlj(vKfA@oV6}6ht*P22bZMr_on10z0VQLK`EE#cXp4H21`BNZ|`U$1_sSG?DKHHc@s+>(y1}n0&VNJR902jt$Vd z!E9W{kESkC9;cRR$e6=ki>;nVY1z(GDeH0IFqb&$r*(ZoEp+U&r-uV(@oLCED%EQ_ z#5_vJO;s1d8gbz5Hb+N$Fkh2!^i`r0>=%W3DRix)s}8<-KMW!~u!Vlv#8Cg5N1b(% z9$NcJKO7;(&xe|sNJDo`WE)GqbicF%>ZNkF-Q5`HA!2n+W8e+a?s?UzJpUY}?DD?k zt3G;Mkf!(+1+jma0-M-R(Z?~h^AjW~cTWVZ7e&_HEsS4@PEUrQgnvnvn>%A)KfyEg z5{#P`?-C`R^X=Bnq_-VVUtF(>F=}yo!~-o|-IBM;zu`lF{iBqDkTN`BDJ=UW*ky$; zyl6eS9&f!J>V2uc;%4_HSU9?bO-t==64c=7DjiHHtc+LSB3OUhLD<~3vIxQI zTsF@hoePG;E<8UiaxDtO@Z(%TLlI=-dp@2SQ1Qkr?g@~yIMhQj_xbfgx3#M{-< z?``#I1e93Fp>tmy(3yo}=piZGj3?qd5QU%kJ$R0>wt3(!@V&xt;g-<9^eVRWHy&U?RKAY&yhw;LQSOo zBKGGX)nRID!}ony^g^BWo6_RjA=Y#Dr? z9mQvgNS=9FbW0$tVDcosi^o-Gc*PZ{S09ID@DV#o@JB(SzY$TPnQ(mi9ldr%v@7s^ zt;j{NEXuX<2VzOMZa+b9!;$>s%#fF_dP?e&_s&MuO_I2w_eva9Wt8>CMHw7C7C%ZH zd&U=5d~P(=P~C(N5~G!DsZ`Q+wpFTC!BY&XXPXHvPv*nk)ibLKwbvhLq(Fq}4+npQ zAcV^=gQDn@$m&3uL`z)l>QI8u$I~dNa>+>g2TJGL$7V&CUC9>Wl*@mZ}yP z)_2QqgTkaHw(L>Z^R^mMrZ+}gqEF{$-Z0wQwD`k48B#LX>&=!&5j*aoZo z5}_qFZyc|)en1?;u5pC={CTuPOLqB5`sBY{eecb8lwrR|4`=o78vwi23HPDPwj%vS|02-@RXv9B>%Kc|QT(RrK^6KbNPubic7Ys@) z*W?!4ge7KtKT;vaVx)I3p!T%WM7sltX~75GROL|m+In*ERJLAj;VyVZM8DFY#Z+z^ z3erut&i=vuJ0G}2k7*Ve%|;iA3+M|Wd{lY5@Gd&DE0nuj(0E_+fgd~V%TrRf`Cw2R z+UIfc$thGq^RSeKEHCtmwA*!a1@;xXZ0VCqO~%0eLMQck9&jds3{4M9`$i2fh~Uk! zkSaC(RrA3S{cWhqzZVD9M9Z8CgrEk=irYM!m8M`I|K}QixlO%u&=N`K5f{{6#o;&6t4Wq(Ml6pYbYoD< z_x)-_HO~k@VgVdom5uf3eyTz4KZw#bY=7pU&N2s5{L^d4+^fA;%(lfZq?Qb@$62E9aj49=T#Lig#yU8B{8iN`tN3H+_j7g?>Y1u*X zyCC?Tp}{S(9&oJl3zaa_TYt5yGHD{dGlP{GTVJ#cx@E{7J zrP{6L8_AZN{sM{NIfpS?sl)4N$kHc|aA_tG%WN0Ug^oSv1t7Mhu@|Xe`ZC$vh-D$r zi*zzk>30U2@%_XW=Mxx(fCH-1%L+0CIU`5>EHI4|&j0{P4gi5#5Z7iWuSM^wG&le~?0kR6ZBB`b>1-YhjKmz`h->;SlSCNlnLD=nl4(Udu2601nr0 z3Cj{f>C{jP&D2Q5h+FKL&LapqpTPA))TSIu;_J-6&7Q^ni#st2VptVxfR?GjmmgfV zKCu`Q>Po^`rSs;fq4*4qxS;cqoR9VRR5JpciOv8oL%)Fys_;Nz5*(^Tk4skFRf8ytPhjQ``$~cvz6*#B%5-64>+3 z)}D`~qu0CJ%#)2CVz|W&&<>i$643d7|9eX{cf#A?3QV}gtOgLXJ*qt~w?XF#|Gghh zmPq1;a~E9GNMS_)E?*VLqa-RCpkolVVcMd1N!|Y zXysO?msSlyZ)&y(e6cfLP|S3_t71al79xD*M3Pz)iMIE>4QBq3Aw0fmQqB@!ftkTGA+==ijg z8@dz9!6qlBt4c3_%#jwbO?&&NTPp3#z5W*N#Z##To$)u7Z;>L;b4%6 ztTkAbZNms)!#;uP$0aMs?ChF6``6KXNdYD_?*rK;a=Wge{!vn4Kmw3sPY!uJs;%}z&X-Cb^zDS4_&DcR^D-1BcuPFI z_rE{?wn|x@8Af_v=Dx`vVgr4V;J`Vvl2_HIa$BX%oEs8T=fL?b-&_c~trG3uFmuN4 zBLtn{NI`k>-4D*(nI{^A_RI2A{>nLr5^ULdC&Bw*)V;o4kkDfep(5>(&?~(*G%(@& zIpB8&SmwH=OKZq;y+R@M`3O(9EFMAfWo><@t>8^c%qbPFpTLJdKX~{+A|91n3dHUt z2mQmi=_lyKh=OSR=ifAdYQ)xUsfPc#%=$fCH!r;TS z0CdOwp3)YJ{xwUx3%#0y^6&FOIl~+ln2`1N0x$+fJ{X`yQ~NnA{dYglfyDUGw1>A( zKtlg;&inA7G-O6K21ga+=A4&{ZFUvu(lP{<7hrn`&dWIgPl4E+FFLUtrTI((y7QWY zekFEze=Fo{mn;OG!EUKwdE&{Af1y_kzeeQ71Ht#`SWrdmgm)#TcH?8Y&%EV_R1{YgI>)pU7^9aA*cV8T6|tom|(nr4DkFX z#)lsn?o8rt_@j0qB;FN-K|iU`Tde(*3n0VF#Js%~&4BM`TF9%fF9K0~bYnLpG{481 zdlUcf!iB*@gaA@e&Ax`C?6T9HPaI0WYfM2CSv$FJM$MpwA?!^KkJHGk7U*U)>o6$) z{KqY#8SN-{!%=i!5ifLxBSGcKMncX674eKw$t4kxX8fDB_#^Jqojej!YjOoL>mN<; zDOtG=UjDa5sFUiK#Yv>f5w2HnX(*r~_8gJyUs2Asjv}(IaNi7JGb)x-E%Qe!Nb(51 z`a_QWi^^#Jo*!pC-V9{>A6eT6z!d!_95>X_#g^L=w@s9fzpC#%%mX>SFE0O~4-Mum z3+NIpI-d;TxOY15&o1LbhT1@TC^67f0DG(%W5V`YA2=k#6rbb49`lMF*+GDEqB#T; zWowt1-e|$W?<}Iw&(Qe2_<*GjODW9)`HB~aGVBss^)}Il%7-FV@eAG3i@>GcjS1fO zKTa?!m<)*>nCAnG`N=mOm1#|f!fvky7c?!T1FKN`AK7qjc%Q38VHA`^P41k_KWv<3 z!u_-FeBM!C#Fxe(tVhDs! zwszkg)~^4J6q?hzmz0nd>AdixsN(aykeD_fVe@PaH7MM!5DDxR#!30BAQ0ksE3GVP z`76-*Dj(NXW@j^?$JAD2Opx>$T^#C^22ai~HG zBs0ghELU%se0(0r-CzL(xdGyA}I9U(QmB*BPTMG{DP`gIAv! z{+qB4DlPDu0M_WF%p^Nw;KZ3zZ~11K(tsNWl4Pwb*2z~@fg?}ilren&NUtyYN+bHG z?5aEui}y)Q-u^$YF(Dzf9es*~N~6^g=N^qI5<5-X5+x{sanNnqH2o8ayi>dv?kZ(!J2$5Rl6~gH>zXMW6%VB1Wo}>1lzt^6OvKNx zoyakD)-tBqyi@WOzN0Wz3t(Nsw6~~+P3rV+x1WH~`6D37^p=?q6HI=P%x)QxFE_h{ zyL^)SddbMH*rDlA95Yql-wL-h4IOw#P%5aDHO#3p=5?4GOT z;sYtozMLVac$=*K4azYwD#wyiU>^^D-OYvAk0Wn%%wzg#WJ32P;y_{iLYq0r z@4UByKj@Cryt#>vMUxr~d6h@DY!Yo&%B;762n(DQDmBh&_`yMGQF*T2<}kmeTG!%b zKsnt24liS!@o9dJM3B<&ZkaDkC>hy(0u-u#dd+Uqy&|tw?1FPetl1>eVShlntDN_5 zzI`qYv3$y_4@ax@)hQd}^?reFN^yAkC1dhrnq2V(fhTb7mn(AzgaWt{Q#h=+zR@UZ z_l&tg5_-Ya*In3Tt+^r!nPtdxZBhUpS1~)xVp8lS7RHcBtLJ=S=4hl<{tEdjOLiOd zDoSIkZ}`Tgaj{h?lkSYFT)Ki`PP8-`uP5z-8eBN)lvCmT7e2g7D4q z)RwZr1tXI#7^pUiNOB2nq8|3#(x-jG5R;1%xLH~|$K@xRu? z^K1BaVW1X5GKp6wTycwTeL9dB<-cO|QwRsdvVmX8G(g@Z4YK3t%3b3Rv>H_p5$4^R6?xyv5%Q1+tZwbd& z1C|QeqN53{L?EYbe!i>q;kDulaWM-FP&NeCv$?;zxhHlO$Y13WhwAUE$2>&*42EsI z&|OGjvAyZDQ|n5W>}DgoI!)nl_=erQxayion$2mVR65t;iS%KmP;nMl>3Bj3-D$j>0`oSna&>iTtLx9iel&_=k$5!o9Pkxi~;nbO%8bB%??tUwP?d`x4h zPC08eVLt?a$yt}_HtLYhJnmo>Z4a+I%G`PU3nmPHT{_D>FJ%?1jLbUd!2{hW!yxXMR(no(|Vq-ysR0x;m) z(XDEq^U0yIGdnUh#k%U1I;4QH{m;l0h=!0I0Qha~x}!JiD;6 zMq&U{)=}}D0bh;t!>vm8j|EW>d za$8iw9Y^&tB?ckB@6(hkt;xig`SWL_4Q&GL7UtO!n+mV&0PaXJb5zp(4d<{ITJ%alkR z;}8KGXH<6)&=M&!_y05(PptkG5^i9m{fL;!z{ago8a=H%M#U+`MxePk?aK=$V%QJ- zX@>TEo>XRjn~n_Y8DHO3bXHuM_j>pHV5pLGi{lOch{+E$X7$^RP+I%PZ+Yx_AHJ$r zE7j@0@L;dbpX#(xop>r#E+Ydyc#;q^z`M-{*pygnHb67+2X> z=3ts4oYb1tlP4=>6cV}rc}j&nez77Xj0H00uOLX@cy#NZ;H!SiHq4wvpSUV)_N|BH z-Z|3bQgmiJXbN*G&<)DQ6#q9e!%DPhqF^&N%$W^rRk#cP004XHzIBR3xr4slug|40 zW=S*p2r*kqHrk`w*}ijW5(83&|4P;go*l#KS=K$#Go5B50Yssrum6=_Mxxf&n|mnA zJxd~xSVEKFCVn9Ft@IPL_d?a7PM8Rv*Cq8g64yRwre&_s+(C7blmGNy=C{gmyG1pP zv{6Nf?L!ns84M{DCagp^{|T1WZUq><$L(l73;S%V3+!7kEKRz~J@{D`3DaG%LO!5~ zMn-_jkHD51Lg6=99-`Xi*+xC_&8Ps!x#Swq4Y3j>ZI>8zF$X(q>^S=T6|awv-Vajp3@-m z;AHV|UXMC0UrMTZw=wRaeq;8<`l9UQV}>K9K<`Fa)nHsgW=mfPwK=R}B}#XX_^EtZ zd)cQNJ|Bm7VUABPZcdN-=6BtC;<2z%Lq=zIEm20T&*@r4QA zZ@gese+bG{H`+2I*rj8jO%N~{9^`(}sOBzm$NdjH;TLoa!0g0$Q13#M&g@?HSPI8< ziftDB)@`R2aa1{3zlXnPwn&>=;uK9=;{)5+vqu+qL5bsAaRqeV3=Ak#y%ud{^bu$7 zNq5|9jG@$a<@X=P#}9r}igWK{{Q|NBIS%qkH`$HlU`+?N>r^i!1{nMUSI>8`82zOD zri`-n{sXQ^WCTdi!Z|y<-<(r&Dz1Hs82I<(`~a*C6|r;nf!T;$30=-bZ9dNj44>$8 z>SB;W&ds)Gbe##u9ViDYW8yiaU)IrBTT7xjN+Df6K5$Slt$WHA2^4P|IFsC2lBHkaSPN^_?pNi4zT5sluA$Gn%d1r(CN(Ht_iwJnVx`5h@YNc>YbEbHX~-I_B&Ds(n+yiEbm!yRh?crA!w@R2Okpo z(XlGdiw0?ViR9c<;bF{F&381Q?^uagb^*1eYOKX!CPTWknP@Ll>yjU7sdoZFH#jhD z2A7b$IFl69*+E^lYoabDN4lmCCbSO+<;^x2PFS&nk5$kdb=t}wzjGB3 zrM)4IrEUic=63v0ZZVw_WxG#bhCXYoDO{X+CcT~u-s-w;eZH^5Zzw*^Fy^Gg_00Jk zX(e=S(oyz;tyDbogva0~1=1(_VV@=QM>fWpSJ~a_-qs(|Qz-_R(moMyV1ElXkR#16 z6P>}{ed@{&u3^b&Af-btg&I2azsyFTA`>>S%K>THhG)X|&)3GO%~nc>4m|#_GGXGV zz(x-^haZ}8zA`C%-trR4^ijVpvf%PI@J`I&hm^sE5*A*iq0uu$HmA-IHI4Ry*BdtY zRp(lY>b&#(I)5ROSa-*6HMQ*$u5d2GtN=-@z{rX)N)(YRd^K-)Y~7rR`umUQSkju4 z2dhGZr>1J9g5H?Pu#vlC>GM8Jd{xswl(sFuMO0=uYFm*5bFHG{_G-cK4j0RnQl1Vu zByL>lSZxckarh)DHZ@NXsqW?G3?oSz5M9KJSXJuqhJ&5{<8jUDQpUTmzHy~C?^;rV z&#<=_834{sP66$;TdrsnKzDhd&Hd7|b?-badEBOyc|GWH&`R&GObKLlo-j zE`kaO9K-%G+o9n#@TqZ5EB&NxVa%X7%r*_9oFA!=Hu6Ey8OdXDHl^2>f|qoOtEibb zAV8H}lqt@ZUG&1O8!Hs2IgDr-j;?+!R4kh!3kPX~Q$pdBCX%B8Zp86`@8Q-Pir>TS ze-&LFH8`PhBc};(>>SgDlB>_4*X7&cQ1LNlsi`(Ns(99DiHOYdb=-t}{8}Ov;9VD-7uP{K&~>hHGW2sYuOI$)R{NhhAjfIf>4Nzp0TG?XGL(mORk~A zNCFxb5Wy%Xj-9w|bv($}alI8&ZK9`OZQxvS!mp!BDJ!sNr{o#7aOz9Pi!0lWt+FIX zWBdO>%F>B%p+^FC?}##8_JNZJ0?4Uj=BPY-V!7mUntrTq@TXN$V7{!iLP}t9a-i)S zU6!bpmDtUs0X<`F5(C3{bMmCv|tpc+~4hIg`nv^Y! z2CYC&E$R1pp;#CCuzZ1)MnjR83w+n_ZNzr}- zs8;&s)+mq}K9ghS&SC8WLE^|VMoM4K|wB>KbU*?DrOnVz=BDR|Dv z%Kh-A+eZMFy0K#=qgm!B(aNec-;BwiTjhZ_#Gn&doe0O4$Pn|vck!~J42B=9O+IT> z`*C&}!<^)P!+Jz`6-*yyjdH)4z<;o)2(ZpnN=GQ6sUD5p7nUC8hmtz4Vpzp3?r*{? z3P(p$0BGHPV0ck*KV;b3(3jm=-Z6aaIrTnCB2K_7VES?sxnD1T@V!A=_o>XaDawYH zZWzQQAhZspKI-w?QpoT8Z01zYMWF4Vi=$m?uB3GTVfkwtqTZzD`om+5=}+`3J3MPsplKQ z*j*4Dl*VXf*jy&szc?{Ak&d8@OPe&iuyiZ}zJ3dpW=r4s*Po)5eU7G-oeI}%@wrS) zOV4c+f*j)a>P31|ZcPT$a*ReCF|#w|vD9j~Jn(-MbZv8d{y4d~%U*%?uFoc8 z3~N5?t3(r7zstCSE4E+o#mN>XqrKJ2Itu~TL>`dMC8jA@mb}Uv>@FWnrW2(vyv=-n zq3I1$+k_M`+Uo2z@Ip_-@6s-S4_0|@%Y$mV4`sI@!n>2uqP5CR;~*#!MjTh~vK{;Mk&)#)8fsuJ6=VkV;Qg_hofg4A<;L%s)UbTuIKah>8g( zdn%O-hGZ`(se#Q2Ec^cr1$Kw8NPv(mtVF4{$A0zp?Yf-^H7$Dgy;loUQ|r=FO6WzN z)gJ#=+6dR~R?`m_+UZRv3o%N?y=D(Ra%~roB8; z>@e~Ysis2T7Za8)N<|=xbG%m+OBY_h69GLt2+~T^xbZ#=FIoPk=Pzlcd!;q(JmY+= z^Xx)wGt$6drbOEO=!qarcS}{#$j}}qFvUGT;kq|>$Ug~{tEAf&P# z6i?0Gpe@R%L9%0Nrz+phZjxcJ-)w}AOvwCLnYT$eYFcE>(2)~$5uGNn7Q~6lHvY9)lZk!2 z?Mv<$u<(_c)@BO)uh!ma=f~kc$tY!_{XAd;4PW-?pY6$(5fyTIQZ|+; zAHMWh0u;(b9kG9$TKn5@%v__ZFu5g(GzV+k?E*vCqnT}$SLID4kAs8_0tLvXRCGsq z)9^9H6p6_GL}}7haZ!MTvCpSAdrJFoS-$jzcYWmn18{de^iYOt-Iw(jpc&?ADQadj zOx#QI9<%njf?G~Xo_KoE&^M7`aTzze=(@Hvxt<2F0Qv>bD*u)&FM2Y+Op7kq{N0pg zMWVHROXG<;zzrlpSL6SS{i=&>ppQ94&J!7;K6>~7v{lCW`jv3$NxvM|4n6C@*;*d= z$}eg@^n027D=ih&0EFo2tNB7R*2!bYH1gVfmvX}T zX&>IdKj%Ar&ZT>W9t_H6tl1KW?>$OJR!5)oSy1+0GXKq`uD|Ruqq<9J}a#k?4Fi+6@q#*fBI->LWK$@0fd5 zSke&5EJv=#y4LyD4csBXb0ZZ^k2{1iy*@<5_((@ne+f?l8|RaCf@k+;ua^Kye+%57 zxNa5H*u3g#)DsbNrWtz}|1#BYHBHIC)(ZpntegsHZ%R->glooMKb<-MNV@)tEtqMx zA$58;1O<5Ex+82}?srO7lJi>?teAS|m_qoP@5w~tHR_fY1dz<%=~?%3cB+tL?yCx^ zOa_it_pCmYfX0DKc-1~x!F)mGApQ%Y>+k^X=u4rosD99g4mRwwH?){ zq-S^>vPtSx4_J7J@5-ONcyF{s2#Ivre{6Z3M~C$;8`MoF$C8x%kZ3<^P;|!r!aoq& z*grPW+wT+qa^_!tUap%1%gVZ|ae*^4%*kd;gY0~!EpdF$b!B1c`7=BrA;GuS-(6VK zW$W0NQ#26yhmf4IQHIJ;Ke7wqXdIk(&cJX4^Fi>=X(Lh%f6Q&2Cjp}-%xsCA;W~U* zO1-$mF_s2ZvZ~H)$s5I7Lz|RMG!iibU}zQi8!xL{jS(Og%EQcBAjIr+4Cn!=Yc{=j zpi(cb*gG!SvQ_Fu$9P@cWF^QSgtx^?|Db(thh+I#(pp}^$DwAn#rrQj}SGw4+a*P({}p_0J^6Y(x;ItWlz z`x~Fh@UKQl1*BxskiV!=ECW;6dNG_2I1DAw#9`j&7)kko>Z)pTI1M9Vg zZn+i)%`i9r_b_4%Lqtfk?46WV@N!MCEZdHh|M`C_1egAK02Em%NSs(_IlG|oQZ_is z=D<0zOzZL(3zbu+mKTRItkw^!^@?tUZI0rnRO#}~*gg1cvj1RgmhU}Yq(<(Q7iPuKc^a?%NqoZgO z9XQZ<6!jw6FgfUM`>o0XKs_hEfj^R zCd|yk)u7n}_DUdv0n}zURFftec4sFvBg>D`UDHq3bVgweiWhij0V(X$hROY(xZ<>0fUfP`NDg1!n1%jzx#|%zr+`Yc)Jg%Nmfw!5_y5 zFfLHEukk4&Pgo0^jin(CG-i`N1YOxqxU441C+3&!`$mq^?RmiUyx4`2u9U$qj@nBj z=C8nhiNcbz*Lt=g_sO;y>^&dNg$*yh!DKcm!r`eSg5ByjAtp*TW)QO1R+9Ld^vTj|-NP)2-vpl=Q#V{?b>gi^MPX zQ+6<$t0OK5fifplc{2voD2%&Ye7q%#-}HQwuE+dy+z$G3X5vX{PkJN$A8PaqzP?Yo z$mP`;w{GSd!cpa8)!C_pKAoc4ND=f(shNB3{xF*I$4<+VfiJ3-Om)>RNbEO0Lgl~F z!JY4n3Whrt;*#SM`T9C_gm(x@cy)z_FC}d7G;tzKTzSE7jvWD%!RgYbH+S+>N>0t6 ztB+pmTP?JV6pMz}iCm+Lt~hGB)66S8v-gMD*F0SLSdrqXeVNqNjFsrPTQ$q3+zJAoj(UZMVre;lP*k56&|7o z-HML{7cAVkOZyV>*5sIQIop{1`fwh^ywZ-P*HIR&ww>41S*aBV(}GtmVaqoVPYBFB zQk6;hbkYYr8tnJ=^)7fA&NQ&GIhY*dBR4!!#T&F-_IrE43LK(mP?`7f1#Nn*HWW_EVz z32I(5mb+9ZUm|$$gVmw{Y-98Xer$&h)2M?nQ7Jd%*Q{k#cq#?@vqJ1@pXXbdlT2|9H$rozQb;}cDxKhA zN`4}`PJ56jlOL%^)V~}eIZLODXwJ18Wof-56+(ADo;!lgEBn@OIMH@;z6puHvw!Y+ zeoDV^YhdqHDR83)O7ziO0eYE2-iF+P%*{MH2Z8OrJuD8GysWns;8>%JZ=wAoDL?q{KLI*^+pw%o*+W$EX}@1I*ao+&%2 z*(+#madg`a+u^m?74Nv|aSvkLGrot_#Q`}jXL@F)TUlU$A(lNVjw3?FhosK(4KzbBe$$9)7#n{<0vV% zHvCvMcuCl?8SExz9W?N=f?ux9YVVQ^AEEEd`0RqY8PVqmSGb3p{Cvs8nRpqH`waeu z5EHhSn*SV~FO-EEkpzuaQ)|8Ta#ic;m2f0i&Mh#@jP@5STY76)p-QD?Z_}g(A4U62 zBc%M_t+V5TSFNucnhHj|OY>$w9%Sy5j!xOG3FE1pUOJ;!9Y4FU-jQ5}7WTzZTvg_z zynDwBS^C@0H=61x`kfz+tWW7z!&UCypFyX(GQYnRMRH9sz$NuRh)C5EflclEZ+Kvr>H-HVh8q_8he z#W>0kvVFZqKE= zl$cboN##d}7`sw27MGS2yHv>U6BmdKV`0S@_RZ=`;q2t}&OV{$w(N4JdPddgjWzx^ zUo-6wdJSvXLQ7u2<7Jz&LymFp^{8aKyZhmqxS3c)Ncs-7>+c@ofM`JQP8=p-zKdm* z5OcVh@bxk-p^4%DW?ad)>I&k^6?;%>ab=`tq3+d#`sc$M8CRXv?=_m+#d=LZctm>^ ziRkH`T}zfs+9p(5E}Ih2#WsxxKw}?JM-c(@sq0wmjPd_#KYVQA7>UnyZLLhp=h0se zyjqBQ2_QFZlWqcgYEM#w@hzXDDy|9;tifZ-otI%_I_Ilw7%o1tx+qf$q{XH6|Kfyx&aMqKO4O;XfA z3J@*;Ty+YU-llg)FbZK@hg~N6VIo0+%*NOiw#kOD9-V5gG#^HPIGM21Sb6nn72Tv8 zWF|B*&Brv2m3#4x44`9^Gsp z+wM!#CKacsLolwS%(xs7ATh78LwQK0`5^!J>NBGIEh4z9`9ihxxN8sX(d`c}r~^zd zyl4s-QG>roB@-Rla@_NXo88lE=-Wq`yQ_suvxu-E?uW~oY-6tEkh1(UH?VWc=-AP| zSez1>PSQ8#dH)CKCjo4Y`I!{k3)W0kq{V3H%j5Zs!4+FQ?N@mKyA#mgk_#_L*>R8C z%N6MhxXLH6l)-0j42v2Ix!EbDFUsdrPq06Apd@B2kuNM7W2Ei-;q+?7mF;!vD~-GL zMx-^TujD4t$WP{Y4#IS^B)BRww;V8&wfmixcT1&*k(tt2#c zcEeAffcrXc@Uxi7$S87L$mofD<`a+ax4n;Yuh`YFeRjgSb@6G8Su*4g#<}N`&01W( zuU^6|HglWZNHgQpDgU0|>I|-$f+yU5{mlB1j34-HpY4ZK^Xy{Bs*Leh-?cWS+@Sn- zBxQfY{tHSPD~&pjxxOO+Aw%|2Jm;lS?C$PrP^UE)4!n`N>nh7W^OU>`rEt>Rabz@t z(6ao%&aymNHfwa@5lw4>n2B=ELgkk4Z%~vOu(X%*Yx`Y>UbhDFrukRqCMgA&Dc!qYPrrL|h?F!vKwClkPrgK;bYwJR z7>Hsjm8;J+J#`>aI@;t2*NGro`WkbtO%jXzqOXhkZjf)!?KY~ zm}xR^m@A^q%YE29ix9CV)1B{s=knBzx-Z_i|j6 z$39BgH}OB%@bop=vO#Gq*PJ-9Z8wTKzYZuD>?6aj%T~||T!G3#AZ;H9?p0%s; zEJ#mwHKOd%q!&Sc@c!WR7RcOq{hUz@d!wEJCE)Y-AF~tc4QNf8XIuJ z^qPI;*WcBtv(Fb6j2H$*d(??@!cU4&4&4DAT>Sb*&(^zx`)yaFV%Ybso$Y+^tWujF zkXix*vQ=fH(|PrMHRgR*ncyZ1Q1I!=x)8f~V*eo->@#_dNapRu;RYAIp&mx-Fk=8& zf~({;8FjEfh^K4*Mz{+u49WicPA=Q`{H`7h!OG8&SjKE1Y2W&H7aOC#R?(pe4dr~M zt4yLQnUORZS33C>^{Vw@6K!4&xd5&*|B;q#;W>Rvv{XGZ_10TFIFCX(|&DUNsUp;>hsU}JvDuJOFFk0Lr6DQm^M8jONQnO^I`(me2P zap*Pg)ICy`#Ut`Nlx{YxzCG2ov~ZY>4xHJ^e7$Z(?7xcAt@k^xoU(jF-0Zn0Q~Rz` z*O_{1uzTI{C>}ntt+{Deco`CwIxekSSbOf$`NRwk*#k>Oj@!+Tp{q6hU(z7~A zas(0#eir*qragXo(D<0?=S|JV4Ktj$UU4#*GHx`g5kW}sVT^k$Dyul$o?Oh-k)bDm8|L!E{p)&Biuq5u-plYS ziG7ewVPzBbuX2~cs3~if81PTefM>kmdd}=aX$=?`xj$oMw*9zQH!$0mj93_fLx|P! zDXDrj<6p3v?tcjMG32|a=tHRt5}2j;>b)*!i4WU2%(z#F;NtQFM%+{0N7Asi#k!D% zfbw5&*q@0h{BY#J((?6U=6sK20Noe`^#P|BW`wq%ibt-Hp&({z@}QETwx1tuLsZ7E z#7tPr-dg8dDmKfTa|{?ej=cNXXW_C`yeSUcdG`$yC-K)Du!Y_q;L#Ne-6-wp!)WPX z^gm&}%TBasAKP!@3y_SnqvyfLDlwP1oU1I;NdEFpsTKwv8iiN3J-uVnKI*Fw0s3g6ZuMPH@+p8hK6~@m; zfG3daI?h2VGlXfmS^QDzUpm6KmO z=31+i0bky&8VFzmPtE)NG`^E37lwHZx7#v@87r$d|?K3`%?4%2Rk(L>7mXFME8@iO(%qHi} z>dIJ>{$=hjbA1>5V%h9~lCo)@^%ryh*;Sc(G+cA|eQWD)Z>xEPf|vU201mu#q7=EtjJT?$G)A|LA%TsHT(e4>SPu6yD-!tow(oqin6Hy?rYO4!*zv|&?H`@?l7wCK=88*b;Dq8F&`D=q!7T9 z^cp~=Waubc@uwGnkaZzPSQ;F9b&0i%x*5bQ1ssM(KIi25d@tEPphv-LZSd05Hb0Qd z03RxYwUNhn8zZ%Wf z*lq&YXqe|ouCSy68NFn`)=3R;itrWu_{WX?=q4(MaBJKj@|CE)&*>=g`&`+c%o&t- zOWeDDMd_mJJfeJ7g1rGB1$6-^$_0J#kgc3Dbn)K;xOh!>;2kASa2fV>gZXc5*~w=@ zPaphXT0xubat)mYbgZ8X*wp6{J)qKKM|fZcf{0kBsz*fz#ihdJ_q9l*gcleWh9dTA z_iLS`l!0?xSylNn_DB`kw=ltsT5cAt408nzej)hc*nV+TYg0_X#G5|bX`VCSLGqR5 z-B)q(J>Al2Y30)gFfLW=l0Fo;%7C5#?xxYY(|C~3J#oO{*Kr`=)`kcoI#rIaS~bA` z+#?Q+{FLSj@}ObjvGiT2*0T5;n~|hjT``8!XAcp6zrPX)NN|BK-2gD@NtJ;XW;gC{ z(%4Tfsd&ykqbrjCIZ;S=D~6hX0=Qi+Lw)tWuLN~lMxuBY0%ha+{(-%Cxe=E3 zGuM%;;etiR;q)r<74?~Luk{pULu1l8mKEr>3l}EuS^lh$ZVHCF3$laB9k8pt3w5Ci zJ#G~RO|)SqQhjsYB$@{J@n_2Zlp4#Y5q;fbA1LHKC&M{w{rY9!;rH4oZ+3N7Men8^ zhF!j4;PV6G4DPItP2cXzX1N|Veo_iLczh^27%0U*xdWcxb=7a|Nml?v+Boc$j%ecW zNHNkIqBx|cOVE&l(TT<2INzNZbs+bvg5s%zRcWr3D5S0A(=`_Tssv^$CcwqI_A==DDyJPc8Su(we_-sI6x80$7O*9Og*tUq=Y4DA;4w zS`U5OLLL-mN>LsE#g|K1m+ZmiPf93E^MmsC)P-+lMz@=0X{jl90;R!5#Qv+c**s9^ za;I>(VU1@WA`l!PCD!>E(Dd&@D;2HMm|E1)$_)hI4@5Vt1C{4U>P_UA6JnxY9vKE( z7${tT*@0$p%QrgWejMJ*rY6C1P(LQFL{Lx>Gj{8FtPmh%9iLqG~4|6lzjUHpck_V?i zdxgS%CZ%Q5q$NBCc6oI_x|sf2)_URFI5f|T?{8CxZU(`*!sHW4A=AmhFNP)by85OI z-;v+SEzfx1=7EOp8y=chR-^a=ngPfCGZqRs$=Bw0sGQUk5^!UeZDy+qH_zt%egLIv zU%yxpULFS*)d#eBuIAKs;djERb1?NCXf(m|CP|M>VU3SFVnPfI6wu>fn42^7cMIzu zb#Pa~NMbvG^(q_f#>tb;luRqt<^mWT;7$!DL8F#^QyMR~YUTa&iC5IlS1)SJA<>cy z&)oKX4^Dv{WxM%_=(zR1JF&uK(OT&Nr=-Bpk8xZdCbpQp!P<19>?6S%zaAD^sQ99- zy!9_`w%rsiDktqB$kRvy!O4e3RgE@GF*xfOpF2W=X6y1?fiI-Y4_#))BPwSg?p>bS zbfVCU*Y2&%Cdf#K9d^j^AiVz&a&c@3MvkwSn>~8~vXw3m4WHAI!AVU}atde~D6AmoQN-dL{GfesqpdPhr@ zE&;^q@8V`Lzhv^AvK}w)MbyPEM&jv~83lBXpePk)7kXpugeOkEBMd?K;5D;s&Pcse zB~0$GN6vbEI||0ZNewUmyPcNWZ370dTsTummq9D=KZm{8czq(l#srv(n?MOpbuW;D z!CCR;>`Z(2L*Ox(>V?x4QG0a7x+@Ad@9{U?P2cZ(c0FEB(_^3LK@c&Nxue&yckZbHySLeA0bJ5@$miowJb=p}3HSLtC_DBl zV8{QX3nk3gP`PapD3ie|UJRuQR(@3-4zu(;gYn&__F+{_xiTPYo~K~|FF3Ug<-gxs z;OBz3H-DQZR= zP?ue_4A8@S5p%C5$fAIOr>SL4ciV0Y+VyG8!zIG#qHoy};|IV;faVsF@iLYG}QQFe2Ce5D< zoizx)08K{|E;+-qaGC`~KWAl3GU$G~yP8f&x=&7cDF6}4fZ{Ah7HywSr)uy}=%Q?O zGxX5XV^E|oLt35D!3P-ad`g;&7yP(1IuZ0c1mA?>J1h~{V21WJ=DC+-A3SaV@r69@ zzjDB;D+XrikUo^u=MxUP4KR@g{F# z1HJc{M^m>}9vlv^H}GlS^Wr23g?XjL0e(n~6S6v&I~}^y^2Dz6f~K4*E2W)E4S-Ri zVSwk*6}|fI;XZbR6RvZ@&F2bF!WM3%y@pheT$G;$OyNgUBip_LO4s(x1J$`+=G8-l zZvZR7E*)UK!0%-dQ~rit{?jLm(tZ+Fps5}6J-^S#ri0Hicl_tB7dsyOqN{4RDK`!g z%$)Mn5YTs{a)8S46x?v`IACxr?t$4+mt$$%lgcA6VOtjfECu+EzjbOd?Bk7vPaP>x z$C(Wg9RikaB$lGalL6wY#)(5T@iIcMMEbEb=#>(BxrIqE1ugB>(awq2JNxTe4}9FH zm>1p6B}BN$*4%E%dj$)IP>#e+w(8%pkBi(ZDsW5G3B@RFKd5FD?*7RB{NbGYuFIgt z^WqD=R!v$rFhFeXYAc(?lbZq8r#DM*VG%=z^D&0Pd=bG`!QSRL~`tt5roO zt(*bo${v-QfpaA}iE|~Y>21d{2-krj&fps2&1(LSmdI$X+yAMuEevz*ne`5&SGg4w z+VQ#q@Rb*gN)RDcG4TH7Xn@jUwx8R#A42I{>$fu|f~dpHz%gL1%FSRIFV<-vd7bST=H@UZd( zOC&VS(MH3my45k?t)f0FI08-{X2pBrynzaxpc{HRD5OsYc?pAWj>fI5yWg+fb+2@g zBoVmlea8^N%nNP@UL?ui{+xh?e3vMAUD%J)Ze&uokL=@ZQ8r0kqX6KTMO0{ero|G2(F8~P&mu3 zm0s9;a2B!CN!ywCGU(uYQ7q0EGXF*5Ug`IRbB55eG#kANIZ77~ruUqqW|#93zU)^c zn6WIxP*u|pg~>BjOaGdvA{;>u=tTYnqZWcjJ3(L?u076vuy+tlP4G)5_Iw}R^w`2urOR%mHDe^@LmF+$@EOnZaP8Om(AGT7^cz>Ib+wGa8bgr{WHq!4OSSus< zI7-9~H1-ZfKWd!u;pS~ynQk6fkr}KMvcU&iPLCH{DH-!~CBIXK?eSW^pJrXe)0tBh z?p8&Y;pWUC7!?v#H&r#qJ9imeICg*4LZ3$ zo>6r9%6C-RVjyPgm;P zA~pwfb*kw59kv>y?!fF^_+xlzf~3FW|=Pf)EaP7CvR7$`_a;B-?KW) zTV!*|nTvg-Kc-)h<)Dw%;S5Y(k2Rsrq$zyUsqsA#MQNy7J=IBT z${y?A)vC=)KsiKh_3-L+x`$N!A6tjagx7r@gq*Hij4?M98kf}`oPW1E$@8sG(5VRA1w7-ZNK z^aR_a*GK9{2!aO^p$DR8lN@}1mI;zwP6IjiIsY9=>-V(b>6A=#thf#_@V?|Z>Lqh< zrWL*tt~|kezqApj+G$i*>a_aY@d$w{OJ6N=YDUWtec&t4&IM0D^RGZ8G_PVZU=-$nQLo*q6o`TZ6UfzZXcCHsh6d(vhG7$ z`81wQw4Sbp&+tQZoB9XP?P68{0Hs&cLx}u(wM zYmyYeF}(Gvu;$=ll~g2DG`b(^os?1p%)}!neWrpqMiKlUZ(sdREA3tjtaUdabXYsS z~yIFO0s+3G&vbEU4RLe<3mX$Yb%C%c&I!MQQ9k7 z1)m!1ncf=Ot5yK1HX|o-tWFWtC$qlEysPu_Pe)%>Y&s8W8gmM^r}YSUYFg*9;V#&( zMX=BrH}VbOUJF42W&R6u=I@M}Eu{&SH=Kb^##cMv5Wp4sr0jut1&MkdgXZLqJIcs` zdF6?o*+7>@Y-_T?Jqerur zEe;VXuVFm4Hjr{=MKhVP9dhv22W`{|ePx(eK=4WU%BLfr4usc{CVZs{1HH(Nj}VJt zLe`b}wxSR*Uw54~f9&d6FBZ?c`Y|2m_^VlVOpk{RLaqc?q4e?IDY-BIL7zvkwM}9e@1WF2b#3 z_%9vMTzu|>t^<73w@(azUeL67vE1ri>)zHk0dD9IC*@GnU^tx=iT=og@e0yd>un`D zHxWo?M*lnsd|teQ2!m6pQvUV+qT8JOSsehc0xR*BxHls@vyTt&6Qpzr#Z-7okCzlNp;*9T|_3~ z2G7iggsCgr0Qn4jB&u?({b#@ATIBEQw4>VRJwY(SE-U1l!U77qlUMn3BtpZ4b+O5u zE3j?5o;*|PXJA3c_Yp}Qp<6WDEh%pNBT+@{KJ%`5_sZ5y)|YL?ns)`CJIvkdW`_}F z7`}i!tClcR82braccwg?=;reVwzBpRbdYo1yo+8+;bxVtkxxENl8TksG zyjbpYZlAUSZzrhLUr1A*CW!<~Q-nycfIF~m4pnQHXLn>)i5yt@yX5*jFqFV@bVgzN1b92e%AP^Qn%Q5!hc6(r_iUY}ZoXMj zd?C`e0Uq!pn-QTFV#b$Vq^KuN*zsCJ>jf)F1MC(Thw{wI(Trb}h*18ZSz?Q^7r@x{ z_0>zUz-bKezwWL9#hdDUbyrBm1>h?O!H7j7(nv)NVIkw6=t8_JGaub-L946+u$ zZJvlr_F?L~oQEoyofkJl-P#n8J!UE7HD<)N(oL@qfGgG>ZQ{~YX3u<%Y46ta;z z6~EtwfAvLqGBa<;8^&G<;-7JlUcGaO5az9J+%h|z>if9>1C8Gr%LIspVcvhBHV+(tTP^WE(hHbmc zQ2k^E-tAf<9Qx&Yyl#2runG}f7?{wFobfuw+FD`>*2@92p(ah>|Dn;RvzBK>fvubd z9!n^Bb0niz`Yx#L^O`8CZ)S>wUgz$x4fu6liZkk+xCma-sg}x|R7s1uwJGN6OG~+v z0ls`ax*l8VWNHo=cm(!%!}IpHg3#*8EuPVklN%bpL#voFEV;g;2wa7buEF-^SjD%4 z^;|XzvJ1nWvz45+j%&n!O3=~&Zpu@klr_QG zX3RDXXYN8NwYwt-c;7eI=~kw&koH5Ayq|5)xR)%ttBobo;7;M~%>?euWPD*b08 zfz%^P^~za=W;_)_YTd>=*ZFXD!1}OSEo3EqZV@|9!d*<-*4@NHKl49xFT~ytC=6|+(E>kHmxd5*3|qlg!|BXv)sa*a=7jK>$qUGEU?%7et(~e{o(C!L2}S- zz@676hIooJa)TsHHaf8}RjgI-k;3A(iG9<_ z-rY^VsDs@Mb!<~}V%ka*5NOJ!&9u;gu*dNiPbYEwh)GXi#7yrN$-1%aqJ99#OcRAY z#=G3f+(HwrNQBpYxhXYhZXU7IH!HstojLM$mtd_MdOSip*SIw$CLfEpG2V$1)+H*{ zGg|Gm<#|z(K<@GL;&1copkv@ZOsS-#ZUws^H|9n8juHPzmlMuA>~3O%#v zRHH%hgMMvQLMm_Wlr$@_J(bc;Gv|3~UVx{G>3GlgfhbN6sB=ewZSC!I$WSo(1!>Aq zdob-e-5YvMybd=EG7wB6zs$=Gt>kPtXOm_mMu?l&RF;BYISY;%ub3tm1Z=`wkN+Dtkz!(ndg3B1<)7`opwsDf-#Y zFP%hhTLHpYf4D!bK|}JQXn@3UkM=-YW-ZjiBN#@knVw&-`&rN3K8UYhgW~WL$b3MX z{mSz0%z0xo>uBfxb+G?@HE$EixbY{V88Kw#pkD< z>ROmGMQiHA`8GT=yZd&UHnH9%T(+~Y=ql<&`FNe8U|C`xBXlH@{$wW9U2{8LDJC(R zM0lXhYc-T?@e#w10~h){hI_RmmFj8`i#K@qRy+|KU)5#BS3S5v5?|AUX>7nHV@{DU zD&8qKEr9tXF68Dj{ECWmtj-CLx}egl@NLfKMHM!CxbHe&bHecj=tMWgkja&fnb*ym zp#`G+?azgSgMvVS9(Xw;rRo(Lwu&JeVZb~mjawp3(9A<-`a4d*1%-hC5!$sqHd2rq zZT-qTeDsHz6Mj0rW7kc%j!`i6kz&vJb^1mf`z&SrKgGvq%0C9sxf5U85wnkpOMa#43co(4$!=R=U`mD7DXh6fM1+Ts6JeKovW*# z$~hZxbr}fkHJ+(jXEl44^PPCw&S>}e;)zm;gVk=IYRvUV>XX&6j+w# zCB3NmOS(J{&g#8t9HqYgQEKZL6gLPyAbM*^ooSHqJVN^XT781!%hPY2O zX8KCwB=R=~-=r`;21=mb1Dz#kY}R(v%TU>Xk|UNrYk^Vh=iGu(!#J5$2<{a!0u#$g ztl0(&w2#S!dp0PAaFp!&6yl5AiXs|M6{#<)m@#+p#RBoWZeS^l( zI|Yk?)gEcIFPDg!2mt@vmN}8dZ{xj@`Nt!=kHfL`fy}KZ!YHBcpvlJee`yCnZvuGZ zKOk1zSBRl_fR;C1&Pk;E+0D-)*mTJ0!#0CAiX};dKih74>}6YTY|8>WF5)8^1f(p! zJZ@qfql+Cvb6ts!WejT|14&CLF$!2#M{np$r|ZN_d@LE+d=qL4rAUNo@%uld z(+ob0{jrjt=-C*pbdwc@0hYwr;c$D6vWj^wJlIhtS1Fh^7iaP6kmyk`xjhO=6+x!P z+Mq2gf!N|~x3cR`XkI@Z`IGCX7N9p}xXTZEu-qvq>yecm^I_}6$uDTeHD31Bg8FhG zZUtZW3PM$c$7ThVJqwYM5=j$7PP(+4wAo&fF(Bj zGqECCKTeyA@`zn1(pl(t&y0zlmKh(q#Sg6MzefftO}#Zxrc`kX{^nb?4Dh z)HDM#7q0EYysq<-yWGGM&UEjva#sT0P}pkl)9_II<@G07-EKysz4$Uz0QqmnhGrJS3Zz~_~dWEsMmUzb=tgygOpf1qlHNpWL7 z?DrL(GA_t13S!39?c7D)#5a0`#u@Ke2*jJv=H-*|-|~9ova?`MUjcdcbVy+m^_^UI zE)yeqqr!X19wfEcW7V9synP?Gz==>dd-J0`absDGv4PUlzYZX%2yZ4mn-4amP5sNFd%v zR%_#-3dfv};rM0lP)fCLymlD^YtCzbosvSe#I96zMQuC~C8Izs4S{YCSiH_rLoS-} z*ta#s8dx*QOb@Aps_NBpD&MSNE)SX{`Qq}#KBetigzQPUmKh7{ z_hP>I_LsSyvXLpdAySwA;qUO5KMx|NE%p`45 zmDP$6>n5bWMTe}fI!Mrm`EejpARFDR4Y-ukQ^WX)NL7FjS)l8*mAm{$91Mfo#hoCo zgIqAP+IRj`Qy(enEqsVNnN+;-PjE*y(-&D$Kl1a_cwMwxYV?GR3T`t`CA0!a{Yb_x zG6#gOTQ3e81%b+JdFwG#;~kUz{cd&NlG}f9mUl#`^1GpT4-Gx<$=A@Jeq4@6Oy|Le z+We}L^wL-#_to9{C!F5})J&>tUNKliQHduDnb|d~`yC|K*DSLSrAoC~A3?|36KW-F`JcUf6-IOhOt+$tukAId{J-bS3!)_!?{^9|aj$P>FX^ z$5!{I`8yFV(QO0&bttUJ@G!@_AvazeT^c1#SfwPi-`ceCx3JnTkr$IwKkr5T?(aq% z>L&2#PPvcVZ(I(Q-$gD1a?I%Bk~l^DutdVdK_`%P5mk^GVX45J4s&ahxZe4C`?eH~ zkqSpVF)AM})SuO6ETlE_rbRXj7lo)%}2zN`cxK0FITQ@?t(}McmIHUrjL+^n|k1JaUoQ&y$B=C()P9A*Se0%Py!;g|R z4NfurBQ7v-JD~|n91J!SreW{v2sP)fM!274Rn*7vjMhQZj{^zKRi&D{Q+?4jM*DwO zYS#j1Go2hb_s*;Q;0!Bvi;cbKZodf3BxKc=Vx zEg;?*lnxo18xQrdHct5RqwifyV~yskSgzn+&VTp0n>?}&+&)sjv2gixSk}~06*-*;WBR>G(^AM5J^1tt;&B zf*jnX;hA3~V~g078rUO)j8`by9`!I+IqWTT#q+_@`$nscZjPGMKteY?6krmaDw5hc)~qY<~R> zF$hJ8bVAKHeN5c?Vxm~#9rH(P%zEusaJXBNTb)j6&5iv}ZP0~`GhUy?bo__?x?%q0 zrfUR#_lx%);##=))~{PRa8SL#Ee$iP3-%rvB7wC6s5cex5oyH8Nc5Ux)M$_rrl?IT)_1!ps#jhnY5ed8<=O(4&-g2cV zh(%Je@i0h{P)FpZnLhe)y>8^lti*ltMx6EA-P`s+WKv0qA~Ka0`=9=8faEw#9%+); zp6Zcf5P3&G0ZnibaY(D!VgnllISmE0>Ej1x&AFwg*efMQ*2@=9aV4oJmDy7?a-Y0A zPAexH95^WaOYkGH>!{!4Xin<-YU%CHBC}NduxwJ>v6lcx7d7gppQ~hnXMf;SXa1xr zlP_xb?Cx(*2wFDYHHbg zGJ!q!=ca-Y^8q62+9UXB6B8Pnv`=wU3>mL9vd&yrsB#^jsn70?%l`yRH4drnY73{W zydJO5+D%@&1^aF0nN$efuD<`>#!X^y4^H>0%&Ih(IpA_ESZ5ljNp&279lMXkvkyuR z&8TX}PMCM&bU${@NNi6i1#h<)qa?S--BSZD*io37>p7i@h#S)@(>Mrn0Kr{?joKKoT6 zn}V3NN|sW<`;fPA`*1VrRxqK2@U)$udU1@e{L#lU`B>v8VoI~Vom~TodgV1izHdM) zy1`w%N@$5}KGp?fj;fl{PikMG?GN;BnAXGv@%Tf4@*91_svqcG`g21>?>}DClrA4u zh##OWNkVIBm`7r(#u7Cy`>KDGVbH&?J9*AETvpr8`=P@((A*WZsf+gFkgW6gf*Dhm zbMEgnxBJKUbKonfU<<}#eak##<%lCmsQb=^{p{A&+{yBG?Tt&EKe!xQMlDl~ zWOesR-ru<^v3?|AQT>NjMv@zCxh)rOMSSucB`f=^G}R^9E?YZ5&MO6p+4hIv{M&o0I;^s-aCHBI*i%qL}? zF+4lErLRchDRTw&uLx>gZ3o@Bc_@1R%^t08gSNoKzK3^~oz03WE*;=n?wQl2njBNf zP_RS17+n=7;o_hcYrHeoyR5gxf{GAQpi)c50hb1?$u-mtF_|AGABZ;Q6e6OGA1~x0 z)O<`h_Blz}oLf-ig4wQ+y3lhwG|7P!#~ll&U{_-HUoO+*%rB+aM=C%?K;a|2Q>V!u z;5uwQF7cD6&g#5Fx}%G){4Vh(Av2ZO_)td+>@M`R08@jC|7B_>v~7xA1|n0$=Okk% zgreuu_XvHvN;J8Q38sfkV|`POEs<;KeVlD{>6sA=fHn8Y_r0QYD4X3CrB#{)(aLOSmdDI3rB9q~|jJTp>Yd@bUpJtj% zdtl@E@gFInK4SPTs7&%ibg8qJuBQE7vvkW218~X#G)IccMs@qtlTa4q7le1EO9GU1 z7MV$7_rEUftv`w6&;7LR?TEqC2V`q!tRcVu^by!|0$X*O$3-M-XaEStTw49_{fwLz zXJ&7X_5E3OWAfT6Cvs=$fK*13S@x6=QPcQ$MpzpEU~`M zeqO}T_CrAFW)&)YXDUW^v8JJMcRd)*ka_>o*WOR{ZY4X5Y9UJ%i(lcWP*K>fsRGk{ zGZn?!wPn2?Vz~^y=C6zNAr)|;w_AcbHec);7!ij4`}~czQD6qi_y5zmupI*uNk4Th zGsDSs%h5RP?blAWEoNsfa!OG$2nrd$!o8+#^wNqeUPSKPD!>~MQw|-$q#z_+Gg%@< z8sn4Z&2SV44A1v3^Pslxl`6bGaaz4>f5blhHXX$ppD8;&X#402D9gyBU|)TGD1Wvb zn9Z&#()b%woSnojlzgEzbw+OIeCS4!d1?(SQcR?#xR)J>9x!?J`u(Y_cMR1ztmU#0 zZY0ftS1@wAo-Gwsge)>>UPd2AD&Ma{YnxVYiKR1X0Gwk#b27v%N`0e^zWEiS z#g_hs>ZlNVC)VT4IuzyY$zW##1$B=*tbe<()d61?JYj-c8)wP))m?f-F8ij|%vNSi z^*J-Q5E}W)eRJaI@np*?$-y5}?Xu=Ywx|He;HI_BH|H8N!=HQEwuNfbd0 zTmq*o9MIYc#yTIzBf1cWNs8B+;inn3g&ocEVuRFk zBL+-DNa$bkHI4bnMXQe{UVRQRm$lB7)}fxl9l9D3Y5A17%jFx(VAJK3D-q0K391vP zo`zk){v0XNvj1UFzQCMMJZ3`_X$+U{H5kKrfMSx!8tAk8Hnmr|~wPigDOmsUbU5ZrmiR7oE< zgSKfWO4HGlDT}Ajse*oR2_#qS7Oj67duEOC-1Xd53-o|N%mSkfk)}^+s_M#c5cgmH zQjXynP72jt>u&O{vUuedn=?T`7?_q-FUqXWyS2@!CF6(9_iw{NAsr_csm*R^(~#6= zf4-a>mSc}6=E!(>u|8Fvqo_w1ci7(dn$|B{2k}n2`F|5A3jR5hkrZeU%y}lgxOz1%=Ii1lBYX)hU9KoJF&Vz zE$d;~NkEHMKCt2L#Rp3;SJa$Y4t#MYu|uI$Q;zZ{a=Fl*?=Au>K8)e#gp`mms5WWGuBldP$SmghuLgTLz+u84#Df@}@SEs*oaFXV@b zzdv(2g%{KONd-h>%H8We&YriVD${%0pBU9Gd4fhai`5?)I>TGvzNXXzu}@>V9;&(x_z7@T4{W;6(6*Nh#d7pgt&P)en1rZMiev`V zDF4FQXL3xK1>HdzGUg<=%Ajvwr^3zCD#i}epagD(_ZOXxoG_OtTQn^96VX#LP!a zsNmA3R?&rTDQZWvLNZL~eU_&+tSfJWy7nSyNs;Fzz_dL3^ORm_x=;{(CDd zLVj+9-ES{jZy7C1A~t1Ce9OgqvwzmPq22N*D(GIB)oPW>Hv{*!{1{)$*8whWunle` zpyQil$j|Jl?4~&5kkVeZ91`zM)RP_1*slIfPnJ@J=<%jF_PpfqF=X68Qq!&3 z{18FHS_p<3O64>g&0Ald8!7dbNI4^6VfZV->eZ<3Gk%6d`=c7NDX+R;`0Cr`5Cg2xDYM;|WwS;S5mG6hdakP3^eYUP z;ojO$bHOknDouR-LD)j4Bco2;F_Of7@20R1^Zly5F93KK)95i4qr}>IWK2aG9!YTX zt@80I@nBskF>x;UXlU+=tTi=&u|csL&>n_6{>w~52TFn)pKvh)px?t{SY4p(&YdBMAig#`TzX@oB9(7`2RjAP(0oCpOS)W5Fo;|xn?=7g-2lc9eFROiKY95R@0Oa~;ts>tN|e@Lb3Ai2 z%$>SA3Gm9nL0;%m64J0y?5ql zxT^a|jeg01b?22 z+0LZE$BQ_mk1QQp7)eG%Tplk=0G~_YhpDE{RDXc;*KQrUy;{Dpm)8%7GHiYM?0{40 zfvcYDCqp-TEW_ow>wYwuQ??pxM$~)DgHl@?30%Ac?rJD+OHy03#0(UWik*>n%MsPS z)l@15SuHc5Kq)Nky_7{tDe=)Q>SZsv92p$R8d}H(e@M)6POTMEy0kY9Pg@U48Mx-W zy}Vg7-Nfn&9dS`D!Lz0il!v?beRnD)POKryhqsL}m6g2#`UR`8#?5VLH^r=BW9Qne zZ})})ik|zd_qp;jNo`iDZo~d95o#m;`HQNeffO*nWQ=hmQQqJ?hPxn3dLi03DK&NEPc#tX zC>ojC;Gv_H?$^N?>D8qXh8*cFKWkO0HaoyI0I^Dcmn0x8KHj@-STYaml1!LL7$}Zt0F%x7HCeJF zsMTrBGF*eBHMJ-_7BGp#FF=BA-s3)vl=fz!Wr*{y6nGu=%XSvOBLcn8r8H9W8LrMT zwSOKPq4V!63yEl4+<6oj;>wU9CnqGJ^LqwK6II5|Hwspp8Xls1r8mwKB>~w=xvFIb z6)oVRs$9t%369R`4Tx{L2vLpjsia9FQ2vOxcyK`3@IKqrJK%T~zkN~d+8}5PY1uv+ZcQZ^( zOg0f4*esG>pXItCbEcZZX>Dya^b-njb)MAz2?Wm~cB>;V(yxeJ;UoPisl?|C8R6_) zU@p7iTRybSc)DLKod`HaIyE(lm&D->rgGA1eldh^s zV8vZoB%jh=cbQ60`rHHBaD~VCAt0LL1>ZkGJ0s-d_x?ZZ<*r*FaH*EWeBmT`lci#43zh>D~y}#&=TgD0f+I%h$zLZKOP0Id;s?#RF}tTGZ{a~K%=+) zoqrfNtAQ2A8W%FW&(W;wL$=GKy)*6lftkkH4%$U&#lDD))Yh_cW6B#Tb1zYWk;zAn zh=~RM+bc^@GGcaHI$u1D>sPQUbMf}{WQruUO%+?xi(M?fEuw*thT)vDoC)2 z9^S4^wRAojaT$pG6oMv~^={U;+8KicqbnvQC)b=;emeKqf(JJB2v~7(pd_CLivOkk zK(FPO9tM^feE3?l%2olPOQJQc58>jecBb`r0hKKZRy_c->%ZSSbNDF3?rVe$ez^CL zQj~s~Ab*)Kg;*1rG zN^Mgoms@u|~)&&8%KF31I~0S1kt$;Qs(OVAY{PMEH&1r?2MsRQ*LU#~?-eES22KhiDNo zJP8hvA<2olXlG|<1Q5eJVjXarxxIrgpM;d$`}XErLH;$Kp|o-B*2vo8$v7un_pNI@ zAcHRRUmnZacUa!vw_x=vf`cVeZ@`~k^_u;jRX^;a8rNePC#~)e%?R`TeV$}%!Yjn4 z^Z+pC)o5Uv-3*u!;DAIWigF38Dj;`e-Te+MUS++%7m&UK6l?y|Wk{w^QO*xA?_@Sa z%byKMIOE8x%#VnDrtqhK+ugo>n`TM> zv$Xe7^s(;?zLKVx>wHu7#EH1*Ezg40yU_d@SBhmQ=0xkWAYj-L3jNuTlzF=l)N^ow z&XK_KdeHZVhRLd*p}hUi{oMs*2?wfh3w@xdR9lHsagvO>S>`fuubLVXM7)SW^2_4w z%ZGw9l~HHFT>-OJSmo2FqnDSL^P__lDbr2`*!Qle5(rK==2~TLAR;y6c&vemeOqNA%%5Zq$gn`G0P$V9|@Hi8+K5brJWswZ*6x4=qR#@4Pxm z_BAGIDpJNV$odixml@{wurb6E=K==<1A}1wza;hoo(~UQYL}3bk_xkLF-V=3K4Sh~ zRCuPTu`#v6=Se+?^ulC56iB0%Bv#+*MYOfH9xGn$_q#)kJxO*+36)kqOZk*V@&hp# z*2Y;&mNEd@@wmboX0zin|KmVymd2YNo8|l9h}GsBLq)w#;#@ot0-R+s#MYLhNZo+* zQH$@kT+YePK41W9Gab|#Jj>1$&OTq@lZKFsepfS_o#f_JYRR#1*2?*IFDL-mOui>S z@!;KpAJVEQtYJ{k`HvQqm?si}mvL2Es6CxdAu91E#-1rzR-qC+MhAZ2xmx$&J9ML^DQu z)hJ!j@-KIb@&W0gR+E_X#?4GHfP=;koOQE=I>7mveur~WUDgkFrg`JeFdPluMRQUU!(cZ(j* z>TwcJ2G?ywK7Z|itB}Wd-T?**kgX6%;j$$?0eFTqBW0-$aFO{QREL5^@x-k*dltYW zdE|g6HgQiZgGNKf#;zr|@=a%Of_=BB3^0wGAQjf4JVykc4w!R&KsDHbM zTsxT2%WgDo7PBho1z9jiPK3&a7BsSsxPU-osv%?L|Baz0EF@aud7)PdzWL<_v_Z5% z%;~ihD~2_IJJ_=VLyo&;ynz;y^ zn1**X^-;-Ppa{|P;L@{Z=)Ew(*g!XMo6`UYDaFX0169WxPnGuC-JTHw9UJ}ytB)<| z0$|}=mUQ$!5k>1---W@5?}7(_WCf2&sNrM%s;R(K8~;+Z_aI66kG#NjGZ9f{yG6}f zVim+v5);q;MUf*OyY4J*x;xz=)eM6lTv=JsXliOQTJSz6TZpaSNh)p)g(v_F+y6y2 z{~!+jPh!C|1O16PvKGhpp;|!<0WOx<06NeTEq{F%&TPTVIrw76H~)*k<)J9Fk3#^m z`G(_^ESPACf2%l%SS}#%;)znxTwnLU>Dt!#&w}hBHTDj^U=@${HQ6odyE$Ld(V*r! zxB{+@^B7S6Z9jQf<1EDj5aGEcT}R2fVu`nmKQ$Dn>sM z@6=m5U*|g>0W8Kf(dC!j7 zm*9b(m%Gt53ipzYj*5v{{XcYl2{e@JAMiU!S5g1!s-$QcG^tDp$yT@9GGnWeZERDC z7+WOSr`vXK_e#wSCTklKgVG@Ds9PE-l?<|0gEXNm+4+92Wybx^_r2$IPRG3Ov;6k; zdH5%HnBC(j#W^3?hqSXD=wps_9S)gouC*#+GRLc_;O`TikVU{K-JEqaKo%cxdHJR# za~56Krv+utyaxUXisFmOlNV=yQ0DG-!%m5JU8pKa!-gAbY5D)PIrPlO z^%!m_l|$B3)!k_4^&P-?W8sP!0QlRrJk9GK>5ccW9hRbb*7;Q#*d>^CTxHYTsO zrG}c(52Q)>v;c9fBUqi;m9XMjC5DUNRmAM&0H%1@aK2Zb2E*Aa5HEcY{6o6I{{8zG znfd~o{69T%>}6F2j&LD2v>~{scm-Uh)p;VBsiQW$Zy$2Wk>Kl`oAC?WKHyCS!|eJA zFAye|eh$@wH{CcwJAIZu?#|vfBTdk`4(P0kaP?d>58>JN&E%Bat8pI>rk9?~YcOJ-ID=-g_(MZOz)YH8(S~}_ zi)YN|oe`)yXgY4!0zlrlkSnRPZQG9_vaqL?9B#n7w;s{oy@B8uCCCSGdMoO9jkQcU zIuH8|cX4l?L~W@b-EsPGiNNX(6hVk|6&gTUsi2^{!>)Ut@{NCUnaE3RqBycO633GsI~7<;e$2h+|;`oJHwB`DpRLH7+7tL_$96!DYw z{D4w&ItE&(QXqa|g&jVRHK$9`lvb)I7p2y?7enl!ByfCWz=`QR3O-n+`v2GcGsF}% zbQ$XKUIRz2hyWYzjypU2x*(39yErW|&ZD4*`p4(J5bNKZ z6KOWnyPYffrhU3I1E0%ap=Cx$`;+TH>bgMMPN>RswRQ>}*h1Go4H?^#xo)?>7InMP z@o49&R|7Ub&GFP|U76sDB*q9d+}Y+#Nt&*9taID*2*pSg?V}(~jZ`{c^rCf6?Txd3 zHR=d`$4-0J=&7svkITAjt>> zj2>zRL<`P6ll;Q_aRS^d89Q(AX{Y_yDPWa7ob_s2~^eX_vV35O4 zO~Y{Ya|3W~jUd^~V4Yq5LcS@nw-2C|8yD<>YA08=b?`q+XDGK=k6=v?29NRVo8=mgMb%%ve1U%3CoO%9 zZ|rH%wbqYFeQ!%*v!Yl_@7nf$bAcDiccm=`JlnxUEUDIoV2%x{y z($b(3!V}13zB6338TK!zT}8US8jr!)F%v}GJmoofXfHPQ9RhQ|+!DhIjts0kzPuR_ zkQE26LN&H>s*vLdlL>lW^wJjo%ygd5Uchac=+=#Jo>m)DRdj{%3h1QQ@KAwGUA6FZpCVxR*+ zD0e3)n<&5nleEbA;hpu!xGUf+Jc=~2g;!mJz!`jGsA22`9*Tp|iW$in_4%frr^3l7 zQtWo5KM8IV4t@&;>%=2w?`6VrIPQ%=zZ^v}^WcyVFiDOEy1Ic+v*$lJtvCH4(hoNo z$0+SGFbK89wXwI2%|{w~pOA&MV5OBkReA#6%kwQS;p1_ZQt!JRPC#iLYr^MrqR>0O3A88c9X?GzwGW~EtRW8) ze9K@>7W04fp>gKvr=fnzebkPFuu*BkT|lYd*Xta96{S;=U3;Z0C(7W8VmzR42gi{1tfp8+@!O1CW&zqt4< zeEYAdP7^fk)?*xCP7=qhjTX{=$5lVBQ9RR63WI}3xh};a?TF;?+ie23qA1nAp3!yj$qj3>1 z<*Z+oFK%8yo1G8&_P8Ul^i9_pjm^nWt4J&R-T~SnviiiUeO>2s=iz4tYFrEiR?a84#5vzu^(LVE;Py}>$E@pukG_}&aQa3;+*v^2 zq9tUQB{!Sqf%8FNt}4cuEq(p`$}5oHB>4KX{rBQdghB*6nJL_r@H!GSbOiWXd@#>U zYn{T!Jr}pHnKE0xWj|e12yti!2(WJ@DA`ii7;kr^u2D~ZME@2gctAMQuX66=*-CTY zcR&@dKy)i7EmqDJC7kkCOiL#?^j{4nt-5Y)h}qJ;JwnkS-{XRM56332yq<3ZdZnnZ zYU(Q-KU)JHnJQ2XOYnfhhd4P7LKoizFMp9twI%9?f^eyaX*vc+=Tk+n%Y`%PyTTyl z5vI>u=g!^BC-|OV54;2`>Nct31Q6};&sey&sp@=WG5Bv(hJBo%Z*Zt@Kd7%~9tzTM ze@-<+-_R~sLApH;qI%|NI?D~xS)~t?j1GxN^}7Y?%cu-H)13}UDlug=QVkS>)Bat)89ZqP3Z$3%|*l4$U>L^VlbiV+#(JYG*UmxF=}dR zqd#d-Jw1co?xP+a9+Ox|CppNS-3|8&=MW6_!1=hCq%@=`O|8u=*5&C!1Zvy1ZOxsh zuWn2pBj?IWeb~OTJ?1a6P!|HCh61>fq_9}oXt!w9*)@QfY9bC*c&mI$Lo|6`$->JZ zi%V3>&B=LQP0n-%J-(k+uJ7^@ZVDfUu)ez!!u@CQ5N$qk^|DJ-fq#I? z@IiVc%1CU`^7qNYbjYeK>Sr5)x@vLHYv>WCF24U!IFsDu;uV#ZjUcUmYvsgw&&Rp^ zkrbfqAE07eAi8>KGB===n&oM3W>!+%WY=SKnQr79~)XcQT~cBk^lDO*fC&wC)vq?ivz;i@CQv^MjU2u zFV^D9?v}Ga<3R_ELe`2mvaKZ|gUc(ND(K}4c%#`xLjuZ;Mcuz?W7;g*Y5lgW# zI0?T@?C>Dq_Q0#Q`A}Rw4GG#UJWx<9+L8Lsv^RXkW}ENo@R6o(rG}!m$579jfW{wb z$;{HL?AW83U1XxJ7Vm|9C?ej(SyT{$k)dK|B|LXDH zSg=$Wq>6Qfu$#YBrlo?KF^WqM5D8q`Ic-D32|B4_^izN!JIL8t@Pol%OsYJiu;4=? zeVh?uCC9P|2Whk@@$Z0d0<;+v9@@DYu*)5&;}m+zD%l5SbR!@>jq$0l!0PkKOPncJciAC(~sy%TctL6 zWr5ilL^t*2#-Hs6yRzlgPd%+!#R@qcpzCb@$Y1DPcz=ebS9h*{m@##%3S0&-x7cR9IOm4aBUcb<>_Vr(^Kbt1c>Pz+!hZ3eT|;eyXLFe zrb|~A@KIClOl~tAM>Yj9a1NgwSL$P3mI`uW1^m-XUg-t+&QGcCgw{0G z8d|+2b(}qA+$r|J$v`uopTF4%=B*l7l2Lygg*OfYx!Xd+AC?Dk@l3BBtKd9uM>$qg zAASFwQ}0kys5tC^h*nsGAhtoCl;g_&XJ??LHK0gvx!an^XWI=HlPdfejd!Ih@0Ww( zHR>AB1D9`lQf?7S9=%v^%Rkor@<9)Ues zjz(+_K0G9M>AfWYIS@RmrY@3CI0VQn=tXuiEXyj0M{9hmQPdHCsJU(113Qoyze5JU z>{@{quUAe{d+gu>r9WZG?bsx136~lp6BCnfZ~oTt5OLpdiY-dNT5qA>Su@5Gm*S~sRw*dha*A8-iKwOh zB4~|rY+&S4*#`|^o%|r|B4rx@7{ZCZDNFHsL|hgbVqu?iI~sP(E^v2CaOH8OoNOT6 z8_upkV%{GhI$g6p+h7a%(U&~N*dZB~{03O*nub0(k(GS2rwIrvoZ)<9scbr_0_8$y zmAdB4l98`=Fv~$w4bApJ$gyLea1Fu;a2}`8bu$aaajiRB9k$e@rK#DsOW{itkH?!N z_IjyM&Oq$8w9e66OuwPjq|l#%XCdZk4!0^jz0d5m&YeiET@F$&8f(1?lk=$_{5pfv z!A0T{oPIW%(r|#Qy#H8tre=}ex9 zGwVN&S`mjC>6r!YjHQ*AU}0HUxZR2+Z=M9&tzvKJ1`%(3(JSoAZP-efFqOH~r~=%q zeEnSg5#sP?Y^^$`xVGzz)8wI+OCrpSyhaz2i`}BK(lLJfC zu|4!PQM8cXB}U!?n+6!;l;KX2Zr1z}IB#oKOu2$g>`rDbW76*OM-Iz>u?C6beT0^% zcM_=m!D4~3$l0ZA9S@@?IMc>_+}+(HWc$-m8-va4-KTOh53D|K!)vu-c@&REFQ3xK zqEXAhnZ8MKJcB>ty=iyVnz*}-E!JXr5`R)VE{pgu4t?*DRDU7Jx`E?_ObdyiuiGI1 zVO~<2CC1$2HwPb*qz}?f%+1Zi_ehU4MOi!QCI}><3mT3$Ij;OdW-5vXR8)B%!j)s# z(wkDydR;n|&O8_RsrZ3|R{w`A4Jdu+pD4O5B4-x|T$_}9Yq`;4tI6Zd5Hf8sLvQ)A zC^R=UH5mNQo0j0$=+QkwZZYmU7RGApuGj+ zz3JQWN#Hw0s@^y(5d_6IztgWL4Z6pji7UiZjhEM>l=g`x#hPWe^kOCn}xL<8|q8D_ww zDHomq1H2{XT;Mm&r2z9?2^+eFJt7YiuMXi2<0CNdKEQY%3+h9QM;oQZ=1$J7>w;fk zS|+I^S)$Lju&HDx&lGOBS(A%eMj=jW(?L~eF0_DMj;h(-2lLq5n5$olOAgs1{UvQ4 zR1j7bq186U>LT8F-=D*oF~NxC-e%0>UC_ENoR6 zd9(sLoubcSP9;jcu}J0zeb1eEA~Tf%0N@!|5_ONv8&a?`IezVGUoPfSbMS}U39)Q3 zu%sH8!0)w0?#+$KA&&FVW}q3MS7tVyY4D7}Mp{dRJ&(jv4rSQod2L6JRZ0Bd&7-R< z9_=)m1B7q+pRbNS>q+_O$|S)Nv6#`siQ5a_Slofs`uvH0r}JHRUDW~A=n`QlOjg42eN(?glGBunDsz4N3^*qpF^fWd?q$dP{uo`3_e zX0{sC7V~-VosYLHvfXD@#3fJKt#XJlWKH~Am4KajrT>M7F*5jdtIWzBR1+ljeW*_Jn}W& z8<%W5<&5$9{6))HUR z4yfS%690U&p{x78L}MY8nR!~B+XLoxB9M?CxUSBp(E8V@ho-bK87hxQvmv_BoF;Mp z{@ar_nEwTZXAazhW4C73QVfTY`(&lS6-USC?Uud)U6yUpKYjhL7)5)P_`p8mUl_U0vnOy-dWx zHfM0ze_@YWT?)wf|2?*q_<|GveLt>EaTC`Z1X0V8b4L$Ti;d@f>FR{Di)KCyy3Gdw zhrN3b#-7Jt6?kyiicicF{d)Z4lZ!0}Nl`TAoDt={&tO@8Rw5SG;b%Go`_cW0+(-u; z=Bcx@GZ1h8AzZSX7@DAQOy>>#sCs&06W$p*f)>}1*&-*S=RH}2gvs5QoN@TvI<((0 zU94~%M;^lcX^*L>rlg-cN}@L1`wyYHmahr0X8?MysnaW8aPhe0Bl0)%j9kR*aRas3 zE$CS|%;VoBkX#)xP(HY-APq+~Lm)FX@5>hP-;aRj_h^A^Az65F6O!Fjl`|_jZ^nRo z8$h5dgU?F^Yi1k*T5|^^>+rxB|I=YD1}PTX--+tUA-~PLWC@|uO~{+SEzpja>8IbG zJrIu#*jR?&lpLQW@t=>Vc?|)oaBiRMtp>31<+mem;6T_}6Gt9ug*M?Yl+c~`OCv#L zB3Y4hn?J(C=7IJ87>X8yPJ3C36f<5O4B~`jre+j{^`2~m&UT2HCLU4?Ox5B|_}{&t zXrKVj+X^ZZNs!9>sByZ5cY=t=PH6Jre!G5g(9$K&5>Z%?%=EKFZpMgbYiLe#J})bq ze*^dv3P2N~2Ot*$O;Zo$47kR6#<=iwsq-1k-g+3_DOCz7hT_9?5k;g5*K@2D#`#dwHt-bTjMy(Kc8KKk_;}Ydji<^7vBM=xWpA3> z2Q>NTy+n3r%`xYvBB>BCyeD-&g+HPskFey#kkCZq?Z&-wXFP8{8choDuADxB5163GK$9KQ)Zy(?l~%4^hXHm5&|q9l&y zfm~`_2fqW8PcIv(EKbiV8G*8W#VCqWVGcxZj1&--sc2ADa6QzMkBCV&;o`y-C5AjLB7X7MFh~n8CI*fwpSOEv40ZfwVEf#--SyDrvqB#V04A))+#@%S(dD5OZ zd~<1k9H}A=@G+Sth4`WMpwmyNCxlW1==PaXp@K_Lt=AHRp*@DP_osiQm7IY9L=&_} zL>}CpCOv)=s?Db2H!?_jtcbuynmM(O4O^3ou)O_RtmBj0w6spxxg#TcMC;%d;lVSE z!e*I}YABf{a>lrb^9z!hF{Wl_&1>f&5iQI2lo4x%a^UOextr|>o4oq?wPmgms^*9#O`k> znzZW=MlRoXQQNbyjOYGG#KO{Q-t zSr{yTgZi0Nk~T=g&l56adXsMDcihZK5};l zdjA;uaDOPuNX(xLvDa^uK?S^5+r_&&yI{DHM0b@u<8SC2u-6>1TC{ewf@Qk-#zq& zW=7-vdJ*NH%WKG`+jx@n%^OeK8?pq8V^fC5%P8OX=pwBtB9w4f6?}X6(lAlr${73h z?%lhqP!oa;{GC0pgi!MPt?&R(dVp%69j+}hzJETVV+VK9OfQipqaZ77tvXz+7-Q%qoGXzKKwQAT$NIX6N7#>cJk^ zMo>x+SY`HK96lxnsNe0Do)$x1Z4J4~;LNwW4Ga&9dF^n~)9ehD*}0xQhlqiv*#nKh zJ*xHh7gR!V^qgHNROfbea`;df4Jmy%<7S>ErEl)f+4=eT(48O$hBhPKf{44%ZHJrl zX2-X1ug!FD1vu4JMNH6Fj$-3srxHT;aX59jpgsB{`j#Q!2<4TKecG1pU> zB|H#rZt`%WOt#gWG363Mf7Yw5(e)LmGxW8g{I2n^JY}i)g;uGq=HlSd-rn8~fC(jd zPsnIAlc&6!^UCs*4L{)!2KD6l!lI(Fd~9ACXMtn}+HPCr-32At>2V zLqq6x3gZ~XyCMyTa3gHULer4Cc<{O-&_LjC zRlOr}yzz_1UvHK14DpYw;J^R=J2*B4K0HqUW+}yzd?pg!w8#B{cS_Y%9>=Zc7Ks-_ z@d^P1rV+X`pt)ntNGD7iayzgv&=iV1mW!#qGIkb49jcuK36K4Ae)^+IdE5ZU+e)Cl z5)%+D?a`x0r+dGC{d$LpYhenu5xi0ta7)$2-u?Hu7E!$&iV5(kR>l;Qv#3C)l`Q` z`w-Rp;<8c57b;yrGL*&CV?t8xCBB7QWZy^o@wc6ES8d z@74-K-EeILLWwRf;a3a`&UTyKj{p^Jk!$qMc){q{#Uv@pS9#I;Xr}I5?joUscZ$vREKOnhHUq~lBjVJICGb!S zI18Be-C&iN!igy@u>uq8F^Nh8>skLC@=36v#-9R)c^E`M?CAQN35VxV`b4S^VQoRl z7fo#NM;?J9#bWB>#Y4Nm(5Y&H?zlEv4nN2K$3mKTOH6dlKfz>zdcLG6#mphCG09dx z@wX|JAPmQW5csfa2EB3r5@aHMt|Vqj1mr(mDUxJuj5U*b0pI?laI-6FsE%?VSTjP&~MA38Fn`F)IdpYR6Phz0yD| z#L}y^6oA_86UVm83jFfM}yC@EteoJV$UNxZS$5b3HnNr z;HJZ*cJL!poi?*k+N-1GSJ&hjblznoZc~b!w5D6Z@94eKFev)8e^}FVw%h^SiQ!D> zq4mAL9;sX?Uenfuu#wIHGS~%$ap1xuGU>FzcOkk-Ew`KV-uc4Eh*_Zs3dT7DEL83T4}^yhxSDF{_d9%Hl8F`X8r< zX_)wWgY^%Y@>w9H5IP5fBEfdOCP?(0*(>ld0F z5vw-s-%DGbe*%KImBY}; zBo8hg=klU81?@X00ws5|@Z!-?y%V5q+80W2JVZ4hEMe(}gBx29Zt!_*dim%inw+{I zBZi#nF*i52^0WFN1_q&2WC;rgnHwm29DkfCocOVPLJ#ihd}u!#`bRR@2Eji)3VSQm zOa1kgCeP9nu5e9kw zOlBr2ExLT3Gs0S{KF2oqc``CS@zkBIk;Z90AjiTb5qi@>Oc@1&>v5hstdYryCV3!7 z{}3;z(HD$BV`JP3KDo+L(xu`fwlp7n_!y;~KCNG6#shY2f9fn2Y&v$OEl&426DR3? z;7>|uetrjV-|>n%|9lz87rg^QLlNEKDGm!Gzn_<9r@dA(T7fa`T(mr|2sxFD-_ySNEN( z;o$q4h_Q(GWJT{qb3DIRBj9N!wu;S}@S)qOOK5(CGyc>L+q-XQR@hIs1yMg8-KoCp-K56RgoS=eYIwfvx7fnx@r7A^4T{dTpiFLZdkZ zTXz>^g9qK|pl`t{{6MBj?-ejlCK{p4W4Kdc*gz3^azTVXu4;A9U=N8Jx{rR~KE|Ra zhY)tOA=lH$zmCemI#2AX<&JxzQEsv==Zd=pVvthkw6tXuj`ZGO5Qo> zXs-k(dPSZ`eeV^l`60=a4PywqG1Y1Ok!GyM>WKZ}h4mBPOskpX^Z*IgTJgYpKv5h6@j;}kXi+vfkcq=B4zn zh^6sFtWCr5bGz2ii!%8m{s6uD7(pN`+4zda3$3;}B!o|nj*cz~g1rUNOIG16_jC9Q zVLU41pSGdfAl8?;oS5qA1=gx|z2(`k5QIttsH19U72eB8C4(Snf9?Oea)c02?fnV7(Q~G$=dV;Iq6%a@^Q!j2(w0(d_P`*cks!xf0GZ!O z>dw_A>j+Vw|9wxE0}5W9Gu40w?69S6W$HljBpRJ?`BhZ2eNu?Mlz6cn}twg5y}N zqx>Oe_XzO+E`xJ67KUqE=gz$}vKySWk3K_HB#&Rz{zCw(z7CEK-bW8hQ-Jqu3BBP3 znWIJ^)E{h{;s9H*$;$_9O~Ua8%Gk#VtFn(yfGg7v1ZarUjtA+t4nv0b5hg?Y};1A9b z`uT0kLFxz6d!i+@EA2prmJEFV_;h|UYZox6SRVT<{5BJ85i;V_r9J+4d5S(i9i#Zk3H+xD3Vw+8YjrxGOniX3Kyb&-tdsV+Rygo_ zb!2(C@@J#EjahZMo(+zlOK(ud=PwI;F1EhP-5_M;L?RBq#5&!U&M1V?_|;5d*nunc zZ$Q=5bXDRBA}`oyHCSGy8E$Yo_`CZUVYhNzO5i67zE0zr$teloWD(o7s)Cjo1rsd~ z$lf1}v%S?j7H^-3GvHV?U|D!EHAUFi+mGFc@@aabm7I+=*h1d`-*%)*pt!jVzv6Ar zpt1!u^fAVH`@}Mx+47JCK%B%LKy6=PS002StF}$VEzBk0ZEHk) z{@n?dmWo=V3t=Q(&=5{fKYs5>Ms{u$(~O}xAQ|FH{;>Rk({Sbn58ly8W+`*{Da`d! zCf6`)XXb1K{x*P;=eQq(o!N=aMoN*Tovmf(=ig=N@x$>)yX$BBDj-;82>tt)31%co z{sCN@95}9ZL*9m3rpG~KwyVw7*VXmUO4n0Ud$oapX*kFW(gK=YhA38&oi;E!J|9vV zIx4#RN#E=qqu3j`rzA|h@_1D9h3~dwXMN0M8fuKP)r$mM4`JU$gV*=X(uWCd| zv|f-`x0~6AcCtg^nFLEHtj6Y9Nb;X~s|{CV3jdkZUi=?~Wp_RT7R81~JIAcTpGqf$ z-4hnXVAFg(Htmy`W1wkALB@qKC3PD2$+7aObp2#ufsXM5+<)YF_KnT3g%*?E-8jmp zMG(Z+TNTl)_ACV~$Yt$`p0i7f01+N7I7Q_C+vOeGt{yR|oj0vHFl%RCiTe$ByoHc> zN6MtJkgo58U->QOm>)IvIhY>lBB0?1!SW6SZ52iPYm{warrSm>$Sky9@M!^smA_>+ zV2`hYwGg=2v{LT#CO(`swvT=k}P3CUvle0Ja{R7qKYLyx4^*R6~?$h zYY9~sEnW^J9W;!~R{Tf%a~r|%y+}yBC1q0Si))L6cNCLb_oEU@WI^JsA!2ujJ_eWD zZ970^w}823cehm)eJAO`{+ZGYr2QG_g#8Je(^cf7k!wy-xxD_hHL=}puL&J_&pMl1YEKudnwZds> zd636QWkQ6X8@oy^@GH3TS-+b6iJD`WwbSz<7M@j!1`UvkjKDV3&^Q*vz@xSP;diCD z9&;l?DIZWjW}MKSER~S}DQY0eDqPv!mYTViCB+>L2!IQHb7^);Gnzp&GMDC`^IR%2 zK*G;#HcQ>7Kfp_fFl35jDlzW;Sce& zISlu{{=xBbPsUir((E%tT$OUf{+>gB43PdC6rg;KKmCrdLd7kL#(_f$o@L*}AiX0|QE5{qsqlq|v!ZPIOCou(7J41uD@u{0~8ByjMn>Xus zqF!S%1^VfqTp0diX5q9nP++ASc*RVZjym4`a%Nz;}cIu%8=0OjQ2BB9UkiU9MvOWiD z07%(os8yaTN+4>Jt9NJ+eCbPlK7r*3_E=IB!U}#SQjs3_ay_e{Qjmep;S9ziQ95N0 zy9UhVF;?KL#aMUH6xWt~$`qS%QQZoxo4wL0?UEtYLv@Q7ZK5cwwi)PAGJ7Bl$n5Ov zk54Zzu0XN_MFKf{-Oui(LXKPPLICfN1_(f1l{Hxg94Zh`^FN1LgFF17=%qeS_9ruY zuAjpRKwOKXLHs5V2caZf+hrzgnKd=E+h=FdZ!$E~RPij`S6A!yR$^T@V$`6@Ob5jD zU=)Q|uUdA&NKQn6Hr9}^YEBe{sI*>)V1$&)nruTwMbb8r-OaCouxNmaElI-61lPv7 z^Y&!YQj@+yxN_cBWFck5ff{YP&mUP2!-TqkM1Qs&-}WCY+vqG@xUh=w&{$^YQKK+4 z=(8R1aL+_$37r1g8flLDCusNRD5<4)GE|gaAkew z;pY&D5K0eM%fsaEAW5?5=M+!{682nVWV7-i?rlIQA;I(-r2@LKp^Bs?BtgW!F6}QU zMc28s*5~$B(9YH`xSK7CFO`U>eS4QFY*vv(oOX-iRFqfXBy}C^JqWlh zwv}*UfxfXCxHjWJWVb{1hWOA_X6HxOPV*y_OY;9+GZozG9e@}Izjp++@d(*Upy9c6 zd?c$@Pm`xha8<{C?t8*4J{HRkZ z#iap_8WFnhNM%6xY3dP5Vso;}bSx)d1`;I->BzF2z36&q^$^&2Fj{W97)2&Xs?|Ec zr?j~5o#x>mfbToBgMWVe@#-l0szPpdwvUrMr3>KaouoRW!t-U4e+5}6wFaVSJEnTk zTp;?(%sCYB%fV#f$T5r?QNdxxoa4n#z25Tf*;OP#~ka zcY;8Jcs+4TO*msRkJYL^nbMhtf(4(GiybB@P`cie^X_q1m-w&x^2;BdJEPr9Vv(VpgFY1(vI&{aqsxyw^-SJf#2 zu*Qzj6M!@kyc@@=cJhgHqoYK78DtwF#5Fy7k~slkSWt^27x;VvOWL097lm#j0k-Kp z^wDg}FK|VE5rXlFwC;p1E)qudYY8Fu1(Mfz!!@zqxYu0;KCsb1IrfQjd@iV9_IAAR z>KZ)|6v7``s3E4~sjsAL1s_5%xCbZM*jqaY`I6G*Mr)v-h`Y-d)St=Y{&&U>l9iR3 z$b|v47RyIJ>yfeUT?vy$NfnwMR5$dP_x>_9m8&c8{wWZ_O^4^XG@aGgMlSHC61?TN z++xpf;{FRB>J}iapQ^*#raxFn7}?HsUafbgvhP_XR3AZ%+#f_4;uE6rC;|C}G59~3 z*7o|xejV&7cPefb3f)u)w_xp;gPpMgcNNN~5)k!-fi4kV&wfk9rKX`PDE@mv<-?j9%R;Qpe5D=0qa9MaZImM$a$Kvl z6za)22F%idBl46_-KrSPiN{X>##6;@R|glSrVQsV$lhvJ{OO9ilB(p` zWz8!698ei>e{dQG$cDMo5=05=eIhzjf_rsoBmsens#vvqNG&>7E)*}c7=z0WTX*{i zHz=1_COehMbfPRE}7j+yjtwa7_$!F_(6U!pxvD zZt%BfTHI_$4XfDp21|-2CrxXGW9MnO(NAe^ zVDk&1`}rC~V)WF@T;4-8(4=>ikUPEo-hfxxi`?0RhtJt+DMiX5Wh+j=5(a-8Jtj|S zV*WV$?bqGf31Ow-qofKu`erz33gP|=v5JiIkOJ8)&$7%!E=@C`ilCpYq%-ow5q6;rE(`oW-PmgUR})1dTrE5SEnX||VF(~eie+p`Iq z-=jw}o=-+UksgALDW^hxM_~nb^LAjk!y!F>Z3bWX9+8Xi{Ph>gpW+BM<~_ zRV`UL3k9O)6M1g78I-Nv<7SkM5)lSSAH+wqhHP|CA|u@sEN`RDl6^pSlRos0P3W#N zp!Lt6J96r{lqPSMN4+Ngj#_*V5q5Wih;`hKj|4pB@yX#wP?~cguxnbho{j{vq$!2k7Bo2BjZow5Hq0NfF|n082-QwKRLXnD|n5wGTuv} z7J2&oi+5AO05~TvLE&^4K`L_v!7B?j&~xrX3)(Nj(MkgkPz-ial;_RfAn(MMq>jkP~Ox9c`xjN4#!I#VDVc>vv#5dPL02%6Pnc}J?<%g z8yt1oDp~D?mY6u$+7942kLt&xXsjL=uBCKN?R zQ|l*zj_Wadcw5TjdwMJ>u_?FfGm{bvmfYuDpki-AFJ22)!LqO>B8s0XA^Eydvih!P zgC=C+TA<@<<-I2bXaqR_;v!|kv3l&}O(~O$wOb(y6MfFj=edGw{27SCly2djuYqAG zR~=L^9;{R%_Yq{)ptdlH5r0S8Ll#Bdvc8z5+#~s+)jPt0L~*3->uBXox2dSh0uC2Hg%JPVmS)Y-%D8Tv85zT%JX7y8R_RD9{P2> zeg*}~V<+grQ!=Rt;UDw=L?{Bq+u++`=h2nUSpS6Kw*H&EbU3QFV-xpCJ()T_j+T-!{vd#A8U#bk+uS#0Y8k2=e)iqKxW2NRsvtA8POn1hh`HnB3(1U-<6{ z!o)_N**=OYn)ak|e$*1cLrmf8z}}rAEZ#i62<4y|4KQ6aw&fNocg70Ae6jdApZ=@D z&Lx1(L5NvTG6jK9t_0!);gX>fq&Aw=d|S#Si!9V>@d=8n-P5=kDu~1Szq}2=2C`6$ zeNO^2^mM*;X-j~mdfxkJpkjzLx7-kH_2qv1vBC*KsGw5kZ{ofWb^*$ApwGkYRDv-W z0s}(#^g49FnNV?O8)Zo7)Pjpv@7YkYfoHA)g&CEq=w7rQLdc>W3wM`yT2aSghHxr~ z*^(P&5s;;+su-{?8bq}wZ8DPygjW3-1AMS;yH63Qpg@oy$_)M8ax9SLuoMB>WC7LF zSxnjupKb_We}-sUo^e_DREQjyl?t755Rq5{;|)ETwy7;({mULmepCfL)9umlb1{`& z8ZF>o;eP8or;JeKqDc0VvN@j>qhn$w-A|o5mHKPQ>v!)+lP|7s@HX%5LM0zJV%iAq z^o`8*Oxi5zWYUlu=@HwWMAtWyV<}H9?Iaww9)3-*ONHX1?{oC%$0i6vm@WX?esfko z%!!_L2&jzQ1z0p|qdc5_N-dR-u20oBX z2a)^j9w#wMkN_o7>NRymFr*p1)NES$G;UVDj-*s1WEOnA-|>>$@yK%&T+xezYQx>w zeM?wIQ%mc~!_sc}fnC#pT3^HmkI~A+#@sF;eXIp*Cl3OE76*~>b@G(2V?C(VsP?oS z>LPGZS9gS7Y6=z2lrYpC%P2N7>$O#&w0pp%T@PD&N{>S7!dbVy1x>6#>I+(x98e9O ze_|>l9qN_y@L}(r?H-Ogjc5DJ368%utYrl&`2t1o{hO|1y-5--pj+bLOTAzyTz9X` z0->%0)E6g6oC5on+8wXvThh|{|A7p=lY(}PUgO6_c~BIX1qU7*@Dk+DnchN83KO(e z&tIOB(gfRB4CEuc%hEt~F6`nZW9%s1VrrAiG?gMjv{bu6p~Fi;?!gDT0IokF=N+2O z>`fqCS)c7T#e6b5A-reDuuqYye3Zc9>kN*LTR&cU)({ebBbL?<13HAnbd&=#A|O3t z?t4&%MH~d3Te3pCW2&M80_gX#-lm^i6zeXa4v^|`dunL0EWVG(9^6%8<0b~})v1;kN5LnVG;6w4gYO@*w;L>XC_^Xa`&T?SenhsVM9F^nTB zl&y_=JfmFC(c9RCzLi(2#`~%40aL;mIG7dqB#ZfJ6@}%Px_R^FqOFVmj8%6{@aJ~{ z_S1>D@MwYTK8jx-MhT((wg{Sxyy5gAONsX9JVMxg&xU}Vaz5GsmxD&*3t%XQHL4jp z!}gcn2NTjDpa8?5{x8DM^Ict^L)qJF_lBQPCDCH)dte_;1FejuJOL#Yj^o+LuiG z9V#jQK#M3D&Y@1VKZA;UptlMN5_|sic0`xEe>hkMO7TJ){!d-s0TtD`wS7hm#LA7m z0Y(Q9K|n-`G!uh*s1ZexI*MSFrXV7H)GrzfqGv!*T8sh`X=3OI6E*4}B}S!4jSQlq zph%JaKkop;cfYm%v)0XB4yU|zzq>s9**R(vdkQ{4qjQ5Td>mUlhPC2>s;nkpGx67DnR#*mXcQ`YIo`f_D!u6fIO(2~H!@WYDExa9l+S_B*< zCD;~CaYE&JL=c4K1`qa@gvb2I*{Ml~Tt{+|>Djlp2`CQRWH;mY+_p3Q>q9<B>2?=}qY-}EhhU1BC42nK(BjPw(R+PI|EH(X(unL`7FRQg$Pw2Jog%9E^d5^LW(=dDP^5z&xsj)mgyvOTjYxqX2E0&BLHB+kG2O~8h~jQ^uQF@h z+e=TLJPCdOni1%dtg?29(bVkXbpnHVtg5ptXbc+$go;M~HzT?N6%E9McRSQ9VhW*5Za^hb>Zmo??9;PW29>$+P>r2AEsz)-KGEQPOQ}1nz zd?yiR>frJ5vSzuy+yr0iKr(58_1U?I<{m4-rt1)m<@FVOHaQ4V?*Qj?#;UYE0uI+; zUeP`r#0&&ZBmQ?Ed%iEGr~0r+7LsLzlhp5-CZvb~J@ms`GFoRuIzH15^`iG801SS; zgTY3bfivO`KU|y)-772Uz({d~cc?L!ez}50@duWMKQh{8(qF}{dro;myob;oIJ3gB zv2+AZ5mt>*m1A&7#Pv3fVri26)oR;>L(soYgABKeEMIOv_WU(Bv}HeYI)$~)!!oT$ z2J7o7f_C@4mnFpM`85VWw-BZG^(od~`#N*BXtpF5g>6oN?NVZE;-9mGU;f8B!FIPa zOLHd2<22m}dV^{x#!X&L#ls1`olPazj9f~HuTe@`w#X~3=Mr6fRA08qNN_@120eFQ z;uXePUUm{ow-5614@81g9xe1Ez=I>@>#OzeMtJXGjE%WHME%Y@9D+ibujXxRe$dT} zwKLK6)0b_7y7cH}PJ@%%Fe~wP%c?I4 z>s8RLjUflA7NXFN8nWR|zfCRsxsKg`9}^pl{=0Y&?ZDBK`ZDeVX19-ZE5cH%()|EO8S&V- z17@ajLPL&+9pLC8zP`R&lw7tbCGK)FSSjdWJ2Ap^ItOg>QfL>$1|V^hcD?Q2yCh!U z`ySC>D0N=Q@Y9nfCT6+pzP^rz-a6G3=I4I%vgi2Z!Ol&<>wb;(zCx--P|~>3*fUUwjVoe*)E1RpSE* z5VZC@i{zXkA!$ECt9DF%Y2j4$Sxd0DwJfTAqu`>kCi|S?#KX@}xSDT2fHm4!m-E5r zj=z?oIdB+A#vqUe7$|zqLsDkpq9fs{(CIWhaYylsaG1h`gCb*^Ytq$~V?JxVd-;Nc4Q-rg+{>9n;^~r9h72X6NEFp4sy;~6Pc`(iSWxFMwz%zk@fPk8&{(a zm@jJUzw{5^?bx%u#?~#k3Wl!HO8`Y<$u2B^DaV19ZV5ZK5e>Z}oD2R7h1z!W8D(D+ zH-gzoQ>4;^PC8r(=Nb0&Ibbz&>CGnmq(d?6g|d1_Q(?4bc-8fKJ{wrtK%Ce(r`%!N zSl8twAVRkZ1y9Ype$9KeF#0=^y&pXrV*J%lngbndzimW`r=lfV>_caL^tr(`cMcR~ zk)?UmTpil3bF$R4q)6(zkUZLRht=cXE?|NYpjtHL&4_sj9nGx=w`gjU*z?5a`7EIAtB z+iRdp#zrk>!*T?~aIQnOL&(a}3kV;`(4I~vV1R7^?;Esq3%Vmhh|(Kx%7Pldv$6Oh zp+(Y>xJm>gljiHhd_|0RgwwPwx|Y(#PScwu3k^g6^F`OAPL}pIX54r|uTPEkq|lzB z_d^x>!}lZ{m*e-4Wt;?Zg`qZf#&#m>sc0#XYqD=YXL*(?(H558QS3mkFTTk^jbEg> zqRe8>bSRhaPcuOxZ#aqo&pLfST){sX3)zkMu{hI%yLbuuJ52n~jIC{bI3zct_HQ=v zi>fDD81@T+xkT`uGV?PWpkXyKX^ueCXs%V;eN&KxGf6H;lg@C2kFEO!}qp&nhz#7LE26 zp_rJUlCkEc-pbza72Z0Fe7%ed9ICP)CQ|fj*IMY%sD$<%dH56yQsw=Zcy>ahXj!AA zgD6B2iDsnEB2%&Wsc%DKV#JKGt>n7$BRd)OFyV*;jP7~;4sN%%nxnb}gF+~>WKn!v9aV*|9m5dg!!!5n@$3H=kYlj&(G(#su z^obko1*W8o=6D-a^1*CTfHaKBJCfQ9a^?HCjdueHOqT3?1XH;!Rz%#s28D*vWnuXE z^a=p_0q$iJ`amMJP$PDb3=cC%&=vL==8~;KZR|w9bhhJWaPK@KC&1KSmiY;e7CQ0W50cFL6?m#1FtR~|W z^8gR+h1&L%o=senXvQxu*iG^KjHkmxH1-)XbRY%!1bW%Aw|s@SI5P>@#bfD%HTw$E z1NQ?-Ke34y9C7HoPWNJdKY~nX3g!MimLQ09)y8}B4btQAjosuMB+!{jV@0BPXYp8B z9nJ#molpzp8pG`-MMm2k4)8Vw|Cq5&tO7*YDBC{)|DqYu`FaLNffvjnk2Zz;arrK} z49cRVrT32MaLJN31Q3HL0A0OA8>UP0?F#^^9@IdO)zt{TUX518u#h-1mmTGBbaxaE z1;cB&kK_t|2XjQmirj07uF^~{B0KI!$pErxpdSHP-@?|tiJOmA!z^mX>6JN;jODdY zP{1#g>+29AbLk!*n&wV-(_j4QxC(t3K)@k(4=TJG6 z(RXTuhZFTrcKB9r1Z>wOBXOd=AbA_k@$L{C=>Vz6MeR+4uY{|e1BQg}E&x72i>h~!}Wr->#3wFqe!a`QS!`1EKj&jlOR)xm_Sxc>lQ>ue6dsxyI3XhNBV-JYtxjYunQi^igvBMO4+ydaZV+*v(W zY}@C;Ff=OmCwlH^InKIbw%5)n9OpVL z_Cn4bCv*1EI1Fqh373T@ttj{C+<1UErDek(N!{R*d=|75Ohlcib;}m~IE(AsQ-|Pa zpG??hxyu*@%ClbNaD*}1LmQ?@@&~{LR?uGX2uNCa&&U&^s6M}3Ye|@45t4-?kq?Qk z<~h?9odq2!Bo}ov5C_0+jEQ^Ej^j8iGwm|K{;Aa-s^p8}_00=(VZ@ZYu$8+iync`} zD{BRZ1>$wvnAJp$uGhy%ftq=@<1Puadh5i9j1L7J3vY0JTc>l-MC7EZk}%cp?9o(7 zAco$O7crd6LW_0Zz5(=x{~d{g{);d6^xeF;f(h{1POZK)VWmLD?)ti9cw; ze*^XPBM}T1_SL>>YKg#wQ*Iq`*fGa3fj>;ISwt2?Y$mXop`bVNLRMAL5=v2c)v9zI z_e3;m)3|fJRRko(#Sc=UvT53%l0=$v%#oG(k{xbAw$URvE>E%h zj4+I+vC$~*nI(Nl+EDD@u0(lWdV;KzAJCVmDM=?;%b|#**LhEi$B_sXj(7Zdt_nT8m#A*qc%x9 zT|rj7{6T)VXeu2DP^=jZdO#=$5n>WFVtejzld-ky$w6wJr@Y{2V33jM68;!cn!1Ck(RUG# z1CVxVv=ihY27E1ci3zfc3B-dNCYn+_vQFT~?oVC8pBXR;(w1P)yvBL~UO=fUqPeKR z;B-mo^a4PE`Po%l(87@VeN)zzlP07pOLfZh!0gOrN-*oXnciMu zi3L-pMvi6Iw#awHHHa0dmZ)zan17a<+xyh2^gePQkTI((I;g&U{|*Zt75r{7LEq2j zm=wXc(jyDepFd9>UJi(z^}Z11BQ>|313h&jXK>OSVubgIW*v`yWeCw0%Ci(5=)-R5 zj1-0)q?W#U$!_y==zFv2^z2}(*)_{Vub~r$go@J8&_0_eg(H}{f^Q3qpSft!|2Z-| zn6aJMI696ZOAG1|pz?vdFU38c9{;D1zO->E6iA7^S5m{(9iRhY4!JATVJHt|kr8CS7@s{zIAfcsS zDLFPPx4oe62`lC*NlT1YBQA!w1s%WHw%_Fq{k(#|@FTdmWRV>;7t2{uG~NmaKuU;& zz6K+~&-BjuEQ`NOpmSy5seWYE`YoE$Jar@dGAJZ7aH!fh+^p*t_ULb4U%UaW3uKEv zu!90c@2Pq+%P21hEBQ})#QK}&1v;07`w=7QJPaRJ?h{tf1=ezyS!p=75Pne0$Sz(k zNS=4x{D7|D387+x0^%6gp797>qip;z+6~wcARXi7j|48D9<_AFB-PgWHjL3KF5|Zy zCOKrE;oA&W#-Zc_haPAMmDi3OFBgX4oKMG&92N2Lk%Nf8nWPQiXp%$7wu2tA|E77f zJqmz*SM4h;3;B9c;>#3~J`wYf@<}>&(pFj-wGR_R%NBQi<>7%BY?~^`y zt<{pA@>xV9AdLkkfOf$q*5gTs=vpj18PA+F)9XLZx$<}noN_aCDOSlMoEn4U` z|M(Se2PUBw9G|t=vwwt+hA0M1aT6gh$Jja{AKKDVQ)4d)@3Af9P6{@}3h!b18juM% z1I!GOqH!diXak6TYo%6%WlBJr@bb28dpnNIl0VuD7CHvJP25FKNnKB*b>3nipn_nP z&47gyusN;gD|&-9$6Ut5J0!j2UEc}9bGl|4^>MVWuurF?^F&2oXJucMTU|tsX55v| z&M2)vSP>`e1B6-JW8XQbs;-#wPS&_G;DJHbmWGzI1s&#o4JQi+sH&E)e66QOW#_F} zo_eBwn_unY#Cdiq2d-8Ogf5L5Df_QI^IuP!;Yl6HbF50|^6Zyx+Gu0y zlNiyYmKfB7Ge!WC>|}Y?&X**6viW@jstbX|F__Ut^rMJv#1p2J@?W6@E*2GPBXlS5 z%9P3M9y;pk>I&b3JnEW+H(*XJ!P}@vTQ0VlkC$slT7`swOt@1>?d0h}5b(R+;FPGt z|DZ%SMF=e!mB&-0-(>(ErHg()#+wTkjgpP0a;~)2f5++D^|-nL;zFf;wK%XZ#$9(G0g36}fnx zU=$jW`y9FtI>1Rd#TE1E`8HY!$}j_~25!Ps-*g(Wj0wI(&d>GUPu5(=&%dJ@VlezW ziv8!<){C{SInyDD$omFEO-LPvFPBZm_*M%t|NLMB?SlESMX~>NeV?>uaKfC5idorJ za&|U0i_r3+FF$D(7|-FPd0fIHJF9R&w=qS!f*7prl^_D%LXT`?OkeCdIatUpx$E-e zay6(~-pnT27FD&wZ$QqqxX_~xNSL#1L z61`2TxcL;jq!1oqy*qdA;i^#j zU|NaRve`0$XZgE$J1hYoh(^F1f+wswK5urpzU@4FI0aGA&E2N0)=b5eD?hc!zp1IsGLXCTHM z^5ZbVPHHF=$rh}9vB&H=TMp&r_aMVF^cW(LdTi2#8XJhO5w;o%i+xEpe-;D~!q0T^ zdV&AD%;Q78jJB$Z%FwlyA;*15Vm?3mz*X0n=^IHMC*Ugz*hYvu@t7iEcgr{LG9^7% zB^Diqdux_xg*_VZ9_eoz9Z1=p?3gh%^*nHpM^>LdjS$XJ;p0oD5tPk8)wdf~^GjBK)C?I=3KV_cZ`>*cgpvv>$0l`z_=r~1_&4bqepwW>|0$; z@4Wh&-eqQ;oywn6qGPmH(6oxB{O;<%hu(aidF)3p*zW?z2&iv-OqDDxUsYU7ji_Y% z`=s@}d^Y+{lx)r1Kt!ff-amx#vh4baUgVr$;_dNF(ZW#B({{(H_x z$&wW3c9ZA+MXEBERJR8^E4B86kVs+i&3n29x`H~!UT@iKqu9X zz`wDs9T`Q%w}N|%RoIB%G$MEDV=s{#BBhXD3yKhF%dT0_exN(OXYWe6w>nQrpvNm3 zM)mK&QBp)XgUxpZUP0vyYkBHfFR*oWUS{d+0x$umikEp;4;_C+cx%VMe-xrdL8E4Q z9if>%jC8`4qEjeyPI}&}vnl=k{rlPeN7H)xXNrcPdZQBbWsjA<g|#$H_w77;_Kb6fGzEzJpj0M|*Y2n6I2B5g%b$bfmEXVgLSiXuP|6>m zg9p`MiN!biHmvh$l9^JXCD3xw`X5z+BCiNLy%FK`;x;0+N{Kk2wnus)a;4hu3TqASm~y@jX0V&qd-fRDal zR(@wUQ?e$}-pI&kG=+$WM3&BJwTL;CDHg{qXk?4rpqti(pa_x?2y;!8EX}&|;H%2A zi*w1nD11ia40NUqL4dvm$4Af)qQge(4(dF9MkUCA9zYLTOqIgRgPW3rf*8~1ySF2a zpEu-?){~TUz!Smo4?{yk$wW;tUDh%q^L@@%G+E^@vO>LzkM-rN)gbO~tY{e@wC|ZF zq~dA;7+}Lwvkf%_#-#=~er8MYf>sNf6!LE$xM~TlS@7?nW8wOw-yaiQ_wVnt=rXc2 zD&`fq)O@W7$wgpJ!>C|f0k%_n9U`V0g< zrc&ofY=m;Sdl|gnKD43tz$bas4Df^ac#P!Hg7mtmeU?ft8nlsMht{K#QiDjmN{U&w zDsdV7O&fviDg*Y^vm0d5xODsg>eh`F9%7_}`#S**X~gJ6!kJiF87^U}G;G`MLMwPT z^{rN`dC+empT9<6#?)Gs0><;EyX8lQEW+GnM6`!%az7EQQA_ex%3U)#Jk@{^s>!|S zLoW+k9-NuG{jOuinnbe~wsq|7C{Yxl5h={FDowX-|BF5vRvY?c*^}U=d3p@0PvA?_z zo`xa#4KuOiZwyv$4LBPNxy@P29 z9#>tD;s`_TCTCcb99mTm{oGO^zrVZXJkH^Y{QP`?nLO&&8ySVdfd}3{WC{7LH@^d> z)68dTwq1Wel1@N8y0<^!h=ZS>QtJ*Smupo4)o+~)9Zhtx*>78wIyh7}Ksmn+tY|gX zu2FkMFd6>L!?Ci{WOasZ7*jYT@8PeFn4F4)goKhee>$dDy@bm+GqHH&SyOVX-te

J20ax=RRVHqf5hpK`CK(1ypI5s$C&uuMJ#(sc*8WNg1k6h(g z;>05t|BC3$JuJ0R^iRBXEqSWGSVudID4S!~euB_(PHNxaM-eXwxhlrKSMA$@KaE|n zmssTOju~yUx#K_a7b7ypk3KvVrnTtGBz|&r*MPh6D`AO>F77(?gS*zJ^KPH)uJPX} zow?X7i*O(pT94g@FA|jG!?F*#O(Eg`-TY(u+JX}V@L9{w^ps^|Id5s;Q<}HY)FFbqLq`c z-AhJv5%9{BKZ(Ay2TkC&hl@fStixrCi-!XWT?0N-?z-2Tr4U-voPq>CI?-K z40@26=|nqJE|VKhUnMo6#xBlna*KY@QCLI3K#yH~(Rds?G&$YS5h!)p$?29S)4ldb zK#nKokLX*}!+&RZKP$UA{u*kmJyMekeC#IfncSz;R3ga7lF8K#ikp zoKf{{-%an|}Tl<5w+{O*e#OKD|Zl^7uqA_+C?jvi@tz8m^ zs_5e0A%}q5&8mRw;=NT%)=>5&4{7Xt(gM2+)Wn2Tkd2diEf72R+3KTcER)boi)e#Kc`Xc4I$W{69ZTPTL((PkM~=4y13N=*ZYpn?X`{W3rWGTC&$J zn|&H(vBt01HKe6WFN{ve!U`hzM39!TJDOw3+%5UvE4Ga+imHuilg!vUilmJL3lMer zZlcT0_T<5LgWb4Lrfclt`jEl9)#)tWBWlTM9YDkSD_ZYc zV?SL+|0*U4=`-EQ_kHUPmd(x(_r*pPsx+fBWl_a|PuoiRb>zecFP~#UlShKnTIzr9 z^q;8HoSdi2=qcG8hX8BCNQ_eRRZl<5aXO(m2Xx038lf(+dGO!+f@>eBu3~Jrt4i6cCd3h z0eL(IFR9-A{b@%Wa_@jRPX)mT3Gs$g{*yX%iNpej5d|Jfc%ZGV z4eY}_TQ@_a3#${6wlqJZeWawfKkDvi!eAHvn~|XJuK_&es>JgL)1=(e5#Q1>PZ>KF-0Ng1{Mo)s$>I*0#T@N#+#e1@U3!E0$~Zt^fT z4Fu~fjnb78-5ruG+y14C`@2^rTg1IXekqVDa3c9Rg3h9oRByJ-e;1o(b(XbUrE!ow zJ^|FuQdb#D3#Ha9LHD99@PSY0CA)lca5@?~;Cad@1cert0katzFHD0sc$5eKW&PV{ zTzM=TW8fL=CL{+Z(ful`d40A09mJVJwgd3W(>D@4P`LXV)~uRI*Ka%+6iCzwh1nwK z4q&tlfi=9{e!>{MbbsZ$z(@2!2pdS?2e&7BPC zEV*nBw7XJy$d9;%0&pQ<3Iw`xn9j)ya(n%h9zo({C&oEE%K2Ho&6HzX6>*05ek&H| zky^cehY;V1f}@s4ho}gSM!CPop43+8|Ag{PS*hXHlyZ#WFeE9{K(pPFhyW&$vnTNThkes-0SRW5%;l6JP7A8q~?wdQw- zm)!dEtu`c)tMh_hM;`LZ-mw?vo3-D&50o6nD;RF#U8T3zzOa=Uov|NS=Z~}JkAIr` zLVYJ0Fl1s6W*{JDAXsT1@B8)&>_WH-CH9@v_3skix8|Pi7TM=6v=ROioITIE7FYOG)1 zV@d~jr+OvMgFKh=+YcbNsdzeGWVx(qe8W+HgXC}yI7NN_Z@lso;?(4)P5fYWjaK{3og3S=_Sh%v1?J2+8kO4b&ZuQm<&7OAcV9 z_G~=+$;#yIEz0TNBs8;KJWy5lvSv}JaHlQrznxi=<$XaGMF*PW4Fc1dy+ILHSeM1j zq=W}c~Z@-BDidt z@YQ5YcG*K9!3RQq$a~JC%&g0@q?k+-v!|y!z~Is7y#t96(q-_er%p)i%&NWB4e2XzJA3BECb3zwNOAuX>M}qcVP>fk zc8q=CU-NgF(YK38H0F|UihPamLY%&TSD#q&J`{)!^x*H9QFDc=GXnUms`p>oVs}Re zGW+)GjUjDso?iD8Yw+iJrELe&j+o=VH$fI8vZ~@y zyC8dr0?uUZff~vYV#QBzs28!I7RG)1iE<$FpN!`;-~-&l-SZ{*${9snduYeivz9){ zu(Yt~i2V0GV5=5iY2B`cf;$>7)+zD^&a#@%%vE%}x-VJN{Xf#EI53M2uu>t2(AAk| zhP#L>uynlP)cIQ=XM*yeFk@-iGo&5uUVJ%~?SE>fyOrMuP=RPqF0$IFnIf6Yw&eXh z>kar`f(vGz1DshlMrb8pb0efj2dD4Z(K`g zZE6Qfy|%ke&UHhS9BXI_UBj0|LW&XpkpV>ZLF%z<&(d6kNViT-U+|u<*F#_0%_MKV zw|S>qa0SjfHfW9Oj5tG_;~DTo*}_Ua@$bex+O8lw`qeY)n5In8UI(! zzVhP<^Eab0aN$pCa?%|}U)KGy_fCzK+ufrer`<(=;v;x`ZA#4F4e$$hj1ashxl)(* z$x$J3%iLE)k)-br#M$}dS*h;tr`5P4Vfad1++E~bj9r|&KQY4YxW9WJ|Eci#1;})U z;@HM#)s`S$078RlT31jZyM#TlFf!vcY2XysC34o0nizFCqmF0P-W60djMf}M9e~5| z@Z6j;{$_r_KWsX!XNgkH1IJoqa_;+bqVpox+!b-JCt?}ZZtgSiNO9|*3VrX8|D9R8 z@_p@{Wb>|lah6+q z35bN>6)&`a=QzyXn`1*<{Opz%x?#~wtq;oq=X5ZfS+j3sH!~@Y4G!WQvj%&YJ$hCM zL*@7K-Up~GoW8YdrrZ?w1+ZCw7lK=NykDui%u#eygCCWrA_#>d+g09u!&IqB?$KzL ze;GoMLz6zPfGSPGZ|?N=EL$!%Iy%pLoqjD6R6H!Z?yKs>Ja!}GG~fguySP2z=yd#6 zLjTt%U_fv}Mdpr%HWhvoZHl=Twpn zCPR<>Y+A0+H5cC2!L^gGMn#@Oi<`f7Kxo;p9tuy%HwpEyT0%4ht*Yx{9!LSZ7bD(H zS|=0)@*4cb=2f;$b)9_)IhW#hlr+h)`xi$=nePSOI5hT#$;5EDPc>)Gxp0m1^Qs2Y z1p+~vdxRn)(mm{j1%J5DDLI08VY_%MX`NLN>|D%wS^*~WG@O{DZ2_SxpucjMw)`9J zbBGN)sRU{<3W5cTIdo2hl_OZL+$=(8r|CgMaAS(968G_LCPswn8yEzCKOyHhD-ST; z`?zgM|y z6qQ5)Ka-A_+{giDOJ0EcoORd~L5bppwfQ;wFndL8j3Pn>CD1l|bxL@mMxBd%e=H_<+019XOpMP>s;hz__PDhF6ePetKD6K4oGsmZ@l%&y)Pc_TUi6UbJ zDOP*1_!IDLc_3T{D!hrY7kXz3hE5~i`ki=Pg{9NpTP1DQF1&0Nl2hF-1NEU~EtIfq z<^G3o2!J_g*b6`<6+!OfN`>y*=hTY7IEkgZyChO*6HL3*Zg8!AZ~#b~DurrbLL2gn zvvNH8aDZHo=#rpi)zo5YpynC$1L0q7DqeTwX=H9CBy!guc4MSbw>(yOUC|=;3t^;? zduP+(3KggLlpbSb zXjK#M-Hfuu=&~lduEfWW z48=X9ICpig2n)jzu(KC0(mMw%M!y){z~54`M`~dAPYO&|glqT(K(h^Fke8ZF%ukFp zk4+dNr)0(lq4=KnUu=kw+C5ZN9SFTXFPEKVf6?>#YiV3+vkyvl`=+~n}> zD0xbqkFjkZR4xAUYL{3*e>5(S8Xhud5GWcs3Qbn6h`4#^i5dH7?4&`(Vm=z!K3SBy zf@delwvGZ6S~+=3V(~ueSe-|1IS_aXGbra~l(Uo@bLUOi>CMW){on8rHPLo~7kv$^ zt$%?ma+-Vxzf@w#7Tf`-viq$x<@PIdB;vENn3w{tRJA@o!xv@YBP6-mKe`RKJtT}D zqHBNeMhQMd@5oWcPKYrWE(O}PPrSuqcXu8SIjp(F7i537Tf3H>p=?k$Oq*m=DyAHq zcuDN;&zzFmK%q66X)s@Ntv^=5`yjXVA8`{ib)+{?h%__{*rQe}sGT(lxcNgO{W@{t z6*fiQr<%M(tZbH-rgP_igmIoTeDc|KpNTsz|Ehbx(+|+9Gb%Cc-W+FsEJrD&SJSWxwRbg=_dLd%)N0@8&RO~IJ00V*& z?*kZ4%VdAHJ!Io7Vssh;FxWsua1dl{6uFKzc?`I&J`aS=vh4vpatxBKm zlW0~c@`c+_%@9s&dh&A)yGv&fi1VO9U;XE5I)1qfW%^MHtIXwp_g>Z|B@}*20KG1; z7e1EauNZm>ca`z7m7-89KAD*sU^*;8u}jlH)QrT4U6?edh>1nTanamAD219gy_QVSj_;}BGJ#d8 zUpWwKSLccPi2o=j$3Bq*NO%x2{{-RpO02Eknt~ayUQUZesm<>4{Goqg1;;Q!UZBTf zY2{j#9%qMEewk=E$G5wpXFg1}_YY^N6@5I$cty_T_@I`L7tm3iz(9PtD8f&WkKVeT zyDUQp?{FyQFuj3}S5e|8p@bDlKp!dWEFR1wkY>?!D_Nzb{hf#rtTJjoXSGmAL)b-?QX7D8)@c%rKucc<(EuT(r+Zfn*q zSFUg8DU_Ti;$4&wC2DlbHQb{Q2Q})Z+cD?&N!nB+<9YpLlUfo}yO#n}b?gVgC_V#l zFKM&u0?u&5C1E2r;_dwD8MzBet-q&d#}y>KYV!*|?S~yU?SH-LR%+GKmjp)wZ*)K) zll)}1dQJxNKfv4Pk_;(_O#rP&66Lk zwky{@3P?;P$w4paz%~nH_CJAsEZ9Cb67YL`sNo{Rdt2IPV5HH!oGWnl#MmWc0v#SmlW$sN$t@C zp+hS@q64*KHh2Cm-Vk^ojC=`1%X{L(sZMSoLxcvYBk1{c*&e+|;c}f!ki$bx#MhbM z1?vlla_gjv{ec3V<5#Me^ZFc+;?&ZP3HwSDS-^;btf?LIt4uEtPTW`xI$3YCc#TL3ZemH)%pD2f(jzK?ht&HSmn&T9oh5I0#34p)wZaM{g1rF zv5i}pp&?jX8DKXFwIG=q;1%H2LjjGH>Mp$80sz#iU8O6iB zb>RZ>()N$&>75PraSI%~9?ZhRAie_Nn@zTGtoV%qLC4-qW%ft(3|x?wJeh%AV5E@$ z&}FLDB_8dljcC`5+=iodsp7pEvuk%-XXrXOOTnlT8Y#S_s+f6P#nwil$JWx#K~^FV7ot5V#+qR9kKdK!zMrGKmE=;!+&3?gk6o!W zArJW{m6rAk96;$?XV{lt8I0VAWF{#4$*l7!sscTx2n+zlb`!%)Ex7K^wAG6AOfVq8 z5nbgX_3(kaQmYY4f)sU-%uMk{;3$>41_IM=xKg0Z)Aw3u7|Kz3ua;QU``bCyfM-3` zPN62^_f|YilHr5$N)d-V&2(C1Jv%)m^PVfO2+s8aNHF6IjiDZVS%O;KBa3gwf&{&w{s())VbrBqhmh z`^nq8>m7t{tWXYA4_~JSZefZtss=icRc!#ZaHF`z4==HrHFBW4TSBWsgBCuBW9_*c zZQivDK0>yCCDbcL0(u<37WqkXmG|x4`>GU~q5w(`u`5lotmiK}S<-Eydr^2F3jfWj z0g^rwx>{1KS*320K<9%nVbYd1GHH@F?q6-F1Y6|7@o)n>%<3K{z-;oR)1a3Jx{*tS1{{bcaZ zL>n`=EvC|Mi-I|@1`<%mYCDLn&Y49TW6AWMzpZoFhSdffwcgb~yeZU%?q9 z4LQ&N4Iy^Akz@|ja9XO4Z0fOR*{)A$qq6W34&yNe-2#I#crG2mpQC z0V*~HuGiAc`;vw`<-lyBJyjCxFA7hwwLQUie#*`>--SJ)5>Wa&5`5o-uvR|c+4XeC zXtdR-wZ5wb=5)HHH#Rro%jpE7_Ectg;$zM47g3(eL2GybNLwog8$>Q<@WSknBaFwp z%;_Cly?vBgU)#2q)1DUI5Tp;E{&Zv&9XS!_rY?uGnjhx6U=F*0f%9b^ft((t5f%Ev z0EWFkCw6tB8RC6Nc?cSLl-@qu9c0OT)T!vWD1O6Eyy0~=Ofawr5`~DMDkHKossgN; zyN9d!<*ysr*V*f6R}-cC>}T0k+nAHUUf%5ows5gvVr3Jc(M73$Vq#+Mjtr_Dn1ub;iL-rQnr|hB zZ@FN(02${1Jc3GH6C_Si3I~L?JN*;@rQFY}4=Irgj|~YLY0FjYe~)eJpfNF`HPJJv z-A}_a;E0EqHs!tV=n05=VXJ1ERT$yDEv@ICRVixBo|eyFCAiPn9~-#sb21y5ZLm*l z(t1GV1<>Jb_9KhY&dL|IovRa1Z;`jIGIeDSyP+=E%N1M_e{Id%NKOC=GRu^=s5M)~r?Ep{}`Mz3ztf>y*{i nH>j&~yA${S*8?72`wqDJ|NkHOlp<3{zDgTg{E+@V=fwX5C2@_> literal 0 HcmV?d00001 diff --git a/test/base.jl b/test/base.jl index 6bf56e8..c91eba0 100644 --- a/test/base.jl +++ b/test/base.jl @@ -1,16 +1,25 @@ using Test using Dates +using HTTP using UUIDs using Base64 using MLFlowClient +using Minio +using URIs -function mlflow_server_is_running(mlf::MLFlow) - try - response = MLFlowClient.mlfget(mlf, "experiments/list") - return isa(response, Dict) - catch e - return false - end +# The route experiments/list was deprecated. +# It is thee in f.ex. 1.30.1, but not in 2.0.1: +# https://mlflow.org/docs/1.30.1/rest-api.html#list-experiments +# https://mlflow.org/docs/2.0.1/rest-api.html#list-experiments +# Query the health endpoint instead: https://github.com/mlflow/mlflow/pull/2725 +""" + mlflow_server_is_running(mlf::MLFlow) + +Check MLFlow health endpoint. Return true if healthy, false otherwise. +""" +function mlflow_server_is_running() + resp = HTTP.request("HEAD", "$(TEST_MLFLOW_URI)/health", readtimeout=10) + return resp.status == 200 end # creates an instance of mlf @@ -19,7 +28,27 @@ macro ensuremlf() e = quote encoded_credentials = Base64.base64encode("admin:password1234") mlf = MLFlow(headers=Dict("Authorization" => "Basic $(encoded_credentials)")) - mlflow_server_is_running(mlf) || return nothing + mlflow_server_is_running() || return nothing + mlf + end + eval(e) +end + +""" + minio_is_running + +Check minio health endpoint. Return true if health, false otherwise +""" +function minio_is_running() + response = HTTP.request("HEAD", "$(TEST_MLFLOW_S3_ENDPOINT_URL)/minio/health/live", readtimeout=10) + return response.status == 200 +end + +macro ensureminio() + e = quote + minio_is_running() || return nothing end eval(e) end + + diff --git a/test/runtests.jl b/test/runtests.jl index 1a0d314..7f145d4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,34 @@ if ~haskey(ENV, "MLFLOW_TRACKING_URI") - error("WARNING: MLFLOW_TRACKING_URI is not set. To run this tests, you need to set the URI of your MLFlow server API") + error("""WARNING: MLFLOW_TRACKING_URI is not set. +To run the unit tests, you need to set the URI of your MLFlow server API. + +A test environment is provided in .devcontainers/compose.yaml, start it as +`docker-compose .devcontainers/compose.yaml up`. + +Then set the environment variables +MLFLOW_TRACKING_URI="http://localhost:5050/api" +AWS_ACCESS_KEY_ID="minioadmin" +AWS_SECRET_ACCESS_KEY="minioadmin" +MLFLOW_S3_ENDPOINT_URL="http://localhost:9000" +""") end + +# Set up access to testing environment defined in docker-compose.test.yaml +const TEST_MLFLOW_URI = "http://localhost:5050/" +const TEST_MLFLOW_TRACKING_URI = "http://127.0.0.1:5050/api" +const TEST_MLFLOW_S3_ENDPOINT_URL = get(ENV, "MLFLOW_S3_ENDPOINT_URL", "http://127.0.0.1:9000") +const TEST_AWS_ACCESS_KEY_ID = get(ENV, "AWS_ACCESS_KEY_ID", "minioadmin") +const TEST_AWS_SECRET_ACCESS_KEY = get(ENV, "AWS_SECRET_ACCESS_KEY", "minioadmin") + + + + include("base.jl") +const minio_cfg = MinioConfig(TEST_MLFLOW_S3_ENDPOINT_URL; username=TEST_AWS_ACCESS_KEY_ID, password=TEST_AWS_SECRET_ACCESS_KEY) +include("setup.jl") + include("types/mlflow.jl") include("services/run.jl") @@ -13,4 +38,4 @@ include("services/artifact.jl") include("services/experiment.jl") include("services/registered_model.jl") include("services/model_version.jl") -include("services/user.jl") +#include("services/user.jl") diff --git a/test/services/experiment.jl b/test/services/experiment.jl index 264bb47..2ef5773 100644 --- a/test/services/experiment.jl +++ b/test/services/experiment.jl @@ -215,165 +215,167 @@ end end end -@testset verbose = true "create experiment permission" begin - @ensuremlf - - experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) - permission = Permission.parse("READ") - - @testset "with string experiment id" begin - user = createuser(mlf, "missy", "gala12345678") - experiment_permission = - createexperimentpermission(mlf, experiment_id, user.username, permission) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - deleteuser(mlf, user.username) - end - - @testset "with integer experiment id" begin - user = createuser(mlf, "missy", "gala12345678") - experiment_permission = - createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - deleteuser(mlf, user.username) - end - - @testset "with Experiment" begin - experiment = getexperiment(mlf, experiment_id) - user = createuser(mlf, "missy", "gala12345678") - experiment_permission = - createexperimentpermission(mlf, experiment, user.username, permission) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - deleteuser(mlf, user.username) - end - - deleteexperiment(mlf, experiment_id) -end - -@testset verbose = true "get experiment permission" begin - @ensuremlf - - experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) - permission = Permission.parse("READ") - user = createuser(mlf, "missy", "gala12345678") - - @testset "with string experiment id" begin - createexperimentpermission(mlf, experiment_id, user.username, permission) - experiment_permission = getexperimentpermission(mlf, experiment_id, user.username) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - @testset "with integer experiment id" begin - createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) - experiment_permission = getexperimentpermission(mlf, parse(Int, experiment_id), user.username) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - @testset "with Experiment" begin - experiment = getexperiment(mlf, experiment_id) - createexperimentpermission(mlf, experiment, user.username, permission) - experiment_permission = getexperimentpermission(mlf, experiment, user.username) - - @test experiment_permission isa ExperimentPermission - @test experiment_permission.experiment_id == experiment_id - @test experiment_permission.user_id == user.id - @test experiment_permission.permission == permission - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - deleteuser(mlf, user.username) - deleteexperiment(mlf, experiment_id) -end - -@testset verbose = true "update experiment permission" begin - @ensuremlf - - experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) - permission = Permission.parse("READ") - user = createuser(mlf, "missy", "gala12345678") - - @testset "with string experiment id" begin - createexperimentpermission(mlf, experiment_id, user.username, permission) - updateexperimentpermission(mlf, experiment_id, user.username, Permission.parse("EDIT")) - experiment_permission = getexperimentpermission(mlf, experiment_id, user.username) - - @test experiment_permission.permission == Permission.parse("EDIT") - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - @testset "with integer experiment id" begin - createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) - updateexperimentpermission(mlf, parse(Int, experiment_id), user.username, Permission.parse("EDIT")) - experiment_permission = getexperimentpermission(mlf, parse(Int, experiment_id), user.username) - - @test experiment_permission.permission == Permission.parse("EDIT") - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - @testset "with Experiment" begin - experiment = getexperiment(mlf, experiment_id) - createexperimentpermission(mlf, experiment, user.username, permission) - updateexperimentpermission(mlf, experiment, user.username, Permission.parse("EDIT")) - experiment_permission = getexperimentpermission(mlf, experiment, user.username) - - @test experiment_permission.permission == Permission.parse("EDIT") - deleteexperimentpermission(mlf, experiment_id, user.username) - end - - deleteuser(mlf, user.username) - deleteexperiment(mlf, experiment_id) -end - -@testset verbose = true "delete experiment permission" begin - @ensuremlf - - experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) - permission = Permission.parse("READ") - user = createuser(mlf, "missy", "gala12345678") - - @testset "with string experiment id" begin - createexperimentpermission(mlf, experiment_id, user.username, permission) - deleteexperimentpermission(mlf, experiment_id, user.username) - @test_throws ErrorException getexperimentpermission(mlf, experiment_id, user.username) - end - - @testset "with integer experiment id" begin - createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) - deleteexperimentpermission(mlf, parse(Int, experiment_id), user.username) - @test_throws ErrorException getexperimentpermission(mlf, parse(Int, experiment_id), user.username) - end - - @testset "with Experiment" begin - experiment = getexperiment(mlf, experiment_id) - createexperimentpermission(mlf, experiment, user.username, permission) - deleteexperimentpermission(mlf, experiment, user.username) - @test_throws ErrorException getexperimentpermission(mlf, experiment, user.username) - end - - deleteuser(mlf, user.username) - deleteexperiment(mlf, experiment_id) -end +# Disabling since /api/2.0/mlflow/users/create is no longer the right api to call. +# This one is used by createuser +#@testset verbose = true "create experiment permission" begin +# @ensuremlf +# +# experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) +# permission = Permission.parse("READ") +# +# @testset "with string experiment id" begin +# user = createuser(mlf, "missy", "gala12345678") +# experiment_permission = +# createexperimentpermission(mlf, experiment_id, user.username, permission) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# deleteuser(mlf, user.username) +# end +# +# @testset "with integer experiment id" begin +# user = createuser(mlf, "missy", "gala12345678") +# experiment_permission = +# createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# deleteuser(mlf, user.username) +# end +# +# @testset "with Experiment" begin +# experiment = getexperiment(mlf, experiment_id) +# user = createuser(mlf, "missy", "gala12345678") +# experiment_permission = +# createexperimentpermission(mlf, experiment, user.username, permission) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# deleteuser(mlf, user.username) +# end +# +# deleteexperiment(mlf, experiment_id) +#end +# +#@testset verbose = true "get experiment permission" begin +# @ensuremlf +# +# experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) +# permission = Permission.parse("READ") +# user = createuser(mlf, "missy", "gala12345678") +# +# @testset "with string experiment id" begin +# createexperimentpermission(mlf, experiment_id, user.username, permission) +# experiment_permission = getexperimentpermission(mlf, experiment_id, user.username) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# @testset "with integer experiment id" begin +# createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) +# experiment_permission = getexperimentpermission(mlf, parse(Int, experiment_id), user.username) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# @testset "with Experiment" begin +# experiment = getexperiment(mlf, experiment_id) +# createexperimentpermission(mlf, experiment, user.username, permission) +# experiment_permission = getexperimentpermission(mlf, experiment, user.username) +# +# @test experiment_permission isa ExperimentPermission +# @test experiment_permission.experiment_id == experiment_id +# @test experiment_permission.user_id == user.id +# @test experiment_permission.permission == permission +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# deleteuser(mlf, user.username) +# deleteexperiment(mlf, experiment_id) +#end +# +#@testset verbose = true "update experiment permission" begin +# @ensuremlf +# +# experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) +# permission = Permission.parse("READ") +# user = createuser(mlf, "missy", "gala12345678") +# +# @testset "with string experiment id" begin +# createexperimentpermission(mlf, experiment_id, user.username, permission) +# updateexperimentpermission(mlf, experiment_id, user.username, Permission.parse("EDIT")) +# experiment_permission = getexperimentpermission(mlf, experiment_id, user.username) +# +# @test experiment_permission.permission == Permission.parse("EDIT") +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# @testset "with integer experiment id" begin +# createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) +# updateexperimentpermission(mlf, parse(Int, experiment_id), user.username, Permission.parse("EDIT")) +# experiment_permission = getexperimentpermission(mlf, parse(Int, experiment_id), user.username) +# +# @test experiment_permission.permission == Permission.parse("EDIT") +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# @testset "with Experiment" begin +# experiment = getexperiment(mlf, experiment_id) +# createexperimentpermission(mlf, experiment, user.username, permission) +# updateexperimentpermission(mlf, experiment, user.username, Permission.parse("EDIT")) +# experiment_permission = getexperimentpermission(mlf, experiment, user.username) +# +# @test experiment_permission.permission == Permission.parse("EDIT") +# deleteexperimentpermission(mlf, experiment_id, user.username) +# end +# +# deleteuser(mlf, user.username) +# deleteexperiment(mlf, experiment_id) +#end +# +#@testset verbose = true "delete experiment permission" begin +# @ensuremlf +# +# experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) +# permission = Permission.parse("READ") +# user = createuser(mlf, "missy", "gala12345678") +# +# @testset "with string experiment id" begin +# createexperimentpermission(mlf, experiment_id, user.username, permission) +# deleteexperimentpermission(mlf, experiment_id, user.username) +# @test_throws ErrorException getexperimentpermission(mlf, experiment_id, user.username) +# end +# +# @testset "with integer experiment id" begin +# createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) +# deleteexperimentpermission(mlf, parse(Int, experiment_id), user.username) +# @test_throws ErrorException getexperimentpermission(mlf, parse(Int, experiment_id), user.username) +# end +# +# @testset "with Experiment" begin +# experiment = getexperiment(mlf, experiment_id) +# createexperimentpermission(mlf, experiment, user.username, permission) +# deleteexperimentpermission(mlf, experiment, user.username) +# @test_throws ErrorException getexperimentpermission(mlf, experiment, user.username) +# end +# +# deleteuser(mlf, user.username) +# deleteexperiment(mlf, experiment_id) +#end diff --git a/test/services/logger.jl b/test/services/logger.jl index 2d77ea6..d9d24f2 100644 --- a/test/services/logger.jl +++ b/test/services/logger.jl @@ -335,3 +335,83 @@ end deleteexperiment(mlf, experiment_id) end + +@testset verbose = true "logartifact" begin + @ensuremlf + result = @ensureminio + if result === nothing + @test_skip "skipped log artifact tests" + else + dummy_file_path = "test_artifact.txt" + dummy_content = "This is a test artifact." + open(dummy_file_path, "w") do fp + write(fp, dummy_content) + end + + image_file_path = joinpath("test", "assets", "julia.png") + + @testset "upload new artifact" begin + experiment_id = createexperiment(mlf, "test-experiment-logartifact") + run = createrun(mlf, experiment_id) + + @test logartifact(minio_cfg, run, dummy_file_path) + + u = URI(run.info.artifact_uri) + bucket_name = u.host + artifact_path = joinpath(u.path[2:end], "test_artifact.txt") + + # verify upload + downloaded_content = s3_get(minio_cfg, bucket_name, artifact_path) + @test String(downloaded_content) == dummy_content + @show String(downloaded_content), dummy_content + + # Cleanup + s3_delete(minio_cfg, bucket_name, artifact_path) + deleterun(mlf, run) + deleteexperiment(mlf, experiment_id) + end + + @testset "upload new artifact with specific name" begin + experiment_id = createexperiment(mlf, "test-experiment-logartifact-2") + run = createrun(mlf, experiment_id) + + @test logartifact(minio_cfg, run, dummy_file_path, "my_artifact.txt") + + u = URI(run.info.artifact_uri) + bucket_name = u.host + artifact_path = joinpath(u.path[2:end], "my_artifact.txt") + + # verify upload + downloaded_content = s3_get(minio_cfg, bucket_name, artifact_path) + @test String(downloaded_content) == dummy_content + @show String(downloaded_content) + + # Cleanup + s3_delete(minio_cfg, bucket_name, artifact_path) + deleterun(mlf, run) + deleteexperiment(mlf, experiment_id) + end + + @testset "upload image artifact" begin + experiment_id = createexperiment(mlf, "test-experiment-logartifact-3") + run = createrun(mlf, experiment_id) + + @test logartifact(minio_cfg, run, image_file_path, "julia.png") + u = URI(run.info.artifact_uri) + bucket_name = u.host + artifact_path = joinpath(u.path[2:end], "julia.png") + + # verify upload + downloaded_content = s3_get(minio_cfg, bucket_name, artifact_path) + @test downloaded_content == read(image_file_path) + s3_delete(minio_cfg, bucket_name, artifact_path) + deleterun(mlf, run) + deleteexperiment(mlf, experiment_id) + end + + rm(dummy_file_path) + end +end + + + diff --git a/test/services/registered_model.jl b/test/services/registered_model.jl index baf7503..88e4207 100644 --- a/test/services/registered_model.jl +++ b/test/services/registered_model.jl @@ -177,69 +177,70 @@ end deleteexperiment(mlf, experiment) end -@testset verbose = true "create registered model permission" begin - @ensuremlf - - registered_model = createregisteredmodel(mlf, "missy"; description="gala") - user = createuser(mlf, "missy", "gala12345678") - permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) - - @test permission isa RegisteredModelPermission - @test permission.name == registered_model.name - @test permission.user_id == user.id - @test permission.permission == ("READ" |> Permission.parse) - - deleteregisteredmodelpermission(mlf, registered_model.name, user.username) - deleteuser(mlf, user.username) - deleteregisteredmodel(mlf, "missy") -end - -@testset verbose = true "get registered model permission" begin - @ensuremlf - - registered_model = createregisteredmodel(mlf, "missy"; description="gala") - user = createuser(mlf, "missy", "gala12345678") - permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) - retrieved_permission = getregisteredmodelpermission(mlf, registered_model.name, user.username) - - @test retrieved_permission isa RegisteredModelPermission - @test retrieved_permission.name == registered_model.name - @test retrieved_permission.user_id == user.id - @test retrieved_permission.permission == ("READ" |> Permission.parse) - - deleteregisteredmodelpermission(mlf, registered_model.name, user.username) - deleteuser(mlf, user.username) - deleteregisteredmodel(mlf, "missy") -end - -@testset verbose = true "update registered model permission" begin - @ensuremlf - - registered_model = createregisteredmodel(mlf, "missy"; description="gala") - user = createuser(mlf, "missy", "gala12345678") - permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) - updateregisteredmodelpermission(mlf, registered_model.name, user.username, "MANAGE" |> Permission.parse) - retrieved_permission = getregisteredmodelpermission(mlf, registered_model.name, user.username) - - @test retrieved_permission isa RegisteredModelPermission - @test retrieved_permission.name == registered_model.name - @test retrieved_permission.user_id == user.id - @test retrieved_permission.permission == ("MANAGE" |> Permission.parse) - - deleteregisteredmodelpermission(mlf, registered_model.name, user.username) - deleteuser(mlf, user.username) - deleteregisteredmodel(mlf, "missy") -end +# Disabling them - Looks like /api/2.0/mlflow/users/create is no longer in the API +#@testset verbose = true "create registered model permission" begin +# @ensuremlf # -@testset verbose = true "delete registered model permission" begin - @ensuremlf - - registered_model = createregisteredmodel(mlf, "missy"; description="gala") - user = createuser(mlf, "missy", "gala12345678") - permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) - deleteregisteredmodelpermission(mlf, registered_model.name, user.username) - - @test_throws ErrorException getregisteredmodelpermission(mlf, registered_model.name, user.username) - deleteuser(mlf, user.username) - deleteregisteredmodel(mlf, "missy") -end +# registered_model = createregisteredmodel(mlf, "missy"; description="gala") +# user = createuser(mlf, "missy", "gala12345678") +# permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) +# +# @test permission isa RegisteredModelPermission +# @test permission.name == registered_model.name +# @test permission.user_id == user.id +# @test permission.permission == ("READ" |> Permission.parse) +# +# deleteregisteredmodelpermission(mlf, registered_model.name, user.username) +# deleteuser(mlf, user.username) +# deleteregisteredmodel(mlf, "missy") +#end +# +#@testset verbose = true "get registered model permission" begin +# @ensuremlf +# +# registered_model = createregisteredmodel(mlf, "missy"; description="gala") +# user = createuser(mlf, "missy", "gala12345678") +# permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) +# retrieved_permission = getregisteredmodelpermission(mlf, registered_model.name, user.username) +# +# @test retrieved_permission isa RegisteredModelPermission +# @test retrieved_permission.name == registered_model.name +# @test retrieved_permission.user_id == user.id +# @test retrieved_permission.permission == ("READ" |> Permission.parse) +# +# deleteregisteredmodelpermission(mlf, registered_model.name, user.username) +# deleteuser(mlf, user.username) +# deleteregisteredmodel(mlf, "missy") +#end +# +#@testset verbose = true "update registered model permission" begin +# @ensuremlf +# +# registered_model = createregisteredmodel(mlf, "missy"; description="gala") +# user = createuser(mlf, "missy", "gala12345678") +# permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) +# updateregisteredmodelpermission(mlf, registered_model.name, user.username, "MANAGE" |> Permission.parse) +# retrieved_permission = getregisteredmodelpermission(mlf, registered_model.name, user.username) +# +# @test retrieved_permission isa RegisteredModelPermission +# @test retrieved_permission.name == registered_model.name +# @test retrieved_permission.user_id == user.id +# @test retrieved_permission.permission == ("MANAGE" |> Permission.parse) +# +# deleteregisteredmodelpermission(mlf, registered_model.name, user.username) +# deleteuser(mlf, user.username) +# deleteregisteredmodel(mlf, "missy") +#end +# +#@testset verbose = true "delete registered model permission" begin +# @ensuremlf +# +# registered_model = createregisteredmodel(mlf, "missy"; description="gala") +# user = createuser(mlf, "missy", "gala12345678") +# permission = createregisteredmodelpermission(mlf, registered_model.name, user.username, "READ" |> Permission.parse) +# deleteregisteredmodelpermission(mlf, registered_model.name, user.username) +# +# @test_throws ErrorException getregisteredmodelpermission(mlf, registered_model.name, user.username) +# deleteuser(mlf, user.username) +# deleteregisteredmodel(mlf, "missy") +#end diff --git a/test/setup.jl b/test/setup.jl new file mode 100644 index 0000000..3465bec --- /dev/null +++ b/test/setup.jl @@ -0,0 +1,25 @@ +# Test if MLFlow and Minio are running (see .devcontainers/compose.yaml) + +@testset verbose = true "infrastructure" begin + println("Testing if MLFlow is running") + + mlflow_up = try + mlflow_server_is_running() + catch + false + end + mlflow_up || @error "The MLFlow test instance is not running. Please start it as `docker-compose -f .devcontainers/compose.yaml -up" + + @test mlflow_up + println("Testing if Minio is running") + + minio_up = try + minio_is_running() + catch + false + end + + minio_up || @error "The Minio test instance is not running. Please start the test environment as `docker-compose -f .devcontainers/compose.yaml up`" + + @test minio_up +end From e61470dbe347e266606a132ba7a47ad8582223df Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Sun, 30 Nov 2025 19:36:26 -0800 Subject: [PATCH 3/8] fixing CI.yml --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bec72fe..5599bd5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Start services - - run: docker-compose -f docker-compose.test.yaml up -d + run: docker-compose -f docker-compose.test.yaml up -d # - name: Setup custom python requirements # if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' # run: | From c8c1e3e928b19d8882c133f194a0854801d3e833 Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Sun, 30 Nov 2025 20:00:49 -0800 Subject: [PATCH 4/8] fixing CI.yml --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5599bd5..7593272 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Start services - run: docker-compose -f docker-compose.test.yaml up -d + run: docker-compose -f docker compose.test.yaml up -d # - name: Setup custom python requirements # if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' # run: | From ebfcaee19eba756b0c11c2f2e59ed3f4785ee2df Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Mon, 1 Dec 2025 06:41:08 -0800 Subject: [PATCH 5/8] Adding authentication to docker file. --- docker-compose.test.yaml | 5 +- test/runtests.jl | 2 +- test/services/experiment.jl | 96 ++++++++++++++++++------------------- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml index b2b4ca8..f71b50c 100644 --- a/docker-compose.test.yaml +++ b/docker-compose.test.yaml @@ -24,7 +24,7 @@ services: " mlflow: - image: ghcr.io/mlflow/mlflow:v2.14.1 + image: ghcr.io/mlflow/mlflow:v3.6.0 depends_on: - create-buckets ports: @@ -33,11 +33,14 @@ services: - AWS_ACCESS_KEY_ID=minioadmin - AWS_SECRET_ACCESS_KEY=minioadmin - MLFLOW_S3_ENDPOINT_URL=http://minio:9000 + - MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' command: > /bin/sh -c " pip install boto3 && + pip install mlflow[auth] && mlflow server --host 0.0.0.0 --port 5050 + --app-name basic-auth --default-artifact-root s3://mlflow " diff --git a/test/runtests.jl b/test/runtests.jl index 7f145d4..3ae9c88 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,4 +38,4 @@ include("services/artifact.jl") include("services/experiment.jl") include("services/registered_model.jl") include("services/model_version.jl") -#include("services/user.jl") +include("services/user.jl") diff --git a/test/services/experiment.jl b/test/services/experiment.jl index 2ef5773..6cbaaf2 100644 --- a/test/services/experiment.jl +++ b/test/services/experiment.jl @@ -217,54 +217,54 @@ end # Disabling since /api/2.0/mlflow/users/create is no longer the right api to call. # This one is used by createuser -#@testset verbose = true "create experiment permission" begin -# @ensuremlf -# -# experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) -# permission = Permission.parse("READ") -# -# @testset "with string experiment id" begin -# user = createuser(mlf, "missy", "gala12345678") -# experiment_permission = -# createexperimentpermission(mlf, experiment_id, user.username, permission) -# -# @test experiment_permission isa ExperimentPermission -# @test experiment_permission.experiment_id == experiment_id -# @test experiment_permission.user_id == user.id -# @test experiment_permission.permission == permission -# deleteexperimentpermission(mlf, experiment_id, user.username) -# deleteuser(mlf, user.username) -# end -# -# @testset "with integer experiment id" begin -# user = createuser(mlf, "missy", "gala12345678") -# experiment_permission = -# createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) -# -# @test experiment_permission isa ExperimentPermission -# @test experiment_permission.experiment_id == experiment_id -# @test experiment_permission.user_id == user.id -# @test experiment_permission.permission == permission -# deleteexperimentpermission(mlf, experiment_id, user.username) -# deleteuser(mlf, user.username) -# end -# -# @testset "with Experiment" begin -# experiment = getexperiment(mlf, experiment_id) -# user = createuser(mlf, "missy", "gala12345678") -# experiment_permission = -# createexperimentpermission(mlf, experiment, user.username, permission) -# -# @test experiment_permission isa ExperimentPermission -# @test experiment_permission.experiment_id == experiment_id -# @test experiment_permission.user_id == user.id -# @test experiment_permission.permission == permission -# deleteexperimentpermission(mlf, experiment_id, user.username) -# deleteuser(mlf, user.username) -# end -# -# deleteexperiment(mlf, experiment_id) -#end +@testset verbose = true "create experiment permission" begin + @ensuremlf + + experiment_id = createexperiment(mlf, UUIDs.uuid4() |> string) + permission = Permission.parse("READ") + + @testset "with string experiment id" begin + user = createuser(mlf, "missy", "gala12345678") + experiment_permission = + createexperimentpermission(mlf, experiment_id, user.username, permission) + + @test experiment_permission isa ExperimentPermission + @test experiment_permission.experiment_id == experiment_id + @test experiment_permission.user_id == user.id + @test experiment_permission.permission == permission + deleteexperimentpermission(mlf, experiment_id, user.username) + deleteuser(mlf, user.username) + end + + @testset "with integer experiment id" begin + user = createuser(mlf, "missy", "gala12345678") + experiment_permission = + createexperimentpermission(mlf, parse(Int, experiment_id), user.username, permission) + + @test experiment_permission isa ExperimentPermission + @test experiment_permission.experiment_id == experiment_id + @test experiment_permission.user_id == user.id + @test experiment_permission.permission == permission + deleteexperimentpermission(mlf, experiment_id, user.username) + deleteuser(mlf, user.username) + end + + @testset "with Experiment" begin + experiment = getexperiment(mlf, experiment_id) + user = createuser(mlf, "missy", "gala12345678") + experiment_permission = + createexperimentpermission(mlf, experiment, user.username, permission) + + @test experiment_permission isa ExperimentPermission + @test experiment_permission.experiment_id == experiment_id + @test experiment_permission.user_id == user.id + @test experiment_permission.permission == permission + deleteexperimentpermission(mlf, experiment_id, user.username) + deleteuser(mlf, user.username) + end + + deleteexperiment(mlf, experiment_id) +end # #@testset verbose = true "get experiment permission" begin # @ensuremlf From b3dc479034d60141892c6b8a4831fc1787340c98 Mon Sep 17 00:00:00 2001 From: Jose Esparza Date: Mon, 1 Dec 2025 15:31:53 -0500 Subject: [PATCH 6/8] Fixing CI pipeline, generating image for artifact testing, and generalizing ENVs in tests --- .github/workflows/CI.yml | 42 +++++++++++++++++++-------------------- Project.toml | 6 ++++-- test/assets/julia.png | Bin 84469 -> 0 bytes test/base.jl | 11 +++++++--- test/runtests.jl | 33 ++++++++---------------------- test/services/logger.jl | 8 +++++--- test/setup.jl | 7 ++----- 7 files changed, 48 insertions(+), 59 deletions(-) delete mode 100644 test/assets/julia.png diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7593272..e6f86d5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -22,23 +22,27 @@ jobs: - x64 steps: - uses: actions/checkout@v2 - - name: Start services - run: docker-compose -f docker compose.test.yaml up -d -# - name: Setup custom python requirements -# if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' -# run: | -# touch ./requirements.txt -# echo "mlflow[auth]==3.2.0" > ./requirements.txt -# - uses: actions/setup-python@v4 -# with: -# python-version: '3.12.3' -# cache: 'pip' -# - name: Setup mlflow locally -# run: | -# export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' -# pip install -r ./requirements.txt -# python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 & -# sleep 5 + - name: Setup custom python requirements + if: hashFiles('**/requirements.txt', '**/pyproject.toml') == '' + run: | + touch ./requirements.txt + echo "mlflow[auth]==3.6.0" > ./requirements.txt + - uses: actions/setup-python@v4 + with: + python-version: '3.12.3' + cache: 'pip' + - uses: infleet/minio-action@v0.0.1 + with: + port: "9000" + version: "latest" + username: "minioadmin" + password: "minioadmin" + - name: Setup mlflow locally + run: | + export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' + pip install -r ./requirements.txt + python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 & + sleep 5 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} @@ -57,10 +61,6 @@ jobs: - uses: julia-actions/julia-runtest@v1 env: JULIA_NUM_THREADS: '1' - MLFLOW_TRACKING_URI: "http://localhost:5050/api" - MLFLOW_S3_ENDPOINT_URL: "http://minio:9000" - AWS_ACCESS_KEY_ID: minioadmin - AWS_SECRET_ACCESS_KEY: minioadmin - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v3 with: diff --git a/Project.toml b/Project.toml index 8ae69c9..7f9b100 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "MLFlowClient" uuid = "64a0f543-368b-4a9a-827a-e71edb2a0b83" -authors = ["@deyandyankov, @pebeto, and contributors"] version = "0.7.0" +authors = ["@deyandyankov, @pebeto, and contributors"] [deps] AWSS3 = "1c724243-ef5b-51ab-93f4-b0a88ac62a95" @@ -22,6 +22,7 @@ FileTypes = "0.1.1" HTTP = "1.0" JSON = "0.21" Minio = "0.2.2" +Plots = "1.41.2" ShowCases = "0.1" URIs = "1.0" julia = "1.0" @@ -29,6 +30,7 @@ julia = "1.0" [extras] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [targets] -test = ["Base64", "Test"] +test = ["Base64", "Plots", "Test"] diff --git a/test/assets/julia.png b/test/assets/julia.png deleted file mode 100644 index 3219471c694da215cda755ea26b55a92947d5fdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84469 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 diff --git a/test/base.jl b/test/base.jl index c91eba0..5ed2940 100644 --- a/test/base.jl +++ b/test/base.jl @@ -1,6 +1,7 @@ using Test using Dates using HTTP +using Plots using UUIDs using Base64 using MLFlowClient @@ -18,8 +19,8 @@ using URIs Check MLFlow health endpoint. Return true if healthy, false otherwise. """ function mlflow_server_is_running() - resp = HTTP.request("HEAD", "$(TEST_MLFLOW_URI)/health", readtimeout=10) - return resp.status == 200 + resp = HTTP.request("HEAD", "http://127.0.0.1:5050/health", readtimeout=10) + return resp.status == 200 end # creates an instance of mlf @@ -40,7 +41,11 @@ end Check minio health endpoint. Return true if health, false otherwise """ function minio_is_running() - response = HTTP.request("HEAD", "$(TEST_MLFLOW_S3_ENDPOINT_URL)/minio/health/live", readtimeout=10) + response = HTTP.request( + "HEAD", + "http://127.0.0.1:9000/minio/health/live", + readtimeout=10, + ) return response.status == 200 end diff --git a/test/runtests.jl b/test/runtests.jl index 3ae9c88..2a288ef 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,32 +1,15 @@ -if ~haskey(ENV, "MLFLOW_TRACKING_URI") - error("""WARNING: MLFLOW_TRACKING_URI is not set. -To run the unit tests, you need to set the URI of your MLFlow server API. - -A test environment is provided in .devcontainers/compose.yaml, start it as -`docker-compose .devcontainers/compose.yaml up`. - -Then set the environment variables -MLFLOW_TRACKING_URI="http://localhost:5050/api" -AWS_ACCESS_KEY_ID="minioadmin" -AWS_SECRET_ACCESS_KEY="minioadmin" -MLFLOW_S3_ENDPOINT_URL="http://localhost:9000" -""") -end - - -# Set up access to testing environment defined in docker-compose.test.yaml -const TEST_MLFLOW_URI = "http://localhost:5050/" -const TEST_MLFLOW_TRACKING_URI = "http://127.0.0.1:5050/api" -const TEST_MLFLOW_S3_ENDPOINT_URL = get(ENV, "MLFLOW_S3_ENDPOINT_URL", "http://127.0.0.1:9000") -const TEST_AWS_ACCESS_KEY_ID = get(ENV, "AWS_ACCESS_KEY_ID", "minioadmin") -const TEST_AWS_SECRET_ACCESS_KEY = get(ENV, "AWS_SECRET_ACCESS_KEY", "minioadmin") - - +@warn "To run this test suite, ensure to run `docker-compose -f docker-compose.test.yml up`" +ENV["GKSwstype"]="nul" # to disable plotting windows during tests +ENV["MLFLOW_TRACKING_URI"] = "http://127.0.0.1:5050/api" include("base.jl") -const minio_cfg = MinioConfig(TEST_MLFLOW_S3_ENDPOINT_URL; username=TEST_AWS_ACCESS_KEY_ID, password=TEST_AWS_SECRET_ACCESS_KEY) +const minio_cfg = MinioConfig( + "http://127.0.0.1:9000"; + username="minioadmin", + password="minioadmin", +) include("setup.jl") include("types/mlflow.jl") diff --git a/test/services/logger.jl b/test/services/logger.jl index d9d24f2..2c68f5f 100644 --- a/test/services/logger.jl +++ b/test/services/logger.jl @@ -348,7 +348,8 @@ end write(fp, dummy_content) end - image_file_path = joinpath("test", "assets", "julia.png") + image_file_path = "fig.png" + savefig(plot(rand(10), rand(10)), image_file_path) @testset "upload new artifact" begin experiment_id = createexperiment(mlf, "test-experiment-logartifact") @@ -396,10 +397,10 @@ end experiment_id = createexperiment(mlf, "test-experiment-logartifact-3") run = createrun(mlf, experiment_id) - @test logartifact(minio_cfg, run, image_file_path, "julia.png") + @test logartifact(minio_cfg, run, image_file_path, "fig.png") u = URI(run.info.artifact_uri) bucket_name = u.host - artifact_path = joinpath(u.path[2:end], "julia.png") + artifact_path = joinpath(u.path[2:end], "fig.png") # verify upload downloaded_content = s3_get(minio_cfg, bucket_name, artifact_path) @@ -410,6 +411,7 @@ end end rm(dummy_file_path) + rm(image_file_path) end end diff --git a/test/setup.jl b/test/setup.jl index 3465bec..bdf25fe 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -1,17 +1,14 @@ # Test if MLFlow and Minio are running (see .devcontainers/compose.yaml) @testset verbose = true "infrastructure" begin - println("Testing if MLFlow is running") - mlflow_up = try mlflow_server_is_running() catch false end - mlflow_up || @error "The MLFlow test instance is not running. Please start it as `docker-compose -f .devcontainers/compose.yaml -up" + mlflow_up || @error "The MLFlow test instance is not running." @test mlflow_up - println("Testing if Minio is running") minio_up = try minio_is_running() @@ -19,7 +16,7 @@ false end - minio_up || @error "The Minio test instance is not running. Please start the test environment as `docker-compose -f .devcontainers/compose.yaml up`" + minio_up || @error "The MiniO test instance is not running." @test minio_up end From e7b047f63b5fc55121e2e662cd84a350c018e512 Mon Sep 17 00:00:00 2001 From: Jose Esparza Date: Mon, 1 Dec 2025 15:41:36 -0500 Subject: [PATCH 7/8] Changing `mlflow` port in CI pipeline to pair with test suite --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e6f86d5..6657aa5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -41,7 +41,7 @@ jobs: run: | export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl' pip install -r ./requirements.txt - python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5000 & + python3 /opt/hostedtoolcache/Python/3.12.3/x64/bin/mlflow server --app-name basic-auth --host 0.0.0.0 --port 5050 & sleep 5 - uses: julia-actions/setup-julia@v1 with: From b617b1dcc3d874720a6408bdf0182f5951cd6bad Mon Sep 17 00:00:00 2001 From: Ralph Kube Date: Sat, 6 Dec 2025 08:12:53 -0800 Subject: [PATCH 8/8] Using rclone as s3 server in workflows --- .github/workflows/CI.yml | 43 ++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6657aa5..7aad67d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -20,6 +20,7 @@ jobs: - ubuntu-latest arch: - x64 + steps: - uses: actions/checkout@v2 - name: Setup custom python requirements @@ -31,12 +32,42 @@ jobs: with: python-version: '3.12.3' cache: 'pip' - - uses: infleet/minio-action@v0.0.1 - with: - port: "9000" - version: "latest" - username: "minioadmin" - password: "minioadmin" + - name: Setup rclone S3 server + run: | + # Download rclone + curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip + unzip rclone-current-linux-amd64.zip + cd rclone-*-linux-amd64 + sudo cp rclone /usr/bin/ + cd .. + + # Create the data directory and the "mlflow" bucket (subdirectory) + mkdir -p ./s3-data/mlflow + + # Start Rclone serving the local folder as S3 + # --auth-key sets access_key,secret_key + rclone serve s3 ./s3-data \ + --addr 127.0.0.1:8080 \ + --auth-key minioadmin,minioadmin \ + --no-modtime \ + & + + # Wait for port 8080 to be ready + sleep 3 +# - name: Start MinIO +# run: | +# curl -O https://dl.min.io/server/minio/release/linux-amd64/minio +# chmod +x minio +# export MINIO_ROOT_USER=minioadmin +# export MINIO_ROOT_PASSWORD=minioadmin +# ./minio server ./data --console-address ":9001" & +# sleep 5 +# - name: Create MLFlow bucket +# run: | +# curl -O https://dl.min.io/client/mc/release/linux-amd64/mc +# chmod +x mc +# ./mc alias set myminio http://localhost:9000 minioadmin minioadmin +# ./mc mb myminio/mlflow - name: Setup mlflow locally run: | export MLFLOW_FLASK_SERVER_SECRET_KEY='mlflowclient.jl'