From f7f891d180edb0efd5b0ef065ec4358697e7a8a1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 5 Mar 2024 07:41:42 +0000 Subject: [PATCH] Updated documentation --- _modules/apis_core/utils/rdf.html | 53 ++++++-- _modules/apis_core/utils/test_rdf.html | 56 ++++++-- genindex.html | 80 ++++++++++- modules/apis_core.html | 33 +++++ modules/apis_core.utils.html | 176 ++++++++++++++++++++++++- objects.inv | Bin 6794 -> 6956 bytes searchindex.js | 2 +- 7 files changed, 367 insertions(+), 33 deletions(-) diff --git a/_modules/apis_core/utils/rdf.html b/_modules/apis_core/utils/rdf.html index 889916f32..f23318319 100644 --- a/_modules/apis_core/utils/rdf.html +++ b/_modules/apis_core/utils/rdf.html @@ -93,6 +93,8 @@

Source code for apis_core.utils.rdf

 # SPDX-License-Identifier: MIT
 
 import logging
+import importlib
+import re
 
 from rdflib import Graph
 from typing import Tuple
@@ -103,14 +105,44 @@ 

Source code for apis_core.utils.rdf

 logger = logging.getLogger(__name__)
 
 
+
+[docs] +def definition_matches_model(definition: str, model: object) -> bool: + if definition.get("superclass", False) and model: + try: + module, cls = definition.get("superclass").rsplit(".", 1) + module = importlib.import_module(module) + parent = getattr(module, cls) + if issubclass(type(model), parent): + return True + except Exception as e: + logger.error("superclass %s led to: %s", definition.get("superclass"), e) + return False
+ + + +
+[docs] +def definition_matches_uri(definition: str, uri: str) -> bool: + if regex := definition.get("regex", False): + logger.info("found regex %s", regex) + pattern = re.compile(regex) + if pattern.fullmatch(uri) is None: + return False + return True
+ + +
[docs] -def get_definition_and_attributes_from_uri(uri: str) -> Tuple[dict, dict]: +def get_definition_and_attributes_from_uri( + uri: str, model: object +) -> Tuple[dict, dict]: """ This function looks for `.toml` files in the `rdfimport` app directories and loads all the files it can parse. For every file that contains a - `filter_sparql` key it tries to use that key on the RDF graph that - represents the data at the given RDF endpoint. It uses the first file that + `superclass` key it checks if it is a superclass of `model`. + It uses the first file that matches to extract attributes from the RDF endpoint and then returns both the parsed file contents and the extracted attributes. The reason we are also returning the parsed file contents is, that you then @@ -127,15 +159,12 @@

Source code for apis_core.utils.rdf

     configs = dict_from_toml_directory("rdfimport")
     matching_definition = None
     for key, definition in configs.items():
-        if definition.get("filter_sparql", False):
-            try:
-                if bool(graph.query(definition["filter_sparql"])):
-                    logger.info("Found %s to match the Uri", key)
-                    matching_definition = definition
-                    matching_definition["filename"] = str(key)
-                    break
-            except Exception as e:
-                logger.error("filter_sparql in %s led to: %s", key, e)
+        if definition_matches_model(definition, model) and definition_matches_uri(
+            definition, uri
+        ):
+            matching_definition = definition
+            matching_definition["filename"] = str(key)
+            break
     model_attributes = dict()
     if matching_definition:
         attributes = matching_definition.get("attributes", [])
diff --git a/_modules/apis_core/utils/test_rdf.html b/_modules/apis_core/utils/test_rdf.html
index a14f06015..565fe7d73 100644
--- a/_modules/apis_core/utils/test_rdf.html
+++ b/_modules/apis_core/utils/test_rdf.html
@@ -97,12 +97,37 @@ 

Source code for apis_core.utils.test_rdf

 from django.test import TestCase
 
 from apis_core.utils import rdf
+from apis_core.apis_entities.abc import E21_Person, E53_Place, E74_Group
 
 # use `curl -H "Accept: application/rdf+xml" -L $URI` to fetch data
 
 testdata = Path(__file__).parent / "testdata"
 
 
+
+[docs] +class Place(E53_Place): + class Meta: + app_label = "test"
+ + + +
+[docs] +class Person(E21_Person): + class Meta: + app_label = "test"
+ + + +
+[docs] +class Institution(E74_Group): + class Meta: + app_label = "test"
+ + +
[docs] class RdfTest(TestCase): @@ -118,19 +143,21 @@

Source code for apis_core.utils.test_rdf

         }
         # https://www.geonames.org/2783029/achensee.html
         uri = str(testdata / "achensee.rdf")
-        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri)
-        self.assertEqual(defintion["model"], "apis_ontology.Place")
+
+        place = Place()
+        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri, place)
         self.assertEqual(achensee, attributes)
[docs] def test_get_definition_from_dict_place_from_dnb(self): - wien = {"name": "Wien", "lat": "048.208199", "lon": "016.371690"} + wien = {"label": "Wien", "latitude": "048.208199", "longitude": "016.371690"} # https://d-nb.info/gnd/4066009-6 uri = str(testdata / "wien.rdf") - defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri) - self.assertEqual(defintion["model"], "apis_ontology.Place") + + place = Place() + defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri, place) self.assertEqual(wien, attributes)
@@ -146,8 +173,9 @@

Source code for apis_core.utils.test_rdf

         }
         # https://d-nb.info/gnd/118833197
         uri = str(testdata / "ramus.rdf")
-        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri)
-        self.assertEqual(defintion["model"], "apis_ontology.Person")
+
+        person = Person()
+        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri, person)
         self.assertEqual(pierre, attributes)
@@ -161,8 +189,11 @@

Source code for apis_core.utils.test_rdf

         }
         # https://d-nb.info/gnd/415006-5
         uri = str(testdata / "ramus_gesellschaft.rdf")
-        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri)
-        self.assertEqual(defintion["model"], "apis_ontology.Institution")
+
+        institution = Institution()
+        defintion, attributes = rdf.get_definition_and_attributes_from_uri(
+            uri, institution
+        )
         self.assertEqual(pierre_ges, attributes)
@@ -178,8 +209,11 @@

Source code for apis_core.utils.test_rdf

         }
         # https://d-nb.info/gnd/35077-1
         uri = str(testdata / "oeaw.rdf")
-        defintion, attributes = rdf.get_definition_and_attributes_from_uri(uri)
-        self.assertEqual(defintion["model"], "apis_ontology.Institution")
+
+        institution = Institution()
+        defintion, attributes = rdf.get_definition_and_attributes_from_uri(
+            uri, institution
+        )
         self.assertEqual(pierre_ges, attributes)
diff --git a/genindex.html b/genindex.html index c0afd420a..3ce54656d 100644 --- a/genindex.html +++ b/genindex.html @@ -1038,9 +1038,17 @@

D

  • date_conversion() (in module apis_core.default_settings.NER_settings)
  • date_of_birth (apis_core.apis_entities.abc.E21_Person attribute) + +
  • date_of_death (apis_core.apis_entities.abc.E21_Person attribute) + +
  • DateParserTest (class in apis_core.utils.test_DateParser)
  • decide_score_stanbol() (in module apis_core.utils.stanbolQueries) @@ -1097,6 +1105,10 @@

    D

  • (apis_core.utils.autocomplete.TypeSenseAutocompleteAdapter method)
  • +
  • definition_matches_model() (in module apis_core.utils.rdf) +
  • +
  • definition_matches_uri() (in module apis_core.utils.rdf) +
  • Delete (class in apis_core.generic.views)
  • delete() (apis_core.generic.views.Delete method) @@ -1281,10 +1293,10 @@

    F

  • filterset_class (apis_core.apis_metainfo.views.UriListView attribute)
  • - - + @@ -2086,6 +2132,12 @@

    O

  • (apis_core.collections.models.SkosCollection attribute)
  • (apis_core.collections.models.SkosCollectionContentObject attribute) +
  • +
  • (apis_core.utils.test_rdf.Institution attribute) +
  • +
  • (apis_core.utils.test_rdf.Person attribute) +
  • +
  • (apis_core.utils.test_rdf.Place attribute)
  • @@ -2158,11 +2210,23 @@

    P

  • (apis_core.generic.views.Update attribute)
  • - - +
  • surname (apis_core.apis_entities.abc.E21_Person attribute) + +
  • diff --git a/modules/apis_core.html b/modules/apis_core.html index cb07aa983..2075629d3 100644 --- a/modules/apis_core.html +++ b/modules/apis_core.html @@ -1134,6 +1134,8 @@

    Subpackagesapis_core.utils.rdf module @@ -1185,6 +1187,37 @@

    Subpackagesapis_core.utils.test_rdf module
      +
    • Institution +
    • +
    • Person +
    • +
    • Place +
    • RdfTest
      • RdfTest.test_get_definition_from_dict_institution_from_dnb()
      • RdfTest.test_get_definition_from_dict_institution_from_dnb2()
      • diff --git a/modules/apis_core.utils.html b/modules/apis_core.utils.html index 1bc107549..82a7203da 100644 --- a/modules/apis_core.utils.html +++ b/modules/apis_core.utils.html @@ -523,13 +523,23 @@

        Submodules

        apis_core.utils.rdf module

        +
        +
        +apis_core.utils.rdf.definition_matches_model(definition: str, model: object) bool[source]
        +
        + +
        +
        +apis_core.utils.rdf.definition_matches_uri(definition: str, uri: str) bool[source]
        +
        +
        -apis_core.utils.rdf.get_definition_and_attributes_from_uri(uri: str) Tuple[dict, dict][source]
        +apis_core.utils.rdf.get_definition_and_attributes_from_uri(uri: str, model: object) Tuple[dict, dict][source]

        This function looks for .toml files in the rdfimport app directories and loads all the files it can parse. For every file that contains a -filter_sparql key it tries to use that key on the RDF graph that -represents the data at the given RDF endpoint. It uses the first file that +superclass key it checks if it is a superclass of model. +It uses the first file that matches to extract attributes from the RDF endpoint and then returns both the parsed file contents and the extracted attributes. The reason we are also returning the parsed file contents is, that you then @@ -684,6 +694,166 @@

        Submodules

        apis_core.utils.test_rdf module

        +
        +
        +class apis_core.utils.test_rdf.Institution(id, label)[source]
        +

        Bases: E74_Group

        +
        +
        +exception DoesNotExist
        +

        Bases: ObjectDoesNotExist

        +
        + +
        +
        +exception MultipleObjectsReturned
        +

        Bases: MultipleObjectsReturned

        +
        + +
        +
        +id
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +label
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +objects = <django.db.models.manager.Manager object>
        +
        + +
        + +
        +
        +class apis_core.utils.test_rdf.Person(id, label, forename, surname, gender, date_of_birth, date_of_death)[source]
        +

        Bases: E21_Person

        +
        +
        +exception DoesNotExist
        +

        Bases: ObjectDoesNotExist

        +
        + +
        +
        +exception MultipleObjectsReturned
        +

        Bases: MultipleObjectsReturned

        +
        + +
        +
        +date_of_birth
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +date_of_death
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +forename
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +gender
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +id
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +label
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +objects = <django.db.models.manager.Manager object>
        +
        + +
        +
        +surname
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        + +
        +
        +class apis_core.utils.test_rdf.Place(id, label, longitude, latitude)[source]
        +

        Bases: E53_Place

        +
        +
        +exception DoesNotExist
        +

        Bases: ObjectDoesNotExist

        +
        + +
        +
        +exception MultipleObjectsReturned
        +

        Bases: MultipleObjectsReturned

        +
        + +
        +
        +id
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +label
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +latitude
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +longitude
        +

        A wrapper for a deferred-loading field. When the value is read from this +object the first time, the query is executed.

        +
        + +
        +
        +objects = <django.db.models.manager.Manager object>
        +
        + +
        +
        class apis_core.utils.test_rdf.RdfTest(methodName='runTest')[source]
        diff --git a/objects.inv b/objects.inv index 92b2f1b301204fbd04a5f44d6ed699f98a1b3af0..014e36e2b6ff9ddec80b14d89b0861aa8972f37f 100644 GIT binary patch delta 6897 zcmVi<9>a7P44!KygnCA}`*{(RtRlhNz{xYqxu z{++bnMvwI&`&a$(yMMcWch!F*{uP%rPiaZZ3PvURM(e*;Jb%A8A4t_CBUN?%)0C)( zTD0Tkfz(oz)OE?WO-*5h4>V;YuHOoZAg;er8BFU(o_eWrk&Lyaqcx)U;?uv7iayYi zk&OLgY4J?TMPF%6Ff^kbqgh%lFPmw3lBO|BR~62bY(};;Ls4!@wodn^scL?>BYT#U zn(=&9p&g4wdw*KTh5nPKqGD79&nvnv#kYCQ>Ng;B!jc$MbDZUfAGuN4p z5Zt95>il|r@mW=uq8#UI<~mp^jhye|Ei3ECP_R@ODmqL|RA?@0EjDN0O)W4f-9Q!- zQZ*%Bj(`2vpW?fWBow~tw8}^|b0CjgF*_Dwd0Pzeag$PP4A1w7od5pM_><@r3;2R> zm67afAdhTU9Sg~>JeIK*cJG7b9X%V{@XhUK*m9YoxGf+ppglACf|p}TJ^Dmz{VJdI zpb6nD9GqQUgg>=jVn=E5cBzLBK|lDu-8x!5>sGxpM@C#h`(-lzGHh}TRPE?a3)_( z&3_YqC^A~pKjCDRO)y&#x;cGseFNOqxiQta;Lz^S!nHjEy6GUl(fWm#Pi>jn=|Ft- zc{oC!Hb}vpS#J&W!ehAT;r7eN`1@b}8h_vXa(f>?{1E?q*xch*rZY)V3D9Zzez~N8 z7ks}V0p5sc>F5iq>Ia&Mas1DqHgqbl4}Tm;NVj1C$N4IERS@69)(`d~UqvS`_g%1G zsO6x(7-HdsD3<_5a)HwLjisW^`pRBej`&6v1@)yiWRGZNy*k!QWOApG)>!R*D<8c# zOP_lD_ECNDpt~->Oq;{Oukt!FBl?nDc$A03XE`I}!PKHq9 zuY$of4Qxtxq{-^Gp@p0+^=Eo;#>SR@B%SE|T*#KcO!SFz=>U2OQdIM}q@t^;#PbL} z$65I1ksfHccU8EMFKirpZ__;uP`0DA3%u?((H?|*-61XD>&E;3fGTEF)N-O5p2-~) z7NVi(prP8pNuctUGqWQH7k^f)6sVQJ_7LPn_|L4mARl}lGfnZwfZJp-+J= zgU8SDvx#VJ-YGBveqw@EfQ^`f1T=(Sc|x`o7Le_8g0z-glWj&ZrH|$^8~Gtg_N}@_z^s)$~xv?ZO!4 zWOPldIu;+{R5v3oeM~mu-#4TJu4p3wVe$2kLjLuE=(+}I1(Jujjb`abceFgPO7%_Z zU?5%w&H&}%07T=Pqqpm$m!a=iDW1CA+h2|xrW&2$cdNxH?~hY~#e4=&xO2w6;dw2- zk@HsZ&u3cJz{9&)T7Oj1J$(f)n0ao^6BvB5is$&Oebd@=CEbt$)JP^6 z@E#XaprUI!VxktX#K`aO4J#@9X2dWF>YNT@UMhWcHVDbfcoO$4=87q1>1mcaXCiCa zt7_p%7@~T^rx#N0t1v(eXZ$Hv)g%e6Dx^m02zc3=hdd>kC4Y*%-tE{cGWbW4;__|+{|@d$Mt&LgS|Fjo1? z$K9a}hpMzR0h`;;a^pQf4j+-6BSnKkeWFN%f^kf#z^aKwrItwLU(Ue|{1SeoAGN1g zq~9`{<)30bx_?P`kmaWXUN%NM`cX3Jt^Bb&xe8V~qXgI0c?eA8Q>`#@5&8CypDbq=>qZ#%?fCn?2TJ1R{A=R2w(2H`( z#(pP%FRG=aWxi8OX&xR^3lLV2G`z}l1PN1A$IwKcx_@`|X8_;@vyx*liov+=4SM$x zX1}CAt_Ny_C+g7$POERczI_!vtVGe*gcj$|T(7~kAvV6NHM8oT)=in~^(16+()}}b zk7T(9n9?dKS<#juGHqA#L!545tMe&lFGK5AG08IiLel{6#X>+(W=^LVy--{QAes9` ze6|&!aDQ=-5-XZ*#;V64CRmYYic+sW=Z~~xHOUj%S5dal|A)R|wD+SR*=N3xvcFA) zsI3KF;+oAcEiwULN&dFs-*fuGw3xia&x8rES|_)|9fKG8J?HiJZpR4vC42QCJ-dYq z+_PKc)ZX_-EHA{q$bw+@MHU9RFFGZ~tgn9osDF0Q6=W1z|6ns{w$uA}`ov0z*E&MR zLCn8a_g%>$daQ=D!g?-1t(qWMG5^M#jQr5kd+0t!$Oh?LBqwmMB9Uro1f@Pp^^_k7 z%MnX_QAC-=ApbBji&1{DV-{n4Vz(@Y_=Zhc40@qkkMl{3FnHa9RL?j`X#hf8GrVz^ z#D5?UxFn)XcfzvrCsc;$y)26gfQfMp3~lr##mxdcQrgA*mF#JW)W;ij{28%e{Y~n- z1;Sk;?|Nw2o#7c(#Ctoyj#;qKg@4h@jKbize4U6`yTER6oQ>fjTg|aHVvZH0U`J@! zB|C)-Q?!$G{Ic~8M*u*h%y|ZB7c>gyT`kCTkh`hzEE0bldGG%dzKt&!<*L_ zAd;mDZ&RZvB>Np@;aKe`$}=Rua@vA80N$|n5_n#wv>XklI3Fu+C3HNlAtF|vvkv{T zh8pZ`AJqxS7TY?*fP(zo`TZMT%g@=@hkwyqL?bTTu+y4pnEK{u5g-^B{Jfg0BY(HI zO*s4fE>o6`uRi1kYGX&*{=TWpQ8*oANBKY<@9JBu9p(+9BL`4a))EKIMKtkA_N(|4 zt9)90{4(lDf%48e?uwIn79dD-aKdx;RbL4URAADR1<%x5_5K8%SMgzQYtxX=lV2-Gyv#5 zmopr-PJm}-6Hv@k_~*HDC=e8xnX@>+t>6SLcu9JNjoNmc9yzVAp6UO9mFo zY5$Yncr>qt&<2lFP=*s{P=66#`*x1%vsM8H^^0)=fko~|C#V7Xt_iYS`jo$p>-M}4 zc%`49stA&9EAkLXc{*P_CY@$jzE`0uwTl+Zi>AJKDzxKR&}69gUUoKev=WMpHAI@g zxgN^*VY#0GS z!UF8lC=F03m^clqQ?oo$0~R_&;on(qJ$Jcdu8NGX|p{13D=O?F5krWarbFk16_38H5}00>2M7bZI*LObjiPqxLA&!yb>*7t6^3v*#y_ghau&YXMegz4R(~<#Ft36;BOh`W1XNtk z#GS}lkProhIm|GGCV*M%jeq>?l0E|cJ{JyZ$HYK|?bP&DEpQjLZnEbcffvxab-Flt zB>5hZ)UUpTLGdyU@NyneG8XKm8TBaP#oO?lHtbut$KzP=62`l|T^Vx?%i*8?Ap+x6 z%OnT!aO^Y_?SH7nG*ZWZ*uI5=E(oZxnyKFrXHat$2;)75Z(|{Y5l{mFR_mPOz z+X5g~?qdpm)jlC&SL_p@XMmiQ$S{>!&JNp1E@u6pJ0S+#dm7Ktn(RIMQ2Uo29jg5+ z^_})MzAQX40n)pBSbZ}2G%YiUsL*_TTmzvjkaiA*vVT;&JwMj&k2$*6`xyHqM+V<% zz)#_6Tz8p0`JKGNS6~g1b72LDTW_P4l>VNrxk9&|JX7NKQ!Y-G3smRbDVK>xzw(4^ zoxCVd03OPVoF`WS-FzlJ5Kd<^{R_d&j31tO<$9r>ccPEfJ=eNd;})-WuO=stEK5r| z=imdz5r5+uDw?V)cJOmw4;9_IGHqTW82SlKhM);TVrc+KMz%DIGm`HEz>wOaV~q3S z{%VrGOfMKH4q_6O_|x|WPGG1Rshcx?K~i8sPrF?A28s&Y2@Tm7>yd`ZNCq8#z;d>4 znhjC6j^TPf>Uu>74&CjFGCcMLGCn-f<9ZYfFn?@c=&_tM^)tsw!RcDgvMDPx+HN=c zF)!cm9aitorwcwo@%GKw-G!4iXzi-YC#dxx=*Dn=pyd2f3hj?#(?LwxVPVyOUBhKb zb)l+t6OL}}?>bWk8N8l@=oORuczRff;Eu(%3fIxZVbnO07Z3z1SqU_QCmpWET0_|9zTAj%{zJY+wFa5O!QE1 zKT+OZOxK~OEn%Fz5~(j%owo@*crLASM@ik36g?5j2}4u`=?`oc2F`Z+E=052K4UlL zsWax%K0&siTJ5Ig*ESB&w1o|HCj<)?yMGKmJd)Td>ia&NViNl#bRg>yf_OQ@E8_4P zj9Y-=6$Dllq)r}Zs^xNkNBo9$kp3#U-V+)HhHYD}F_0Yy&S7KS>#`N3&a^s?5f8nR zd!p@dEDiwR7>#u$$uC~xN|=AV#FfA{3(p3T@?-QDUF=9w^YU%BQ+LsK2zoep)_TSoePD0qoxioLo=L%j+S z;TDWV3sA8vS|G!-h?rKV1~ynm*NLOq+>n55ay=}7#r3d22G;{o?5$5ccz3R)^%z}O zo0~_@68f<{gb8MKXGu}bex`EeuNmTwbDCQ%F9FL zj8#mXNPH+cq`dlDt{+vDlmej#o{$8QV3jEqq^S7MfmL#~E8aC()=p5Ny15XoK50Ex)&pUOdu!K0)D4C)%d0LJ02WHFrkJ-`JBgcy=znkA0u8PL z9Y`*BfE^aV*J0dZu&-j2(wVISSVrsD%HV3ArP(I@mp5TN8Oxt&`Z=X}&E%yV&Yh-P z`bSu*|9t2k2}`q{?t=L`x@%x2r75#u$+*|A9ipghlxepz(CRVAY{W>tc#3(Ry~Vt! z*@69ow7UXuiLeS1a0SdMHh+S917N&U-n%6)osdZ*gWbL@lxI1J~AJ|W0@w*_J8D*u=m2_5{7%E zjD-}x;229GbSEAWBkw$&jgjq+kX!SxM&BzU7`?-JWcOj66l;(FNw=FDb+ig%MP*W1 zOmz436)Y>p8NSP@OCcaL64>)t`q0VyrsfG3wnyvrZQ`k9yJ7`5>zrmdg|u2mfIYu7 z0VrwJWOaq2Z!HcJ?SB&?myuXW(F(=oVDK{KK~ot69+WbNoRu{J)delq2Y3NGly)Ls zHR`G;z;>op4`Mln1>Xs3c|CssDpD;LrJW{3W*bqYqBQD3itFI(_JoYbUIxu}F#5+D|W}+{20ov2{g4HpTB^ zdigvX0nu9yt%Ck2Qn-P&7DH-tEpdc4*Qg`ep^P0`HhDh3t3SO&U};3}?>;|l#NYSo z@AIX1J~>BHdVi##j%3J1!MVbl(Kc%U?E#OoyP_jf4vS^e+11o+_Sfo}IgRLpc!xVu zwm191$+hmERE4wESGyaZ$Jy+%SpE>TRVP2I06B&HUw84;8d1{JkMc}P((l)=&GeyU zwBb)Qe=k0JXbL$%(T=gAa_2LFLcLOc4r4z$H({NJ4u4h)Au_99*tvZb!=RjG?rV}B z$sJXXNJm21czIT{+!>r|J zx$x$B7Jr9hqGyp}YFj^}2NtT)0vy56y^0Z$aT{EgY z!SuVr8VQfk2dm8sp}d4mfL9ke#52A}eS;wbSl_b$xOJF_6Xm%D3bJj9@A`EQDE)yU z34oIyk%#_KK?4!=5~^-RVW#2(MHzBdt~DfL41dV^liuI}OK-Hx=qr=UomDq4gcDbb zk2+AhqbHk}eCyso$}JN;X!#Z^Cm8rr?MTK#@c>=*g+RFZSGw6`=UZtXc+pEN#)S$X zr80IFNt;6vtLo|)Z*_^by)OYOUJcxSAf)&0=8PC7ys-LF_9y&B*h+gtjw%I6+JPRn z(tp!~m&V|s#LZ=46-()stj?gEmj{*AAO_y44c#f%B(O|(AW0KCa&E$Mrixp6AJZ!C zJpNXuaZDTL?e(f`uP#Tq-GICTYdsvpD!OHMAIEPbHY7wUqm;ERXUz{;oU)R-xm39V ztg%%4nG8Uz>;BDOWRzpE1NUo&d|L4zWwLyw#cI6)>ZuqUzIUHF{VpEjQp|;61A&O>5g{ zL+6fO_$Bv9T7TVbRV`nUalv#OWU#0m11VC-B^O&Ty3zlKDajF0{e{Eo&dMx@pMP$i z%0@aY*jN2m4U%Ou0R2!u!(Ew6uw%oaWc0257mP7Y2pdal2cvPCZy_`0Jgn^q!VzEC z6LW}>;X;HOcei5E=e!c5sb>CSN5|CJf}YoCEg>wvENcnO<))w|y4?mSytMOGL$2a? zVzLWmDq0K4N{l$Vff$ZDpu>0ZmVcExEEi;Ucj_r62gniu-g2 zGg`I8`YDgK#-~qR$|Ww=cd_PEkP+;<1pFK{1%+ygF7x(tA0o>~Cr>ai>EEf;1 z-~DvG&v;dl@=ZS^KUL-YMRgkv58kyAB^{o<|M1c2F|!fp^6cjJv(sbrP;At0nbXtG zPyWgivTZW)h0D`}GuQI@lz%7k@xeJP`IBpToHS46!rkCE=9t-)eV)(KIli6i`Zr!a zNfEivX@7f_s5nw>Y&%=R{z8k~*dJZVH3hR(X8o-HRR89KTzOEgq*p0@rWr2|@-Sz5ZnWy3*l2WiDIKb{`ti?isZtqc)hSIWa5?l{ckm z?5_I6wK9FWexsQ%1A1L^^-BNu&;PBi#Z!&UFI6)Ca;0L)X4y%Z+Ml;CX+5co;{#8g zXn7^FZX}0_w|DIq`hWLdb+@7O>ZH}N+2hVP*Zs2vNuJ1_o~r0;K-IpR+`2leIe7Gl zaHKQCJ4iS6e8(Ml?W3c-PG^SK9y)M&!S2~nVz1v0Vyl1B!dy-N1l<$CPN1f6V*pH0Z<^>)|iu3(%5x3}P(k{!IVo rL4SWI(>P`*Lvv_vYrJYITE>HJ!v4|exBBNl{UnLY;{9bEa delta 6734 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 kDN0`*noEPO>XH_5Z