From 494fdad26bbd9ba5e0f911156fbe18b7af99b582 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 20 Oct 2023 09:48:04 +0100 Subject: [PATCH 01/64] DP-1833 First draft of the catalogue library (python 3.10) (#1960) * Initial commit of the catalogue library This uses python 3.10 since 3.11 doesn't work yet. * Add a minimal client class This creates the basic hierarchy of service/database/schema/table. To be extended later with optional metadata. * Add a gitignore * Add usage instructions * Lint * Set serviceType = Glue * Update documentation --- lib/datahub-client/.gitignore | 9 + lib/datahub-client/README.md | 46 + .../data_platform_catalogue/__init__.py | 1 + .../data_platform_catalogue/client.py | 161 ++ lib/datahub-client/diagram.png | Bin 0 -> 227322 bytes lib/datahub-client/poetry.lock | 2056 +++++++++++++++++ lib/datahub-client/pyproject.toml | 22 + lib/datahub-client/tests/__init__.py | 0 lib/datahub-client/tests/test_client.py | 196 ++ 9 files changed, 2491 insertions(+) create mode 100644 lib/datahub-client/.gitignore create mode 100644 lib/datahub-client/README.md create mode 100644 lib/datahub-client/data_platform_catalogue/__init__.py create mode 100644 lib/datahub-client/data_platform_catalogue/client.py create mode 100644 lib/datahub-client/diagram.png create mode 100644 lib/datahub-client/poetry.lock create mode 100644 lib/datahub-client/pyproject.toml create mode 100644 lib/datahub-client/tests/__init__.py create mode 100644 lib/datahub-client/tests/test_client.py diff --git a/lib/datahub-client/.gitignore b/lib/datahub-client/.gitignore new file mode 100644 index 00000000..b03b80e2 --- /dev/null +++ b/lib/datahub-client/.gitignore @@ -0,0 +1,9 @@ +.env +coverage/ +venv/ +env/ +.DS_STORE +.vscode +*.code-workspace +dist/ +__pycache__ diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md new file mode 100644 index 00000000..2be4d8b8 --- /dev/null +++ b/lib/datahub-client/README.md @@ -0,0 +1,46 @@ +# Data platform catalogue + +This library is part of the Ministry of Justice data platform. + +It provides functionality to publish object metadata to the OpenMetadata data catalogue +so that data products are discoverable. + +## How to install + +To install the package using `pip`, run: + +```shell +pip install data-platform-catalogue +``` + +## Topology + +- Each internal data platform catalogue is mapped to a database in the + OpenMetadata catalogue +- Each data product is mapped to a schema +- Each table is mapped to a table + +![Topology diagram](./diagram.png) + +## Example usage + +```python +from data_platform_catalogue import CatalogueClient + +client = CatalogueClient( + jwt_token="***", + api_uri="https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api" +) + +assert client.is_healthy() + +service_fqn = client.create_or_update_database_service(name="data_platform") +database_fqn = client.create_or_update_database(name="all_data_products", service_fqn=service_fqn) +schema_fqn = client.create_or_update_database(name="my_data_product", database_fqn=database_fqn) + +table_fqn = client.create_or_update_table( + name="my_table", + schema_fqn=schema_fqn, + column_types={"foo": "string", "bar": "int"} +) +``` diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py new file mode 100644 index 00000000..9df5f2ed --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -0,0 +1 @@ +from .client import CatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client.py b/lib/datahub-client/data_platform_catalogue/client.py new file mode 100644 index 00000000..f8a1ea1c --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client.py @@ -0,0 +1,161 @@ +import json +import logging + +from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest +from metadata.generated.schema.api.data.createDatabaseSchema import ( + CreateDatabaseSchemaRequest, +) +from metadata.generated.schema.api.data.createTable import CreateTableRequest +from metadata.generated.schema.entity.data.table import Column +from metadata.generated.schema.entity.data.table import DataType as OpenMetadataDataType +from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import ( + OpenMetadataConnection, +) +from metadata.generated.schema.entity.services.databaseService import ( + DatabaseServiceType, +) +from metadata.generated.schema.security.client.openMetadataJWTClientConfig import ( + OpenMetadataJWTClientConfig, +) +from metadata.ingestion.ometa.ometa_api import OpenMetadata + +logger = logging.getLogger(__name__) + + +DATA_TYPE_MAPPING = { + "boolean": OpenMetadataDataType.BOOLEAN, + "tinyint": OpenMetadataDataType.TINYINT, + "smallint": OpenMetadataDataType.SMALLINT, + "int": OpenMetadataDataType.INT, + "integer": OpenMetadataDataType.INT, + "bigint": OpenMetadataDataType.BIGINT, + "double": OpenMetadataDataType.DOUBLE, + "float": OpenMetadataDataType.FLOAT, + "decimal": OpenMetadataDataType.DECIMAL, + "char": OpenMetadataDataType.CHAR, + "varchar": OpenMetadataDataType.VARCHAR, + "string": OpenMetadataDataType.STRING, + "date": OpenMetadataDataType.DATE, + "timestamp": OpenMetadataDataType.TIMESTAMP, +} + + +class CatalogueClient: + """ + Client for pushing metadata to the catalogue. + + Tables in the catalogue are arranged into the following hierarchy: + DatabaseService -> Database -> Schema -> Table + """ + + def __init__( + self, + jwt_token, + api_uri: str = "https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api", + ): + self.server_config = OpenMetadataConnection( + hostPort=api_uri, + securityConfig=OpenMetadataJWTClientConfig(jwtToken=jwt_token), + authProvider="openmetadata", + ) + self.metadata = OpenMetadata(self.server_config) + + def is_healthy(self) -> bool: + """ + Ping the catalogue health check and return True if healthy. + """ + return self.metadata.health_check() + + def create_or_update_database_service( + self, name: str = "data-platform", display_name: str = "Data platform" + ) -> str: + """ + Define a database service. + We have one service representing the connection to the data platform's internal + glue catalogue. + + Returns the fully qualified name of the metadata object in the catalogue. + """ + # Directly pass JSON as a workaround because in metadata.create_or_update won't let us pass + # an empty connection config if serviceType = Glue. + # This workaround can be removed when we update to OpenMetadata 1.2.0. + service = { + "name": name, + "displayName": display_name, + "serviceType": DatabaseServiceType.Glue.value, + "connection": {"config": {}}, + } + + logger.info(f"Creating {service}") + + response = self.metadata.client.put( + "/services/databaseServices", data=json.dumps(service) + ) + + return response["fullyQualifiedName"] + + def create_or_update_database(self, name: str, service_fqn: str): + """ + Define a database. + There should be one database per data platform catalogue. + """ + create_db = CreateDatabaseRequest( + name=name, + service=service_fqn, + ) + return self._create_or_update_entity(create_db) + + def create_or_update_schema(self, name: str, database_fqn: str): + """ + Define a database schema. + There should be one schema per data product. + """ + create_schema = CreateDatabaseSchemaRequest(name=name, database=database_fqn) + return self._create_or_update_entity(create_schema) + + def create_or_update_table( + self, name: str, column_types: dict[str, str], schema_fqn: str + ): + """ + Define a table. + There can be many tables per data product. + """ + columns = [ + Column(name=k, dataType=DATA_TYPE_MAPPING[v]) + for k, v in column_types.items() + ] + create_table = CreateTableRequest( + name=name, + databaseSchema=schema_fqn, + columns=columns, + ) + return self._create_or_update_entity(create_table) + + def _create_or_update_entity(self, data) -> str: + logger.info(f"Creating {data.json()}") + response = self.metadata.create_or_update(data=data) + return response.dict()["fullyQualifiedName"] + + def delete_database_service(self, fqn: str): + """ + Delete a database service. + """ + raise NotImplementedError + + def delete_database(self, fqn: str): + """ + Delete a database. + """ + raise NotImplementedError + + def delete_schema(self, fqn: str): + """ + Delete a schema + """ + raise NotImplementedError + + def delete_table(self, fqn: str): + """ + Delete a table. + """ + raise NotImplementedError diff --git a/lib/datahub-client/diagram.png b/lib/datahub-client/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..de5e82758dd7027968521747af7d7c7ebe84b88d GIT binary patch literal 227322 zcmaHT1zc2H+b;|tgET7L-3>#B^1*In394N>T~?(FfcHvU}q{{`=(Z*()U~_qS7<%YE z5C#B?1p^1YgN6RWgj&JC|GtNTp@v>zVBjDjFo@7A7W7Xk3-CX!faNT>|K8{PA{12> z1B0PgRTC#OGdpKXdlxoRG*oDC1+COHT{Pw8_)P3=nT<^Cjm?-nY#n}eff4ZFgWlSj zxfqdp*xJ}R^LYr8|EA!B-v4^cLQeXd#Kl^WTvJ|&RLtJVjFg+1m6?@X=s77VseqHI zIiHHSu(0v+^0Kh9v#_%>K`EG=J?&hKJecg9 zDgI*eKYYZ^oK2jp99*pI?MQ#|H8Qq$brB>d|0U?ZKY!_I=3(`>Bs=H-m<2VEmTu+d@0K2>l(F|8)Lm)C(k1z3JL_#Y1cE1JKbLc=NaT!7`jLqq6!!s{>$7#I;4u(+t22kgENqCb_y z_sirocXJCjix&h&?qFH$04m_KK%lYsd9sh>h=q7&^9tSO@k#GwI3cgtt#kKWh#z&%ZS}(;TKRsQz%T|52~vr>A!@QKVY%9Kge70mKD__>&p_ z&xOz*IPZNn=rqVRwH;^h?%?~2K#`T;b>rke$$otzH-w+35B$69Zwl4`ATDgCNEs7@ zSPKR#JPPc8FTmP~zd!udWC*}y1qcNg>b&`<3cvVZI(&iq*RvLQs6cN(YxUQEF#XMk zV)+f?zn%pkiMIf+;R9{8@cu9l7a-a44ej5yy!hacd5i=Ig`vD7{B?-_Fam?e z5R;a1UN$s{^b-c*%nXO^?Kx4-okP>k@8rm0V?{=-os`V>mK zx@pV?ii(Q=9~um*Wgj1(7b{3}N5 z6o8G5jU8iGclUzf?z0ZywMVwK6~Q0=Smy$~di84O^m|Rs|Bpr)$wOAQKRABk{~^cB z%xqQqI+00-I`@NQ3k|$^f5#ueCndq8Wny{-aj19Qo0ONvjPzFo4kU*C=@KDwk<)|o z7L9M(Tx1fQ03k9~oqxIZ=7R_r9Cj=PjgoDP2SWisd!5a=@Dk~K1t4hPxI^BhWW z0fY`Qbe{dm>LxPG@Li-R+yYy85lP zrDaSIx)6^oKgX6h+Oog{%+F}$sA$ zmO%i3+O*N1g!rlWyh&=Q=KP~Z1G&d(FO75mG7P%wlFyZ=qtD&kl5Y$7*`a&$TzFV% zNo2=ST;}~cZ1{mC@%-+to}$3kV8qeaf8r&n8N-7m0a>EY2bcah z;y0}>ts9mF!AGllCMh_P!izzKV*` zU0i?+*hnn-%podJ504VjobqEu-BrVu;OWDWpD;xa!P2$l&1q6|+KN3fbis&;fI(ts zYEU+ocb)xiJ=NFt9yyLe{7?yaHr4#7PVBR;JkwaceO#fq^b%EgfXXP|XxMays~MxF#wHQ3@b>w6i1^m^sx_L1MyU4+HMj-nquB?IMtT`e>T6h*y6 zT|VoM-5xJez39fIwVhO`S`!IWQPQA}`e@{<+~0v7&h29L?r#OQ%&~ z8U_*EjKtUag@5NXm^eO+G10vVC|G3R4%xa8#l&q z8JeoEa-E+~eIP;`w)rk+1ILX#Zcw=!a)`EIMW*nI!3e zj42`^(S4bcC?b4k1$@nT4x^?gNL+^p;?5NXDiJ)q)JauM~9$cSH1MPh(iw8 zR4~+6$6>dqz&xJoBkJ(YqR?6LF#NV*MgG zKJN@NM(&1o1P#k0CpETx5Hlb4ErLpY4y<9-5CGCL0GCIxL{wzAz^R5!t}s`OU^0(Z zWPJ?7KIfh)To?|OouhUiL{c?;oDP0}JFhPHh$zY|rnfjkUIKexE1mk10qJbnm|mLxXFALEeIk$%t^*(9*&aK?lKj z4$+l*kfO8&K3zvY0X3)MGC~iQ_VlmnmruG$qNT3%qbJdLuJZiNa$(-oS5yz~>(u&n zD*1$$#@IaF5SPa$T-A}yBl(`jW#;BoFopRp34|h)pZiF=aU61>jB^1tx#HVNNm~Zq zNaF9CNS%6%ZGdL){Gdr?jJwt69j^!vj>s`?ONR{~;W#>+4K=oiG^gn$p-~FIKPD~% zKmj=8d52%)-mpd#28@dlyJ8WK1g@HaK2?S2;7!H*A0a)#8ic&{L9#f5U} zg&8pKNVH?9POX>^!hpbqyrdtxhTVqv9NSSrC<*&zhhkpopPrBnuG?U^TSF5T9RQUl zp}F*IPdwNxemDJa1LCHGfdsN0Yq7^MC?czIj>GRaTB610^?p#87dnwwbo6$H5f2Mq z&5Ldjp%&F$&RWAw23X$C4Dt4CfRe%qqTORP%0>Qa$jiTVhwSFXsA z#w~a0-xGe)>#LJvWVr(uzV^ucxdkRuSIH#w^Ob&k;aS)F=_o@svyEi`Di$-uarzC~ z25m%m!SJ3g;+8rbB24 ztQVmyU}$LI%2Cv|Bb+&ZA+R$X=iS=%SlC@TTi5tVR#kVPe`Rb=2UUha$rL5KW-VKx z?jd=Y|JK-z_*r+A70kuUB%7{SqG1UfJU3w8?yC$B*)kEHP_OwpU1jl?~uQc>tNo;=MQ@0=+vfkUqu zp_Gb7a!<71{Te2cyXKo}39&(14s^|>W3*3|f-KEsg|EyS5O1Z3%CGW*2CuVQ8@9GG z{hB~_%noIw73{86^2`sW}h05d|2cgZWavn6cs9AM#%|aqq7}hGNP$q z3n<&Kj4I%+BA=HVMS~hxA}NIx7$g@gk$C!(ZEyLC2pyj&!8Ta1(cQxve9VFot0D;* zx??@qA;qjhS9QZaL#fcc_WfxxI~UGAemx zrg$4kSU8Xf5I-t|uvFT2Zaik&8$*09E!oDl)^3=`k|SDsH8C89yijp!JUoVIRT4V3 zAV8kxWDhdG)BJg&B)X!F`vto2J`|(o4aQw~@Iei=JEQ`WI~;O*19nHoB~y zO+z0W@!`X}rZxDq^+gRKnXbz3?$ZdG>WZt5biw@=-EPC+x*|vqcX$wKjo!oE;#HI3 zMqdJj7d*u>MWF3d7-u59K#}MwB2E&e()%_VEmNt%D41{u0OKd5!X&RLJ$Hohx=6p; zhOXBTQ5Lv-Om&Myuga$LqNm<>hf+Il##=WFKqSS94jn&?JD)$m&^1~V$Tz+*sDtE> zhmbE*mVy>s;)FmKT7J-i-+ZE7ZtTI#b8W+Tt#8ZhR!zNS72@w@TQ-q_ndpd@F^uOf zq>jKC|9rZ&i-^jYQv0OhI0eFzc{dvEyN)`HhrX*=DY{+{Dgrzq0(;XFtlJ#5}glP~$c6S-CUT zrxY5!8$GkxLvMl>x-?xm+BhfKlqr1EHZS64X*ZKNc?j(E@>1lvSI{!I&N81GHoGsx z?-z512?Q;CBGL7Uwrdu>tc?YlK-H-CcGqii>rkTmj^hCig_dN+vHDD#0~qy=?BV4h zqukb4g7(q6Kh|=W*I}P7?Vs|e(q;&H>#47@BwL8E5Vsu}k$8HG?h04CuYs}RGX*At z62wB?MLx{y;RUi*7LYUW0@)r{XyUF;UZ`(T)!;V-AZ-o zQzQtd^=4ej(2UIAHO|EYCpvee1|}Q}M-#snIP5XQ7i4+k#JiPUmHS?X%E1_0VrHzk#IL z$8@x&+heXffKe|vd^SGAkI;l|)CcBXdCb|?pn3(L_~Hby6Kr%^B6(mTt-(N4=4x{s z*2PF}V{qP0x-QLruz^pz2!NBxT=Sow+B|N&dzRwe4OVe2&K1XbsK1|XYQFAdxfDY0 zt+Nlfa@eC+$MbegcrYBRbS~${VY*!{_w6E@`;a>$b}4wDzZS(2OPSOpu)RgGOxSSU z){US&+Q8g!+!nzDJ`my57fMr&S*Kh)?6GG~#kXYM;09zM%iKABDyLl5EnB{$-I3aR z<|yTgj-_d%S9+W0=nC<;riAp+*<}(S!CLUyQ(-z_ znL~ytrNWd1(A^Jo(g$4%Yx>bXB5zASO0UPwyEotI+22<6VYyPuGcRq&=FZYExjWJ& z1SB)Dgh4l`GG3@Zarv3tWK1oR6s&v_sLp*KDS3{J0H<4@sdotw?f3;N`JggJE?Hk?7!M) z7KWr)60XYbP*9Z)$#?v4@Qmzquj7VKNz%;e}VX``dbNp!A{IT0PA;P zZIFs7v>TgS(i8Az-qA?6it#Y*Vp2*(&l`;%f-UI_EuP$3$GTU>)qO$W$*RK1m_f(Q zEV@4vR zB1QI0(?!-W|EK1stEUjgH)HX+iqAPsjbmZr{T=W}xF$y_5TzWq2pm|^3X17GH(ZGJ z5=zOU3SKoUK+NxQ^G0i%TD2hWQ!N`}(^L^5Z~3caFgod{oUw!qEP9IM4DizudN(2F zM&9AdalqQjUhQbmfuwcx`s7Qa=xt)wl_k3M8HQj`}yA{PPR85_+*z;yy zyaCyuiQ<9)6}DixJ(OZXIK-)=SL!Qmc~q*A>@JD)`C9nS+D%2&-7_6Ej*t8$dMASD z2=rogl&{2}xR%&GfPXkxJ3A4>m1jQ2T?x1$ey znw4YMB+ldIoc(am$A=FrHq1ef5gnJYi0{>+{Lkj3SBcu`7PsBHV6rz+U_)W@y%(&y zFtI97nr(imxV;sWS1Sov?zUOG#VqK!Xih)!4KFY1rS{B z$YY1dyij3iecc;j*koZry3J$}9O+difO{|Acz!=BMKX9Sd+OgAng%Ke5UO@prLq&p zbQcn~Q)=~M(@qM;S(4!Ht_J4sz~AnNrKVBl31S|R>&$eo>5t_rc@K;60!iR^-SApP z@Lk)JA_$1lffDclGdUWvB{H_<^z8+BMOyo25PieR>h9CCUSHkm)@szZ3SQAhw4^7o z1v1zJtxKjY*E-`MJ%LlVs*MpL9&RGv1&XR!*Lz) zw%CYhE=mhOg~mIbr3%qK5%~{&g@}6P)7D@jF9HrgjvLMqn3du&Is94%4j!_DP2LF%whx z9O{lmg{~w{mX+iPFH(@buMaUS<|9@sz0uEx7YI{&-K}>`P(m86O?(7>#o++* zScn+%>t|);^j<$hCH$~Kt~HoP2=+j|((?AC`ZciIG%`+3O$b)*HnZMj&sl6iSS(%{?z2^-lW4H73y?h0vVO3F&0MZ>q(UNJI4o|Rp>Vyxk*DzznsuT|5RP(G zFb@uHil&g#k=X+k16++HRn6>y)GA6j2DDNq*)p5BqS%I$*u=ADe>jV(d zk5#ppgG2M0FI$T3RXk>^d8{{3sbbf;o6tEz;})h-_}8v4eB!GLEMCnPjBN#E?8|G2 zVsup_NNTzFR3u4Ds&*#ZL^)EVMq$Eab;Y}bi<#gGNX@v#G}7 zCZ!H`QK7*J%P&Yv2}M9^-Q+pZ@scOZw)>jt$1{BRMQpU9b6Bg!TZIeDfe+%i9o(H` zRggx>HV705mQuSQ#g88=8m)&8SV^7Bm5wbrcW1ECxyjS@4N6s=X1GbJ=@ije0J$4tuEDJk&@r7Vq1%;+G~ z%v}}97N7lAKE$(BzAvvu$=wLBzcOKctsB|uYSU}X>qpvg@P>EPezn5>)s#hmRF@R6n*z25qcz z@cJlC&tp%k5ycNW^o!GtD|y9~v2)O(yRY=Elz>GvC71j^L{jymeL;8X!m|K={SMq0 zL?!?ihj-{zZ!P~I3(J2Q(h<>9H}V8JneyoN~g?FRN#YtTnkMRi6=#_fOD)@ z-F2DevVt>Tg}xF5wI3#^P1UJq$E`T#Cijw6gNOFslDTXr4#bBo<@pf0R>!i`>CnfE zZKPGR${|NNoR%(^;u^l0%1OErp~M`8)ul_QF7M4e`$*+Oxnu+>r2GEC*o!ixH-&66>@Aj%LniwLnR6mw(yYwO4(pGL-1d zAHvpXz2AUdh#HZ;58OW^1Q)zDFRA1`9F6ejr`+eEhnLPRG}w2xqo?*W>1p8K5jdp?B+&Lok~aLc;J-#xsa0kasMZRASE)>BNH6?+yV5jt{!#JjN zN)0w@_+T?dyFw%*b3n1|?XH|aF(kvDUfc1>7Bx(TJ0Orj18ZjR!c&)T)C3Dwv5x^i zX{*jSO}wS@dpmQf?t2>M27V_t^E7`|Qdj5;zz(^`lJ@7`Y~RUkr(;`+_%2k5lz`8#=ARlqnqY` zm;b0m{Hg5}VxPbB?%D0uDWTe^LzD{O6V^+c@htI_bOxODnEh+rd6m%8IFmI=t9fz$ zVzQfEjEf+;mEH-e#+q9CkfWL8ZfX85-TrQeI*d3EFG`CO`&1*I)Kvo3(dV6(6GYi& z(&Woc)l~TCmwgEN6E4e$XW_TtCQH-ENXZv2qOF%DFK0O6WfJ`;i+)4c0=;v+U! zrAHu)x^>r-#X_*+YPW-BUk!x9r74xC!$zr+Jl5S0tdX^*yy-Q<3qdUG3om*cmkXC?D(Dya0uJk;3Vab%*?}{JKn!lOUkfV=d4Y`Y-X{w`kOF8{=Hm?kf`S+Te~gr}DZj90-_mX$j!H5=C#9}nU$w|T=*_9BUS_pCEo z2Y4cgSkP70p372ytE_$Ce6mGamu)32V@5}o?*+``2TvM(f-caiz5Xl@wHcO>4B_f; zR}vb+Zp4(8@wsvLKgCJEHC^-VcZdssXv^g*R$IKidAj3_(%ucY)JR8)E`ONnTkOgEiyOzRT@L#mzEHi- zKFVXhYDeM7^d51=?;Hi+4sqX|uGl!M@OM;|3H$pZ3XqhT@G>`DuSQrB4qQKbqYL7~ zBgDDq3(#x$=C(moO}hwatSE>#JJZKV4+cJT+d@V^d-gxu5tH;-J?uM- zBBh6Qr@!E}6FzL~jGpIW-8I~3C%A-uL+8h-*~QzF1R@4ie&DmS^M58t!MKO_jDIw1 z=PWnf$t=0OxZ^=J+kO^!S@<|H^_wMJQOEjuVHp8-Ph?*71D2stIS6fz*dT3XQvGYP zv>XW_#3S}RB~?eXXC+dq!5ht5tzjjM0HoLEPe&AaAf+<5@1QXIfhHZOP&=A|oO#gm zh*L>DTIlH+)tXVgH183bdT1zm7qijPBE0O5yYMyiM;atkr1K|1X13F>*n;5&=A`2L z1?3RT7o7NYasXlGw~-Ee%ei{^dc5Ab2}p*Hxi!kmc9E2GHZ9U0(u1nT2r?pXpj-a3 zIC9bVg{-?5Ar^I{w!|+N^-L+2nTf|eT|zm~T$-RG9#LuUcoQxGmxWHg4=$V;t}whd zoA&dXvl&I$h>v3E7FeaAy+X`SsHN?P=nC5!hDhKGLvHdlyyYY*4xDh^5OAEGIzz(I^jB$;bH6wxSUb#g5WGGv0Nq`gD5(a6IT zVgAF8G4baDtaG>yQX&w2H(*QzqNdW`AmZ&~)4FR522&_M2_38jHEZt%Q;=+Gx9(5H zVgpJRq0r${V3hSCP zWi8kic(x+Q*fhn>V3pP8aF~P)Yr#8xSjPjFlHT``7~&Vkau_(IOQwG6nK0T^bPwDd z%8B|QCMC4YobzdCv;By4z9+=`5x3lL?_xG9lTJXQA(-2`#J{bF8|C zXL_%7$1ay56afJB_x{ruTOKJV)`gs=rmfc}4u>gY)(~Yhr>v$}X%F0t^oZf|oJ(-4 z9&txvdf|^tqM6v}grH)uztzImo14Cv^=@|&^7M5`PrNKT@-RS?3CxIo67W0(bW@(_ z&+$SAkr&rxBb;P!Z?ADFu++buic2o&f;MBzp=9Qnp>lpH5B+Fg;-ux`dr&*37xe_5 zjF_`4*vj|@TM@>$t4e8N&F_Q> zrB0*XC({!;Q3~u>!h7@d9EKWor}#SuEB3Z2MUDB}P?Fz{);n+Cco6_)s=d*|MH*K0 zybf{Ijp`2T_nbbit)6~TV>Y1Jp;)G@8Yv=v(=-B>-QS+RF%;b^8>0Z9f)Rbh6Di!W z_GzEB3vQj6JF8_Lg9n{^5v`G)fp04};`Rjh4-?!1ckH-2b(_`?UCQC6Q)O1n%*a7nTmfmo=cM$r$`=KcTo7F(9z^|_XRGVa;_nrn zb!%Bd%!{{_1g$8eI1!P#LU zU~r?{CoP=Av;QRR?-P}7F;*y)@=WT7a#Ah;GqT-TroUVX?D}PfuHy*m1?XOtYLQx} zhiagZwSeBPk!m+jYLM+ASzZUTUSgPv+-^O;u=~2Rasm6{hl4K$54#;ZMmWO0>vx+~ zPRyum;9A9K;SBS0d}KC`al=(Jk{vEKB2>}Vd{x-y8eKw01gjh(pX$#;VN8q~rt-cG zKpwY;fkOV0hMC-lleVb%aaN*FO9OnJM}2EiFJZh-!VMyD$kx0X79}JlbB=M50f!X@ z#k~&iDJ;@m-(-ulfOrm3od`>)SIW^ur57xT4vJFw;wy z^I>#+g8#6VB5PJv;K*i5)AC@VJ35ihO93E#QD&3-=?2rUn7ZQVazl}5TUW38GWM-p zT&pt6Vo{9uCPyYX4Hj>xvL*ZD&~#b)IwSDw82q$zhni9d-0CB)TP=Lr&+j(ybO*~X zNx&e2{n#k1awDl0E1{)OMI>6+37+&d!Z$_l+MUi1oxfg#kr9(=F249U-h6K93k0z! z@H?NFs5wO~P;#_EvYbaST*2`3??3C?Lx$K03^f}ZL_-BsruHlPnUSxDRR$JcZ zcJl6@f5MD9F+7_^=;d+3|5WcZ9u&2O+34n#Yd!h3SDGRfkw9#B7?mqVyUE+LrBgfp zKBC6NC)b71%1HCPt|)V$zAZT+Ha@bFR+lhQo+@;S0Gvr&&<)xMDKm$qW^75yQ9eib4c8kl2JFrpM#~^HlE!gjx=uCkqK@< z2u)p}F*DMi1Lpm6n^Nz$q23m0t^`s3G}^S92+s{8>8u;Gf)iENbP*$RzJ2l5uWn8= z8{igMMrlpZ(C1zEC%tVLbsmCOqIypsueLyuXmeMNsSUHIvy(*#UuRG0C8y#lCl!%a z_VlqUGNT)ZS1NmBf{r&fPOUw6xi;~ee-Rv|b4ZJjg#V>!-8*7G%ZTv~qJu!G8a!fo zSS$LN{IptY3Sd#ndQK7BETz0t4C%J{%c{Ep5_h*8Po|Wnz<5f!*-zKPCmkT zLfm7mH!&;W@9#tw;h*J9lD4rLimDx}${u#J7O*Rsiur&HS`qF3CY zIf)^E=T<pvXlM$Fo%6-?*}sMqT=K> z-9C}HA|xO*#KPS3jmi8)2l%hr-Ho`Cn)p}Sxa{6Z&d2b0O)!cWU~$& z$>&2%KJ0G9b;4^f43Wx`iH~IiZ3j$zLh;K)MBtAhwV1!1?iR}Dr?Qyu5ko)Z6=h`I zqn%gx5?I(4S6S0ym!12YD-<+MY5{NfO4+TZUOVV=C-!NOwcq&@NlXh+n+fA9R#EwmkQmj#-=UI#7G8t! zPDngwhgV(1nQVFfA(r3xj!-?JOEt1RX-oQ{FkAdPC13H^$Qll$YYz&WKNzyY0$XMw zEg$?rG`Ip7jOMQx-%fp3(p3adz!zs8gu@`y0&?Jj>|#e$vxm5ZbftnjQ;TbaXqDCh z{N_?6r+4wz;n&_SFFwDshZktx3rZOJht&aT7%gsR09E&TLqXzzb*#yf)x||?X889A z#S!n+tUj9(LcgOw>=ZO4k~2)8Jxs5eQg{dsA+4SEXh<*0iU74GHvJR1{K6kbF0)u2 z4{JM?PusCAL^$j%Zk=X&wizB8ytFw@P!9c&7`k9QGd*{{Y-aRS80PWG>&Y>$e*lsV z12MCT<`IcKM9M;2%7Cm?b0jxCQo-h=!@OH*9V+M&Ep#{e`qxS;SRB)cpt2-U?Wfe4 zZgu;v9O?W(cH%`5)|M z4j331x*sqH(5`F6Z1Da>(62K9=H(8dSlV0%xYFBOuhx>EYx-)cqIm8fnBe|IXM0Oz zefc6Q-L_NM#(_BmycW;0@%$GvD!{_R!fPxAkaGQ=WXo9vz2@gX(f!l_Si6=r?v><1 zS0pIfe05O!7e?|Q=xfg~5h*FDdR-{kdFSjz015%m*w&u?7fmFLx>6bk;4r2v=bwF{ zRICa>o(FqsVN7aZax}OlERgq48{n{DXSv)@)Jc^ z@ipI>S`he*^B?2*7jFHM1m3Rc#=LGb#riy;r15f=?OoZ>kOJ@H^;+JyZ}QUBo{S6( zaR|uRpWwX*l&XXO3@u8)%U=f)UVotvCGo8lc$_vlUHej+cs3-c@uo!A3V#0W|MG=C zP-!L?iulJTAlP_BB*xhNfD^4@o>=m)pgLqxl&$U*Yy?A*tZw_$pOqL5n&v5&5xw%t zwm^T_fXfeGTe3lmF6QLS4}^rYDg4{oQL>f=&Zn~%nK2y00;*x4 zi{`ZY)JuhDIYP{H)VLpr zJb+^KjBnHEUos*vU=Lkx_ML~#I;d|BgCdVB6rgc-=(~yzK^dM z*1HJoSOFOUQU6*AZ~+wJS!HEP(8zKaH*YN*B_7Px^r!JUNBCQ0nfzvq|9;x? zaAgr`1TVlK;P&%h6g@-%+1cb;4Ibs^B3O@i!uO)Y-p8W6PkAf}KNW=q(?4JybG~%@ z^Q?{`Faj-r6bQA{KK{yY8Lq;5idlZY_)7^SnZu&b-tUZncl85Bhg%yVQ5W`qGBjzF z2u$=gi?8qwAAYxc#>?UR-zfeb3V3a8osCF)uekWc#6GhmJ!JuBX<}d? z;AU2%nf~cA1SIX~SnjP>@e$S{A#|%GA#66t;sThE~gVZ@TSRA0?dLB(OIZOXsKYiE*fb>&?IRPLuo|Os7}@O-({Ohlhzr zzFYkCy0vFf{8ec-cb2c-)6>4|#PhrXzM=gqn+Bl3_qf~`L}yhPR=`fvMXMbxEB1mO za6_UXRx*`$pHqo$E((V4#C&W+%m}Y*7JsO6Kb-T<_+Cj`)!SB{+Z~AW4OkyVL#!vR zLj3$>qrX1wn^as{D9X;&W8-b_CRNls(S2n$cl;S8!N|xM)0X`@cYNqGe zF}-2)d+CI6D!hQfQbt1m@1=nRK-q0t7EfA0*+&niMxyE6B$?Nkz(`;Zy`7LNhRFp* zwBKcP{L~eMnK^`PHrvGy*%HN!zmy%WR@kxMi&KfL1vNn&&5;022Ps>od*29hb3dzX z>Q{&tCr-#-C|@WUz`}O>Yz{l2?;j>?BG&Xp8Q8`=a_NnYj*-DB=@?x$YL=k zO*S*^OnI2c0-joLP<}@zMBE=G0sa$~<(M+NJ=H=3np!qWT?MrmT}=r2&YM+a`{H+` zhawiGF1D6&QR+lv$bk#U3DFe@6DoeK0J>t)8W^zW-A8k)2a7nh;f%njT&desug{RN zZa|C}L+e&~v%Sd2EP|$oBx^NoeI+{fqwk^8YdVS2u;*RhO-Yv`CgC0q(>1;&Cir>C z5YOJ0z047i(jaCsL!hEBXZgk$mqM{;cH7JZbZzc0|M)_ACoQU0pMuHx_u>SN92XZ? z#cAEvx@+mX^m~BH(VpK0!}k#OZ=XdU&%1@+mP4n}-dv^A?`eb~;V&LPpM>R*QQV

TgzvUgZ#^_sfpYasGWS%jT>qw8*4{cA>n#(^qf)V+nl|l$ zB3J({NomYspnmdI1r_N?xNGYsOzsSyzDO%Li$8OkD)msG!n(RNp*n**S$Pr&VOHTP zeM67_4N}4Tiv0o7sUpy*OLYVL3{$27GijJ8os>i}C@`KwzQ<5DQ?A1a2Dk_;Qp$sD;}iK)a@{p*;wKaPs-_0xKIU;% z+zspG-bC3}xOL-u)}Lt=(HF;>siG(C7ICSlgCyv;#--_U7hPG>Da=AA0y?gG5BY0k z?sl=QJWgyd*7)dZoh#49GJn*3*8oU2ARCT~Pv|LOg)E&eSd=?d$iKyrOGqx&p?v@9 zeX&JGVnbyI?P0u0LI3_4cF*phwdrIDp40kCch8DFuYwF3a}w&3E}6WaCc^~Ha7>Do zS&ERnD{oSwKR-G@{;wmgF$KV?nwtKif^_O%N5tMEV{cOnEIqZNug_`d)BOTf&pWiE znuo9|E-m;fGd|x{sodDva-0%Ss5(oqP{LE99@;i}lh)9cMi%1^R7w|1muW?Ye)XV{ z@>mV#_FV&8*)Iekgj1<-f0uKsIZSjl&|y&Rdz)9-Eb@^-Cb!=0BWIsn5#6`=AZAU< zNR|}zKp_d4e2r~mseHNbudsHjLsN6h@|E7Ar&3F}=8dd$8G~&ien!8K9fM0xdR-W@ zOhMNX+OVd$S1#@R;*>Tqxl|u_*G3FqkqcXXKdEpqR)Tl6{f5h@8*p``@~-u`L}_` z`<26q_C9+$PB}$c>r2RUOENEMW)5eGx*t48jTn+QF_YO0q}sUbz1s57jFzHUC%Z#y zrn9uQq(i-Q z_K^UDgpoDoOdW~|HTxp^#C$)S*aMv!FbU=E`ziADbhxovv#M<9%xO^q#wR89CpBG5c>X&6R6IoX+Sc})_pWLt^L9xX%DT z2C;wHqV;0O`)!TQ%>=$)FgToyW6`Nhjj<~PRJc^R^+tBZjWH@sn;aEqXaXTo@5|hD zDSYMs{4ap4FO9O=-23rETiT>M8DR)XRG-aWGLM@Cq5{Y9Z#)?H2oyXS=~nM@N^?rNJCu^8KCZ#1?U){wK`!XZ-UE zkfnE4V#$oP=h8(wLaNnY=SdY0waK#9*@c(&vdCRZSn?Jtn^%j``01ZhVn5e(vrz-weWzTlb{k2p=5;Qh`NR z613j%{5{ip3(hVRQRCR$B-4gy(LkD@s(EIu$b{irg|{&o2h*@;VGGh0^s`~(B0fhN z87%fKeZSwIT4I0>mFan*#Ji|94lA!0vbuq_IK*cINC7WB6^POJ0mCUZ6Egj=Za z6w4&^QK=h9iKa)CB=TQ}Y}VJ38KIl-L(&{w!q6REPCnhjoEw1KxL8alXVAjY)300E z;iG@Jo%csEJ*X^c(#l3NC49t}3v+fPpbU(EHSBkIugML=o0Ls&xx%42s7|LRx*s}znt1y;`QM#{L(L<+!p?C`X(^n; z$5$g9{O?@DzLX@*b&-y;)y(vTQ}CRf!Q>T;={I!H#r1qv>#5iLiSd}B%!UK6-d;Lp zC8ID=!cI7>Et%(q4QbM~0-xmaN+o2K?~mIyNR`*@R(<&<>Te%-wz5$2-Fx>$e;Fxz zj=w6fu?czPg6gavCVPooxciWvHa>zjB_m`Vwsl}G#xvkcMeD-53#jcSKQY}{MM*u- z;gmXUkgMN4Kmz(qs~@>f#|_;cfRY3)M&YZzCVYT0x?p0Lmdp;G0=({Y|9#ANgLx`kR)(H#$x?~^t{IcWYU923U_H3bSU+FsX1ij^^+{JzRy z3F{uCWLDnhsdrGmOmvzwt|ilkJ3Kf`&Y$vV^f#4sHObI%=VbIa>u&sK;h%e2#}`M4 z2m`UY2+1(5I%xmv#BjjC8zDZ%4L(?GWXZR*w(BwlAd|9+y+;~<$Ok*|{@_V4oU*&r z+@wBL?_*@v<#cP8>0rWP)bkmFd(}&DaJ|YygE|u@L^poACE`Rk`kc6GMdO~;e4*~d z9*L2IsK1HV*LsF5yyCLoKvuD4S{-tU?86_cfH&()KlS43aH66ocHKeM=a_e55cN(n@Z;_ch&n&{QCPAo3k-ER0Q`GlkdqQc}6R44n zu#T)NFawORx(pm$5gs4#KUqp^BbX^YgQxoe3A9{}XO(4jc9!6~@go5p8g7!O5OVd0 z@#bZoA1*Y1g2vEG)0k{mknCTc@yc|y>9Q;86fuh7i_pvZJ4t)Q7+A!C{Hq;ma$%-D zJtYPiADzM+dbgE#=f}w>?RZvPk0dlX=;afIuDwkG86ukcuQvaCu;q}?%+8V;^pZ{d z07*X_E@vAgkd5SGf;d0}$HS<65QzAT5SSq{XYCrw;%pZ`5ysZtT_a|6HijDcTT<@yiiV_GjPuD~qQxm$0IJX_NUv06Pr=m|a7ZX}O+KIO@f0+k~zo$xH z8Lx+8msnjzi0*dnlySbLajr$`<#Ov5PO_Yqy7L zxV74XP}qat)ei9=dS5a(O!d>}B)qP7-);vOt$I`(Hzp2mzKs z+WB%)5&AvL7MFATIJ}9rd62Ip7wu~Fn(@(-CV9zPViGx%8mbeQ%6Iz9{N~^92US3_d|c3y5+4~ltYxqZ$>z7sIR7hJnlK}d#;Ci zn{4Lf7;bss+i^RV=Up3e*$E(-Nb-~lDEnE>AOX9zzVoN{&#w!Cm-nhn2gZ@J%XO}^)Dhd}YeLMkQ zmZ1)1LhYO1WvlK`?J4vH{YL&Q4L4~QUkBQTw(QM=KCCIp>ToKaYywA(P#1=VM*t7? zh3CiL22@s^4x$xC!rA~4{Ov(aL>{wedMlrMGMZ2t(IE?EMG6U@q);ya$@cklLSPl4_WKb4NhP^{+8h4MW+Gje44Itc z-kXmCo}kPt4Jb7pgLWnLr2i-t|A?-1Effy z=5|DOniFb?ziw}<2Ua*y3x){v8@J~%n`iYU8HBpIOI$+6k0eb$v3^qPHSWK;rB%gy zAJNmhX{u-$zu;d7Drzwt(r1#XlwgWv@RK}#6v()?Py5Lw6CfJvX<|MsP&^H4UfsW* z>g*MVyN?(}v)>$Wa!3xGiFjUY6CFcJa6SB0P7@G?iIsKj75aW(r-Z7(1jbOCl` zVIoXL$4#AIYaYBQGH=wR>?Ikh%2=)P8Mk|@5BB31r{sSB%1k}{5AV5Twxsd9Mf#+6 z?8%f1x%0F!5jNBhWqMSGY%QYV$$)RU)>>AqPoOvsNkfCo=q2I<+a&)w3v(F{& z@^ejQCO8kIG!BBFmd7shvolPBZ&7q`l!xwlCXL$&w=MDTo=m!6@%2Xn?a-)l9 zo7BI74#d93>E*!_S|OpjwPROHf*q<6>vb?*zvF&0_n0U!rViB9B#YBS>de5>Q6fmy zBbdDqtU(5D`~>T5P%c93D_i-YB;eksD{q&sy|I(P!3?&;58EU z-nCaRix^&8WE_%D)eIP|jY{-__p2z2ATAEBNquY;2zoIpnz=_l=6k!_(6?%=u310{ zdE7!38)d;zMC-iWmE(FTxmk49g?vwlG>%t3eN9}WnBv+14)j~1d8q_X(m z;5TqIs5ieBOaLSR3HT3wbsA@A8JW;(Fr6<~yLsA;9jC1ECRDW=2VtpFQriII$1g%^ zi$Z{jsg@3BA<2}stAvoYA~-4-;V~;;)YG!Y9=%sKHaVKF0E~2)?DHl?iRId6g(;pI zT`WQ^#MR!TDB-Z>I_CnS6MI&EZEDidG?pUCl*#fWx1(_EQ*f#B7RyBdOrPl%Gj;MP zq5oMD65XpQ9IW3UX1u!OaIR0K4$;~QEsSf*NUom}iJWEtx4V8_=MJHo6U9$|QsAO# z98A6Z#OIK|(X%!mH!hp=3Jx^o-tLXSLEuUBu=Z93%Sp-5~gVQv8` zNL88^s)&~<_-SIo7^t_LvWIQ-J(`{?)wC{C9DPQ8oJ_C*Hk>pQllCNom#oFsOW+@(%h0%12Y|7~>T^fZdP12&GqI!^+ z_zzuEsSu2{0uoaQ!5%_g*qBG441AZ?%uk2>mfNc}#`2wj2-&Atn%}He`AJw0gfl`v z5)tS!^DpI$yY`q8c11k59vIW!>Ec4elS`2~24?P!RI>Pgt}dzsz{CH-0h;7?BlB!~ zD;EcraLk|xx@?@~jn{7d;P|ULCN!ZF&xswR-v4>g(=>kn$T1%CAfK+p@%2Ches3*R zV}#qjRNb!jrcOUSCNx*2TGiUEP%**=?F91LxF9O6fB%BQs<^QXEQLstL$)o2j>r}jMXNor8)aKP# zd{7J~RrmLq4>o5-7nRbDqD|*NBxw>bG}5@Lu&%khV4vxHzDlffdY&f39{m zC=4)9vy_${LGfC_PtO<)B*e;>xCi$>2LIsm(ujS#!tN-ta!*xFN*<%3Rv!zsPa{K! z`4%r{*V8-7c>e-*(QuG{7vL896Wwp$MUpOl@540^e}y%T@LHLZ#FHuRftE<5sI%addG zQ@Qt44#p&Ok@QB!(j-SS(Y2b3)ij=igN0MHLn@z5G6}X;v* zSpL<1wP_)xBob$Atv5iX3PW^FnOkmng1sLt9yFLPfp3sz*P?>7D9bEnnLK~Fe%sZy zM6r8TNok+C%UbqxbEFzJ)@3&m!& zxMd*}atF)DkoBxOT!aBWtXM|K0aw#$dQnI|@TygNA;EM-4mx#w#6bHD^o_=H4Zrtb zj^+9?4ZK>|+anAav+Zr8rjkKS9`a52;P>?Ju-~5d5BvNZUq=-n=DFoE9H;tDNp7;w zpOvqlyO_TgSA5TK^hl7_xh%)weM4q9zz&mWby1G`bB9A5vzJM%bHZ{b`!1L29ap3G zzezR|H(F_VdBUatrMDIW$O#0R=CaOP{3aGFi9#sCQ@r>z<`};$ zF{vlP1rib4m_=SPPK^>Wo*gbsjvKc8G0c^i<)Y3vZ`W5G_>GH&9SJ&DaWeF=|56(w zbe669zrm-Wzi`0IW~#2!ud|IVoo2grcyf$dmQb-Y1T1iWTSD+UquIFEe~B3w3oQNjfYwr{S@E3yl*IrVE$RsdRLE z>6*Id>`J{^@|J;!(S!SUr*9l{T5iVNSZZXfi@(Uhe|g|O+>$t!_*2u@@ZCzrzMVg2 za}md>=#LS^{6J?-@H+f` zI5iy;2Eu0s*po_q{VoOQ&TiCqy%T05Iu-1(AQ}jC1v4*93ORM3f^8k`iNB<{o9Wpm zMhX(tl|8S_D_rJOfn7x;Bw->n9rNaTdRimw%m%0XfNqN)Ki1%* zXdEovHwhw|MP27;5YcU50#$y6lUEpunj4AN`kbfk8ku7&P~O!sbmR;wpQdjmOhoI8 z)zessml^+=txAK*Vl`NLqrG zY$EBl79)r}sx^ylc15}LjzZO`_F5752}HOreFJhINGM)*{#00CC1iaj36Tj+g-kGo zf+tmD_N(0^!N5c(?`Q|qk=HRpbOQ`gxdmx(v>CCp&5+X9cQ=0-*KLW6Hm zesgJ^Y4BI5ajCLO?B?fJ4yihctdKVJEy7$KHL=j1;QE?m#3UCf{`0pdD=p6Z7Z?}< zivogbkdR&P`}Y4k35funtziQ1%D)Q>V`U{dwC-z;;UM>o?#K0G&$z$ug>$=Taao@1 zLkr*3J~iiW$gJ;azU1>FrO4f|f4uPSn#vNC;S&R#%VNOwE{88p=N%y8l5($tSk`e9 zL*AYNm2rftFfKMP>?9kqEwJdPV&N$xG=Q?W!P;WwD6zDOU|#{y<+;WGXqLzzQR}%a zW?8Ps;^@w=7h&-qYTI{0-(h!vnNTg!V=oet2aB%>8x@t_vDyv^4x!Zi#^Y_o>?suC zP(sFp?~D-+xCX-QO~NVFy%%}@%Cf`NLr=9A!Fbji>YCJrK?0g=(?}W zTp%r7oGdj;KVdl;G`83XPm`lf8#;evr~Dj0a%+PEPa|-M3vZZP)D8b?0+Npsi35|D zT*6ZntDoKu_tQ*bl)Se-U*~eirb*tr-)_eDPYqr(KtSHNOHbY0fW1ul4hNUGWK9oG zmY;xM?(2;+8L+RDZQ)5HN}5|9pSF7C`c7-3W=?lB`_e(n+r+=*oTY0?r%$N8Os8|0 zy26G;w7_@xVfI>+@Xu$qds)$|O%wo$Lptz#@fKctfF8SWwhm1!Mw)nxE&nA)Re#}a z9D%RNvlhk!gwp-1ZVkcVYDvenbTNbvs{L4S!?m*>ogADou(jZWS>*G4C*1wtm}Nuh z5TiAB`2%x`^iv`SNTvWOm3p+`q+4$?A62b4;Dwn$yFuhM%FSuCH14(~!=*9w;^ zYlymwr0!-S+@p{(CCQdo?-${zx>H+DST7U-AH*N?>ViM+PHp;fbHHV-X75`h5za!e zgx)J*@vV~onj9_<(Uxf{GN?(&Cf5fGO$krXFjM1N@b;r5f9LzlyQsRA{Gw^H`NL!J zdw zMH%XnYzw%cVhpYUMtP_gCnE`>b5SH|w#_#9htgLpwRRXh_mWRX8ndI2@ z3`(W}2{M4HRNfCIQud|#m$-p37mVDn^rI!=gU2BwYZua4MzO<9CWJB;?$$UTl#5Z5 z&F?rfA2A5vO29LgJYwvkD>LV);dV@Gk~3cgpB^9g_ocdD_pwF~3*4O=g&=dg74gV0 z8`YONB=e{B1dr22F#=NG3#icx?G}j@BD(~Bg^Lt0$#&ir*AVne#yDHEq5PzFR23;G zCMa!HEs8-!08PJ8D{6y2|7m++3kIwH1j&t^;RYu;YbON z(l}AS1C9QW3OmK6g-5i&xJ<$*?y@fV*{vnJ#=2a0g#20_&OoDFCQnq&c9*gl^s$c3 zd&cKUd;bb3!N<GO zMKgOPS-&VD3R z=`{D-`82q=xJjYuTdK)5{asW@K8}uNv00FwXU;L)PqDzg~QH@wJC-BS5mrIRAf$()@+XM zsv_yP^^wP7kdMhO@%(l5w*>x2PN{K_-N=U54H(2d)4jgLg|L@>Sr7{(_MIlVFkM>W zZ?SN0ajCgXTA+3tt?p=%8eF3-^Zr0A<92ONnyyju2o{)epU}L{Z6>7eaejE`p#V)P zalk!ezKlK+wrn!~gW#G`Vd(<-A3z?=a*Zx&f%`Ir<0h%zuVHU51B}#J95G3e*$U+V zr}wzij5T&q-X2L=%AAZGgi>4zC}b1yU>;(0y$pQv<(P*^uiPCO59RVIU+S9~c}(02 zg6IuNq1__*(YvPtggLk4eiaCoFNLRZd2PiR z8F*UtF24vs1;=TntYmz4n4n168U0%1;^8OuG|&FeCoTbLKk7;NYJdPOC`BmUB}#IA z;ZdmNw%#qLO}tK%eflUl1D$Pm~0*aN~nv#9xqLku0=DbNW9%Y zCK7iJo?tv7a{OC5>vpxA%Om1%w`I!R#aWQtESO}ccRnXFzhh~oi)hA1TRjwHkB(Qf zyOaSowncCh_U~~ptxNsL*Q8#-ZW&tpO$rr-EE*&LuVjRUP#4l=@@Av9wbZ-{f9v7I zWs6bK|Fj|@G_Jik(SP<_rQ2ZtwqvSv4qH4(=?37D&`6rF0g&_DR8Y9rRhJKFWs%KO zK8*7wRYW)`q9W&-M?OlLv>T{eNKtHRUWmh&fzJ*85~Iu%_T6I73hC<{J77DW8+)W& ztzsWU>8E5PiGM~x{)JB!I0p0!K{IQ0fb?Ry#x4}i<#Q?1yT-1HLxby6uRT4RO`}k@k zO&>6|b1=zt81SY28k)p1KpLr9_=42)nsphUO9ZCIfL)I^0BpeR_!!QBkWTI=NzZpe z#l8@DIh+&jpWWr{Ti)|xnTj^YwkPoy(cXpcqk6!bX+q#HmaFxzCQxp|QfQUsh9qAy z3&qql|M-6IV#{)m`)k03QdZLKEq%eR@N|Wvp8tN%zHG2 zOIpMX2STg!Maj*N+KdVCnIL}@4ygPcSE;Rsp^2Kidk&|}b z``A`F*<{1H7?sF5(HwNq9F*B(ayEg&;-{e>M@Ju#q`mknW=wKeg7!4r#>DwS>6njv zVTirq?kAn2tY7VOv7t#wcpXzLar}_ovPr4o_qyt|$nQQLA5Kha|2ko@Us{kd3i4(f zy48!Qk^B_12mBbbU#G%fw2uRsZbhnJ2LbyL06o&jjaPu@%!>7fC~c+NaSoFXeYo3K##IO%fE%Z`?E3&EcE^4~a@)ZQeE zF~s}?gAAS^nZ>UQ*j+oQnmVO-O6klOD*Qh^XTA;~8llnWCQV|Oi4(a3(kSN`&*G|^ zs(?*Jc&SIv8Hg_v<0SaSOp$tz~H6OHLuc#rdUA2R)BRd^Ie}IBH7b zwban5^{MIgj_BUre#yuvbuPWZ=K<$_yMZ16pVKy8!DnaJ>BMS0A5EC|)?2D{+?#EG z`n9#JT%>|D1`)+DpVokt;lo9=$z(T`I5qpxKDZsGLImt5Cr4mFdhhY!RH7C3WN>4L ze9=_?0nRUmO+nQzlKlm1XhMvf5?WM&H%Wte@Tr76-}Q-WpLXz4I$ezU4-6xg6>*mU^GmRBi% z7;Od0xyLfv|7V|K0wM6fCUtrnQ79d;%=Vr4$0RBk?`BLPr+xNk6n_Y4B9;B22NZkZ zhP^DTwL3=f&PFVmgT43T4LkK^c=0h1@+_4lMK zw5*k!33XNDpE<1zw6#4@>+hVocBCE$vbfwVsg`uoMirDVl&>P#P}idAYKC~j#tINW zhx?ktnD@Zc=1Y|JL?f!OuAQk`p=5PvK9EccNS=x9c&&gIHJrracFPo{;qs31+9;QG>c(c;l~z*Z7c8c;T)=t=QxY#;E8TDPJfhc=CyBBrY|@c_jKT-xqzI zj$auEjK>*n`cU4&JMT9)X`5V|wAI5RPh4&@rsAy_@ULt)=II5WXB9vN@x^{At0qyw z|76bTv)=JY16QBQ3;)}$nI~o&c-0!CKzARB>`))@wh^)TWr>+=Y zEYPE5u3Ilz9-CRrv+lzWMxTw!xiis6)|1^8vkCW(!*plHP!M_9J8FrDPkDxi?2p6W zSla*dZMqru68`YLiXZ6pDZl(uU}5&S%iENYn$p*gJ~kH#RUnoe%CalwY<8f7EwkJ= zk2jp$x83XWf6gIoNLeVeMp*fsr!&;$uDTvmg$6sZ+_xRxs(K@aJ8~L$YT^E#S_q&} zS;@X~2(%5dqL&Kn?B2keWz7pPb(`Z(nSs$iq)S zm>8PqtRvkG0Lw6d)2-T>I24tbEnw0#z)8Odtf-g|~2 zh20Pk$jItH;m4-~VL25~jzzSXxVZMdX;yHzh?q|z5nI!bayq)o{~{!EI?re#ncPg! z>&OHZ0w3Xxt09@B5bg>p#A%k0`)^T`t?x4^8(+R+mKV_LcWUC{;qCp_(5HA5dj)r5UIjybQ50)3PB^mbc;VK1TrVlC zX+eS5bV4NXE+RmnJ?!=^`)k0Y0WZUSW2|Pj4u7p4VO-7D~z949%`!Vk#bukc8t+UP=)l7#2yP)*dyjgm{{nItE@*S86tm_b!W;Uj$ zVTkwhVa@!k6<^V48hLvprcs;D4O3Mik5S#ofElZpj%Uoz{$By9y+q4nFGT^ZO=w4L zC@K;&?=4@X(|?wJjz#eiqNS^w@!iCpm1cqkb2C4x(bk40c}6abK-XuCAPX#fLXXKN z|FG2f!#I5(%(rNR6zm+*=YJPTlE(#1kzDf4Cx2*H{UP2@vuo+&lCknEu`{#TcBR{~ zKSSrmnoJ$isLioq%!oL7>oTJXJ+)a>ynP)$e1oZE_^F4iT(&Q>K41&~633(*PRwhH z?(`Op%l1b33wqRI!-enn=1{gnc^C07W!h@w?FiE7g?h-f5t!0Ysc+vXdLXg!VML-N zfav18f#16jfELM&I|O_k^A>wa@pIlaTx{dg_l*^XIEMxxpie4trE<8YIwCEKO5$8248=iK( z&wDfJZN%QYse2NPP%zx(sJEhhqPh-$E8_M|A|M?vI*2CyC3PxU`7EPt8=tPwObmmT z)}lG$YB@!69AmQnLxRrK?isDTVG3o_Y}Bdaro#fyJh^G>fA;YxZt$~DeYZZS8&D5~ z5C&mBmbKhqnu zEZtCol=}9Ig7Z&VWeATr_fKky6577~)$e#omBxvbY6f|xxIVJHe98I+1IlFO{5al& zS41c96kTO8NY^yS=kvrbB2GfmJ@EkuB?*QJ>LK~sFr#$SG)4H0WsUml-L+PAqWd+>6A#v!{B zGZGajBgEjsS)%QeminuaLjn&-v|d!$K5 zcE1?l4b0^w7Q#s|!m*ok;M2gctz_|>|?1KQI$?S2p4F@bpJjJI|^CtdJ ziCz+aVnWmb*PVK`PW2ZJDC0v0RjOqz3zSPMxvtw}S}uc*%C4&XC!`{*;Eon=m;(74 z@N^sG@?0^=&X=h);g=y+1!ohHf`0=8qQAZ?rqPxT6FBYvVtH+X8K5_YGw=Km^u7es z)NtI7ChjUO0e#Hbb(;5sygBCsqOJ1N@L&nSGv!c5sRHHJDKht7>py?ooaX{Ektjn{ zNk~yi?yT{dhQ6*j0_rKoPtK3O9>2a%t$BJSP-5$XgfyMY4PN&H&=MZ!%eR9yR*NVZ zX|F*mzY=-3k4<*qjT-}1`4Nd}jx93J%EI2NmH&ogFddi%cs1llJo*x7_e$sef=>JK z)M_MFUtq7XR;yv)o;cYJi14htse`kz?w~6v1cQl8^QDq&I!LRpO~Y*T7<*gADXDo; z^E(ui7)H2dcwOBAV}fIl>K`SO@{2)-!YURpd>HN(4-EP_A7MEvp-iD@p#+jtN6603 zSb}n`1B$$+**#Wm<^Z2PQbj6~La&6p%z${d6(eimOiSyc^wj2YwpfzhdvJR3)jr;f z!CuZu($aN`R&)NcDoT!~t?XhP0ts%$xg@ghI!?NoV2c6IIBbKm#9!h}Js}SpJIuvb z0$DwEY*I}J{CKIS$pXT5&e2aN&Dvg@vVd5{J?)z&E*odk@kQs2Ou*o=7Q31GpBb}AMMm~>%dm<=tL7qGro zLx|zCZV&pNl@%8Q?v&+lZ9+{&*eiW?0%nL9wy`OKfxS<)O_G zL)$;VQr?U2=P7vFfiKj48vB*ZcknH(J}%1yt0BkKnJ;5T>$|m&RJ*erz-vhvUlDyz zGTLNbbZz1ru`g3XLZ*iC`hHu)2!(KdxGP47{kZ-{!ub;tkT`25^f8I|TQ09;@SbFP0TU)1z$$+6yN(8+Sy-4Ok&#+NAy-*L%txBhHvy)6B* ziH6acK;8}e1U--pf<$Lsx6lXBd+Nj>%BB12u8@2so0e6Jw3NQwlQwvK#-{*9&FJcj zC@&iyM(wMa#CdxpU2?aad zyJz5C+f6JykiY|d?-=Poxh373EaZ<4a`(?1E#`-`gMK#dJL~F{yYP}v_@!v@l1Q;No-j>keZCK;en8{K7;td0v8Q8f zBJ`B3TVTlrq&m|&Y`3J~3lhEN#;to-yG9?`MXycX+2iEkIrFvxp)z!;% zC#eEJISmh30YAq1`)OlT!CzV`IhDpP6X?iu?Q$iTH!5h|gFRvJ&Az8WGDuT$WaXKu zAzw!%Gwj)^R_%Rn6l1^b(7CQ|+PpH|5}3`J9){;Yr0^8LvxEg~x6EcLTUnr0>sc>d z?R~w1zZ{}wdRZZQXp*{e61j@@nHV7^B83+{ zZy|7t+jiOgT6=n%nSj!9d2(X?OXT+b+}>A4Y#aP)H&3E4PvnF`R2$C-2xgd?n+R4# zV@vvsn*u=>PD@zr#U%(Vs37d27c2_|(x6{w{Gs9GPcN>0+k#eL=DqiBXdJw#^=Nuy3n-yQY6D&?bXY z860iIpc-JyH{Zp4!6jw-HWgHlt7iX^VKXUr;qY}zzGCrc-q=wNfRG5w8jj0 z;8XB4oVE}EznLu%m*V23_qKkNBy^ji2ZS#}(LIvFH**y0Go_YWr=Em!O1T_fF6 zaeYhfOb<34IUJJl;OPtzx|L&+PFKc+myEUWJYo$ZHpB7>wy+*>m4l-ClTPkjcvn38 z^ap}7$$tJQj9*J{jdl-1Zd%Q3fn4<=!IC?l#LQv!YdzFQ3N7{^Y;^5lRJQ zDeOyZ?i=K**4~%_vA^yjizD-a-c^Lty6d7FKPj|VK7g}B>ylro zODXcCB1HYnNEPR1FtXb}g%uF5SPe^yi^VNowLa)A8Dae9HD@NEQfj*IBItFw3?krR zq`0ymVj0v}a8~D1k#M7E1C*}hkO0(9%dHC0med3l7^OO>55k^;)i>9q^?lq594=!U z)O8*EbR9(q#bQaK8@PkYAIFXf{Ok%fZCk0rX6*{Y&5*GJD;Q*80`G2r)=F3-7P_%kf4$uwHOm(zd1P**v)|K;;z; z5Tk5)dL-m8m-pkN?4@fnp>kAu|3bR$GVrx1zf8%l<5}H9KnsxBY!@#a9AWl!WmMJ) zsd)ueP)vn&HMcgBQ-YpTO8tUd8$B$&#g`E`v{-IBt-?#O9QS4EBH=1c7dJ}IS|o8o zM9pr&Gz0%Q{rx+Fw%NPVluGJRg_enP(cMctaGogj!2R^vZf{6SpiD=TQoyn=96ZFE z3}-6Ky%^o$<>MC>bgIF>nR)#$rKFd#A+@el&YzIsADoLK~9R(|KLASj$0{S@kD^U}#VzW;y?*d&I?npXK zEd_vU8}#0f3sU+y%;ZMb9E7nCZ*3O2i7nVY4Ew?3`yl*Qs=G#4cxbXdF1rr}mJ=3} z4y`Tv1DkiA(dAD!Nl;vQZvcfW*n0H-?9clw)UW3FZ^W{U+8PVvIm69Tm3S3sjsVS| z=%m^PGmZ%W$j$9BXVg${6i;Gy>KZf}c-DZ-aWsyAU5b9b8tyS9eS>XN~)MCC?6MVR#nr5i=w)wq4ZmDs;sXL4G3?;>fX zhtkmRyYJzlOO&~`m%jR!E&)`!xty4b+e1xUrmF^wzY%^(O-UMCnl&k^FIG-r(2H27 zmpC2|4_$VFOl?fIvHp>8nBy%E!a7_VzqHTjVfY5Z)Rs-;k$H&Z=gJ5m#a!{`>20>+ z+MQ@?yQEH#(ckTjS6$krg{!6ee>WFMY-XNu^u(<>j_U$kBrHVBjiLE=VjBpEI9a6T zlO97sMwfif<}CPSoO}Kbzk|Vc9qsKu{1I@Kpb!=J@Yx`Z{)FBu7mj|eS^05g4ktI2 zNYV|@gfnWTFlY1lugZW9YFHVZjnngaKUGDHs!r2ywb}OOdD>t9U?QjLn$6r{!0}t4 z{8^CH<|HDKB_l4qc)djQ+H|a15H5+d9J!ok9&3N@EYAh?Ef{Dmq*AQubnTKepU_8v z@9JONvjBiG$7?FfI0;0GMb3^c;Jl6)nuZmcC>l|YjCy6p5M5?~g7eqIZG{=D!U}Ps zPW$U9&lbahSvCU#+}V_r|2bss8*3)nbq0;*RX`!~aoDj2^UcQ2It=BC-C9FK zppYOF!OXg|%D@XZTs6?&#n`tsd&p3aHxU>$X@O@@6WD@r`z9`rOYwY|`ipZ%1Ka1t zm}HZf>ZJx)5Uk?gZsrk5E));c80c2FQK&7LlwD@oz<3cDyrom) z7V&R)s9PK{jF%o^5Yt>aX*fd<#6KN>R$>0Uf^}^V5}(=Quh#hep!93op)_zSCQt&Z zi7z3GpN9?CeJ1cZJ#855<1Qz02zU;Ny@4V-una&rtzelY-g7$<;*l*$<)iKE=3@eW z87JD9C=5enmA4}4X-<@?zAm(fm}Az&<7;^a#&YBgqo7O5cp96RO#DSt%IWJgtkrRrQVGp3hZZ+44C!$_OrxI5Gb!?QNez0abz>EtEN7cn(|Tk*j* zU=^tk7q2DNBruSuHNwgYTcbm48 zF}iuaIjAiO@nVUe5m3}z=o3ezqImd-o=|FBO~ZlkYFnY5@S2h^pX~vyRzbabEZp=o z>ZGJyHqf+$pN>2;@%z^~+;}r(c|;Eu{jzHEKSD5@J;qxiu_Cb)}P`! zpOw=)m*$H-8BYkMVukErYJ?*X3a&*PhRre=fPe#-R#5;UJiAA%Rld!D?S8mP4{&C$ z%tQCsH#24tF@>5jq0#@?CtWrCqe3^H+MM$AZD4Nd#xti^K68y}1X7&Huhu6KeSa_9 zv)8I5m&`;wHlE#PaRkHPn*c$WS8d97P8mZUH3un{y0gXhR6Rc(j&o}iE*xo-A_{)~ zqAFS9Trdu_Op z3RNto9=ER3G<`o~+|R&?ryr|4Hu$+DuKbo9v+UW76)ZDup;0V!PKA3J+dw1+kKe3; zdM5AaHL(NoejOBnE7Bp z_bosMwYI+Gy`dQ)mg4Yg!~=Rmg~$Q9vVbD<6rJUB)NQ$0fV2THnE+Q}c7k;!TXGj4 zW58Hx1w|fBko1)ri5iMRGE6h!TGDvW>NoEA6Oq20ABO+d=G_YuyC;iZuVY~5+Rt!a zi{iaM-@|c#@FrSVn_DGLPTPW3C}Qtd*Q_&uun4WP*(`e1oiFfR!)(p5OI|E7{?yU9 z1^8E}hHggVO1#3qIg=Ol{nYe5KA^Vkj9b>68j_G0Lmh-71K23-$sY$QfnW_Ksf)aI zo^C1}4_)>b-bH$Wf~64n2&C#?liMxoz>uh9qGEgJ4Z$V=okl=o;?tVZotQm3^VX4IUC2+}o+X zNtDBJp{~aH*$KnNKExjX=$xE;mD~y!hTer5ePM1zWji*w3@M}2;S<&^_xYZ2%ILXY zr{4BiGFksrmG)ybD~?k?W-Nuhc62cB&$a&OZa7KwGhA_5{C?BB32rk%W^p$UX{5nX zoLt3Dxl&OV5BoX)J-){bYon|l!{)lwRWQA-=8&*j(*tSm~UM|P)<)M}%1>-LrZR%)8^)0C^5Znd7r1N)Y zSI160L1F>YxXIP4ovDc5NbZ81RZ+%k0JU#o1E7vC+a)i|A=CD4Tt#oXhaA&uCENA3 zlGdGCK|=1^@Fz99xAk5GU3IV9oe6s`iaBia52ACB1iv6)dd52BWLi)LGc7c#nRMgX1Wm~Uza|+@8N1p3Qx1L;hAn612=L!yHl(trY3F^+S z_NBUwZ{)xAP07VPVEi|t(4OiOCtjhdY0Lygg3X^v>G%G$t0;3=)+NZ68fBgY>HzGxf0 zezTHDcr$@f#bRVFG?j_1j7l_iEjh^56Vfl;H#1=GW$4Jy*U%kyi5Or6{;_btxcp?N z>-OW942VAU9o~nY8ZcNTNJ=KiUYAr8@%TAky}Y4QE_7GAT1GgDE(y(Z{&h4F$W_ZSl74A$&?sfX3~r1><{~{H0{hDvz~4 zgi(r=fbBA^(x3E)o!PtKK##oRUsHi;!nvF?6|qtSk+l}$ueB(9vVgde<0ZH3SWNIf zieqbVqS%%^y;#9LIRIk#7;6Dq2?mTe%JC0f|9Qs*t1e-TIS#08?nH#bqc_s}-MZ+P zqTa;g#D^DkD}z;705heyykyO@roMk8lEp>MfAtIb|MB#W|8WM|_x2<+vC)`~8{2AZ zHMY^Hv5ls&t;UVf*tX4y8yge;a-Q=&&zt!JX6F9veXqUNx^$r@6cpGlX64LBljxpe zI&C)v(pgQzW-CAXHNr`}q?rbMV?3)6MvZ^+`;sm8pf`-?MJ)*q;lA(S>}Lo8!+7~ zj2BGjaa|p=Wb`L|g+Dx%|1@@2;fW*?cIr8rr|3C3!;_&Q{lk`?rR)hn&@KLi1}7CJ z-Y$3ir){r8LdElNU$Ja6`9X21GaEVY0ai+dl z5}|Ii8^V>k6A#uFl^X&g$X)k+BASRtHv^9C^)PN!M-H3aFQ47aE#ui_u{2U23!dnuR09QTc4Tmc}*y7 zhGc$0J&lj~yb69#IgjUA0kAf1fLllrU%o4!G^l2fe9Bjx>r(#9=~L!Pr?>?(cHz`??!nAD1KBKBSk zV1Hw_{3V|Ws6I4UfLs(U!~7s?zPsb2z12C>I-ynfNAe{N|kKz0nU!rB5n?ShwB|$dmsn=&;f&%J9^%P#KYt>!d<|X zigrVtg2uO|UueO`@bC$%YxcLa!j85Ycw+QB+Js2oup@b*2y=KTcSy%&OlFw0J3lH6 zJllb5u{1T-ky48M+-j|u8>*UXp$H(or!1_bn8iYYcaO}Mr)@qD5p?|2+{z5cm>UBt zkG|>C#@>yMKdoE{&xxw4;Q0EgVd&|zl(S6C1mY`Mcz=zYEEf(gHyM&+J>?xWc9iQD zIBN1pGJa^&w3I)M%@}pd4b)8O(?sFU^4^#3W2+(Up$+qnx$Y{`Uj|_*lcM?4q-zb% z04wTgEbVVzi4`7LWchPw)hK#}KL^b6By!i|;mw4`ktRGFlTdH0-&qgnlE|7l2Y6@s zFGX*x6cZcSS5X3n8@q|$hQvqG2(@L1(hhoxYtHQEZZZIO?aot)y)R!O1OD${4TAjV zN#8F>M+#zvDl#seC)4T0V9~-}wwDN86(|!_HJdaIyze+YuOn!REF7YdNi{x#+B>-W ziA`I@dD)+~d*#A9%~$JU2ji!Nfw4a4+D#dM2q4wD@A7O{*BwuP%CE+wm2U_$nX-kS z-yDC^5--@!dBgEJ)lmfwgz&SdgyeoFeL9iB7~%bh0hi3N5{ng}y@snB<<7SLlOaIS zyHFyuQ>!Dz5HHBR;iVQ7bPxFJXTbVfbwW^$wMP1EO0o0yciLxlDM}6`HAajHRA_3H z{;F%VTn*abNm7PnB|8-~+VQQrGR4(J4rw#4B{&{P!)eZgic*F5!Tdgf4j$8-D?m#m{vqU(Hz zP*-a0!Yg#{sw(um6dN73s6M=$Nm-{g@>y)6ZxHreRYHw}eZVA?Ob@Lh{2nq-uXONp zt5yuc69Un=ZH-Sqp!l$lV^1MXJqm9>`9wIC;uF>@pO$)5U*iPDYeM8e>U^_XgNux> z0a; zj?~IDc4NO`NYXYA`22T+a4Q-rfXZz4HbBqIxe*$I*KoSFr8M07No55w;O%&pT3daA zLCmO$ul}==EG&dF3rQR0K?Wk7i!7R{T%g$IeulND@Qx3`=Uq(^y8N-VX>vT0RZ$d6 zc$J1mMhn1rVP6;;*@jyb-Ip80IE6Ie&&|J`X`gyQK8y#W$Li&K9~RwsI8q;UtY*Nt z0^7o5=ckMd`*F-VR+)X1G2X)*iX}bY=q&$j&(^$w*#@b#2RlR4msToP@=(iHbn7i~87@|&u*y9{Mz>j?l{lN@0uxCY*PH_Y* zaYAP$?0^K*I(Pz`jQDXN84(oOcL>@cw2G5*-=oyTJ#4^`=KCU3a{~(O%nSR)(h@dYM+~?YslPEP%<3c*fjj-p`+Qfr!V$ z)ZkQnN>6c5N&AXeh$%5-=maz_=hj11kS|1sWeGAnS0`UCCydKNh<39`8Mc`}iZ^To zSv&+LJbpz{P@&KM14bSRujZVk6UC8Zw(0O^eFej(=(`Y`^je zN0#)Z?hRXq!?j<02}(rIFjcNgDbcUt_a~*M(|bG$g-qSnvlXFWB`?UCLi-b`!YXj? zOA{pP?$+V#gW}u-03KZXksNE=RQogU)`190sT`(*ZPv`=el@<2?JJ&azLL^^kMI7b z?UB904>P*Qv1-bUjH)30VIby3S-u5sPU{@$D^iwPTe5T=MBSqC)l6Jo7>WI1Qha77 z%}sv^5@>;>5bn2sSiEKj^<)Z~2zkP*#j=-l}5^kfSXIbrsY;~vWsPyX=W*-BK zY|pTyq@=uwiIc_a2_GB3Ag|IxEDfNq&Ba_B45=8Mj;4DgBen1|~?{EImnb&6pv$@X7!*MF{1&6qq1 zn0aL*vHrId5Y$D!0VA}vtu(VC6x;yPkUiHpCs=ywB`8=RNQ`0eNeSjl|<2kxdLn`HQPlQ7RM0!_9$s-qDd%9a=d=&eE<&mraeScfiYr=)(L(= z)U=ONKg8fDV>o=jdXYfO6mOzjJ`UcT&92Te;_4?-?Ecxn z*tNU@3R{e;k&lo^LhE~-t_0Ft^V*L`Lu?I5Q$JhzTYq5%-7ob?d6?6R@Crs@GhL4k zE+F7*lYB!T5B1vsMYAbv@bV;9B`hD`e3B}0*T?*edllg(~g}yQUX(GR*Du0 zlaCLo=6D~5*2+d`@>i%?o7CPap?0b^0-p*6r%JUiI25bBBNyvLBb6) ziYW#|Cl8>ZT7QMErF1Tjt(xhekTdiMDGmWru)HiUKw=`oWp-GNCEeHK2qM=blQmG_ zEB@iMJouyTj*sVs*0A|UjmY3)M8o4w{y0Lvobj{Z#i%FC8n59O_wxj=(=-{ZFU+TY zU-KF082TG`0aZ4mp!<0tf?l$YZ;oH`GUjfdiZO{cuqx3;vss;6U-tZ8nIU(Nv&^N`8>mmp`6PXMdN5Lxwwx<|ym8do8-)m@k#@*N#+C*!ES_f0ChvO@Yhw8(!D4xu%%U~gXE zPQJi912P0ZTt$Lxl~6L^4x=Q+^YBi{s_>Ws$;q$Gt|Ku_}nuu4g!eG=dcZm~! z--MaaL!dX4nC8XC_Pr60IB`!#`2t|OG=9W4%=S1hM|0wVE{)TUI2g)Zxpt0H8YdKH zJF8{@m)PXyJ+D9hd*x6`1`s0)pk2_o@bkc%uIRgOz|5@*+(rF=*YK5()ky^FpBxFZ zuQ#P5nD^bvhRy#C`XljWX^sGRT&&b}Z96lf#z8`z47V09lh5W-7Z&?CuJyc7Qc_68 zBw$cTbytLJ3XBKLr@{R+(Pr0GRB?LHom0jNMe_|@Bo04IRS zoBd0*&;k>o#*o>|H!rHIu{_mW`Lv@{X#*|6@^%p6C9Ey)E6F&&2J=_|4j=itTG#=N z@p36v4V7`@EX=~@u`4lV;QIu5n)vifF$xxz37PP?DNkJayU{RTL^V_v&pgn|0k)LO z)_tsjy|(W&HDPNvmM6vZH_JYu?EHK#I$ah~Uv~NA-#^m%xpbXaA!I1Q9FEH5UvPdrPVb-|h40M*^P`|R z^N^klhO@9pqs4Yy&@G-@&om3oSI6>)R}i7E(qIkl8K+wo9y*IgJaX8}Ndq#cK=1Hp z$N&ydV$HyB8Q_r`Qwn!cxwsbT;>4cZ06z^^Y;=t)yoHuFre+o` zKVtuHwD4h;R#qmjT{MerEif3vjVJhD29pjiLG@b)UEfvvZ*Yq8;?1lDP6Rou`r2^4SxSeyF99%$%e{A|Bp=i=Z$!L3!C`+H11_iwsj1IYu1Ls(@Dx=AhQ#94PQh z#To+P!GyMBQ#e!uJ`y*3ss~iB@zU`{3M-2~FGAz4t7E(rZde~!C(c|2V*w#xEvDTp zM#ti*(?Xl*FiEMf_P!DXdhBwLj${tO@nFJ{Lk*mO{3*(=^L17=2(un<8xvqdA^5lA zc)|^JK!IdvlbX6|oq)qIcwSxVk(_4c%;GMs6Yr%^fJ(8~ba! z*_zhUG65C+-)rm1PnX0n08bp+S_D9jrjCv+iZ*dH?&1k68`ClJ_u^RAPinida5T$8 zQ*zN3eQ5p4UZN=?PpoI1F#W4iB1&>_cDf{i)?RsGo_T)NMSBcuUooTc@&dL=w7pC2OSL0?IYwTg4Z_m@fJY$vb2JTD@glPgx z;NdyS0Ova5a%+`FxJb^9qI8REBxZ??)uK2=m&EBmG)9CQ3s7C}EDxVWCK;w+#qz)7Xy)Co@<>;J3GZ&WW{*GHh1nAboUW za9h70Teut}jx)~9rtQHnb_Hs^_cBF%luN|(7KJf)Qd}`Nw&{O)uTeS1)s&lA*-zkP z`kW3rSL`r4EOL!h|NDUgfc;D1u(nKD+e+K*rTUrTL*vp{qjXM7gma~0)~5KJ1~8Lz zafRy?G>MiY6u>;Qm3TGX2_$trBh{g~%*HI1o&Vo-!bt9(%;rzt^j*G7SiPL`-$qjX z_Kor#r2M9K8h({r?K1!aeokzLfsyZXhIS0P;Ku2sLle9u^t_p9VkzY^L58NKA5I;a zqaOQ|YM8H6W!zU|4J)t6z++^91oCL`Z}f??q?@FQiJF!}{Y%6=J|i>jEW)>$AAOy| z$?Cen?ZqH3QNt6TVV;1`w+c5Gg^mN$D~Q|8qhBPEmrabcwKz}EbuAe=&p}IT?oL{@;B0eDQZbW1F@aO_-iw9` zF4F}eVUNy9XpEdZKMH2TevPMfsDhxR)6Mf!<>zQ$=P?D8iq--{k&L+ zazI0Ggfj9q{ z4Kc)=3f6^mbQGEwZ8a#8uFd%UG+YLa>wE8wDbQ0!wRH5;_ZXqjhQe$F0D)4s5Z-%2 z^8u`&!6o+4XG_@#0ux^$8IU$VtEudc0U0>ldldCDteSU4I|1;DhXY@OT4Cs|+vfE_ zb?2}qL(vBUjBwykR;{03pr(USxIBv8gu=2@lbGihBB!WjTrjVTI(I}VfDMVIgeRjL zlRULlX@Ure7e{j?#(_+>amr_(#x#%l;(%0VI8|<{Dj&?Z{?*bNjB&fph2HO;Z z>fo}CU0q5o=(U&F(dx&^NTHanT^ba$JO1`9i(s>Bgd46c3m2EC0hw9pFymM zMJU4zqZ|F_ez&r0L%Qucy^n7@DMX74xQ3B&e(dfpJ9JEaWh*1QhI$!Y*T#}wjLnuO z@{iybyBi_RCGmW;-(lY~y`@DyM`f92XB(@`zh#sOPuFij0C3|CJ#prWVC#o84VDGd zN*g_1Zu8mCL?sXonj6dObr}PbSekmiU5Qrcp143g_6%>J1_;cq^(fUw&U{EYuF40} zEcU$~E%#VUqwCIx$9aT7AAskHq1uI+9c^^GSl_XO?(S}Vk_b7LPGdb-mHN0$SdJFPpc zL5*0eSi1H$ykjrN&RxW zN+#Jo_8=^rF}e1N!{Iv=NjXyZpX9DvIs0^IbbE^sckm1)_S#(Zh9FbVZ)^e3y( zi1p<#wFf}{dmS>j10OmZ1UKARzsz_)jyn(dX$CSNkb4~u;ri(7JgQdWe z&~M4mTL|%j_;NCVnT;AVA+ZE*A1n7)sokUQGH;Rqat~#A#`A~&KGAoclxKPeq;bds zjNq#qNLPgQjIdJ75J-dn=LK*h0BArjhm(4Wr!eaN5ostO&Ld1N>6+DOo|Vt511*D& z5MD6I=Jd$~-{T$tfbT^0WYA|U?XQnl)`ev)qS}|e2ccIR*u}E5^J*Iw8O3uhr-z`n z!ejvBXqNSI7tc(Q3}P^x$g+T8vEN>BUaI}G31F7PH7_04I$l$eZ-k;)v1f)2mL$mvHzi-3%LJZJSip&&CMGBTuCMCtgDpkrof0+L{eSW~whg zI#^H)Zd{bWXg3?UAX((#J_u4Dd2S00qb*%LLa1N@yeC z2=!76VCfX^Bh}O*pC%ubnQDbJ8;kU|1fF$S`X+`kI$Y?-uMBA`c#OIlmUS>|P zzk-E2Tgb3wrD5vUjK(^lb&hB`W+|Y;#_|`oT0fCUfr&q(W&ABd`dvrFuXNDE@KMRD zeYtG%;^A!-jF@5Y&2bf9+va3^B5-H=@h@gb-((xY5be0tFXJaR(4B5DG2NCAhCRA* zE1QIZK^*`LXe1LKjXx75>xHRI5|JW&3Gb^2jgrb6DJ~x-2D?M8Q&tUv!mG%XLP>9^ zn}sTdQARF*0~)Snw0~}}Et!T-*;;Lu^t8$1LTe*wydKHSGve»GKj&ZCcb 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

D@v^+wtM%jo|g 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 z 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 -M5?iuf3pgA$xQFJxdW~VqwFO^FLJWYff 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%0DX6c=ToVj)C6+m=ncP)qMgWvRYrnx4ng@qqzMp$+ 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 literal 0 HcmV?d00001 diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock new file mode 100644 index 00000000..bacd1a24 --- /dev/null +++ b/lib/datahub-client/poetry.lock @@ -0,0 +1,2056 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.2" +description = "ANTLR 4.9.2 runtime for Python 3.7" +optional = false +python-versions = "*" +files = [ + {file = "antlr4-python3-runtime-4.9.2.tar.gz", hash = "sha256:31f5abdc7faf16a1a6e9bf2eb31565d004359b821b09944436a34361929ae85a"}, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "avro" +version = "1.11.3" +description = "Avro is a serialization and RPC framework." +optional = false +python-versions = ">=3.6" +files = [ + {file = "avro-1.11.3.tar.gz", hash = "sha256:3393bb5139f9cf0791d205756ce1e39a5b58586af5b153d6a3b5a199610e9d17"}, +] + +[package.extras] +snappy = ["python-snappy"] +zstandard = ["zstandard"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "boto3" +version = "1.28.64" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.28.64-py3-none-any.whl", hash = "sha256:a99150a30c038c73e89662836820a8cce914afab5ea377942a37c484b85f4438"}, + {file = "boto3-1.28.64.tar.gz", hash = "sha256:a5cf93b202568e9d378afdc84be55a6dedf11d30156289fe829e23e6d7dccabb"}, +] + +[package.dependencies] +botocore = ">=1.31.64,<1.32.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.7.0,<0.8.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.31.64" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.31.64-py3-none-any.whl", hash = "sha256:7b709310343a5b430ec9025b2e17c0bac6b16c05f1ac1d9521dece3f10c71bac"}, + {file = "botocore-1.31.64.tar.gz", hash = "sha256:d8eb4b724ac437343359b318d73de0cfae0fecb24095827e56135b0ad6b44caf"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.16.26)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +optional = false +python-versions = "*" +files = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] + +[[package]] +name = "cachetools" +version = "5.3.1" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, + {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, +] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "collate-sqllineage" +version = "1.1.5" +description = "Collate SQL Lineage for Analysis Tool powered by Python and sqlfluff based on sqllineage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "collate-sqllineage-1.1.5.tar.gz", hash = "sha256:bc0d66c92202cd6735c30dcee74a7e57cfd02f4275e5517ccb2664499f52a251"}, + {file = "collate_sqllineage-1.1.5-py3-none-any.whl", hash = "sha256:c48e0a09f3704c10b8cd5954bfa8ccd0fca9e5a43c1d55c1ca1fb1be0482f5ef"}, +] + +[package.dependencies] +networkx = ">=2.4" +sqlfluff = "2.1.4" +sqlparse = "0.4.3" + +[package.extras] +ci = ["bandit", "black", "flake8", "flake8-blind-except", "flake8-builtins", "flake8-import-order", "flake8-logging-format", "mypy", "pytest", "pytest-cov", "tox", "twine", "wheel"] +docs = ["Sphinx (>=3.2.0)", "sphinx-rtd-theme (>=0.5.0)"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "commonregex" +version = "1.5.3" +description = "Find all dates, times, emails, phone numbers, links, emails, ip addresses, prices, bitcoin address, and street addresses in a string." +optional = false +python-versions = "*" +files = [ + {file = "commonregex-1.5.4.tar.gz", hash = "sha256:271530678ad8af53c0e8c233a762597969e846d0029f12215ac178791f3de0a5"}, +] + +[[package]] +name = "croniter" +version = "1.3.15" +description = "croniter provides iteration for datetime object with cron like format" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "croniter-1.3.15-py2.py3-none-any.whl", hash = "sha256:f17f877be1d93b9e3191151584a19d8b367b017ab0febc8c5472b9300da61c4c"}, + {file = "croniter-1.3.15.tar.gz", hash = "sha256:924a38fda88f675ec6835667e1d32ac37ff0d65509c2152729d16ff205e32a65"}, +] + +[package.dependencies] +python-dateutil = "*" + +[[package]] +name = "cryptography" +version = "41.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "diff-cover" +version = "8.0.0" +description = "Run coverage and linting reports on diffs" +optional = false +python-versions = ">=3.8.10,<4.0.0" +files = [ + {file = "diff_cover-8.0.0-py3-none-any.whl", hash = "sha256:330b1a655c53c211ffee0dd23c8fee10a2e3539d26dc2362dd3f7606641b8c32"}, + {file = "diff_cover-8.0.0.tar.gz", hash = "sha256:0995b78a1d5101f5d8922006220c070b18e32a5dbb4a96a073a1941705fe71e7"}, +] + +[package.dependencies] +chardet = ">=3.0.0" +Jinja2 = ">=2.7.1" +pluggy = ">=0.13.1,<2" +Pygments = ">=2.9.0,<3.0.0" + +[package.extras] +toml = ["tomli (>=1.2.1)"] + +[[package]] +name = "dnspython" +version = "2.4.2" +description = "DNS toolkit" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, + {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, +] + +[package.extras] +dnssec = ["cryptography (>=2.6,<42.0)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.23)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[[package]] +name = "ecdsa" +version = "0.18.0" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, + {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + +[[package]] +name = "email-validator" +version = "2.0.0.post2" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, + {file = "email_validator-2.0.0.post2.tar.gz", hash = "sha256:1ff6e86044200c56ae23595695c54e9614f4a9551e0e393614f764860b3d7900"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "google" +version = "3.0.0" +description = "Python bindings to the Google search engine." +optional = false +python-versions = "*" +files = [ + {file = "google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935"}, + {file = "google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe"}, +] + +[package.dependencies] +beautifulsoup4 = "*" + +[[package]] +name = "google-auth" +version = "2.23.3" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.23.3.tar.gz", hash = "sha256:6864247895eea5d13b9c57c9e03abb49cb94ce2dc7c58e91cba3248c7477c9e3"}, + {file = "google_auth-2.23.3-py2.py3-none-any.whl", hash = "sha256:a8f4608e65c244ead9e0538f181a96c6e11199ec114d41f1d7b1bffa96937bda"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "greenlet" +version = "3.0.0" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, + {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, + {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, + {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, + {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, + {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, + {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, + {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, + {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, + {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, + {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, + {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, + {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, + {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, + {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, + {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, + {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, + {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, +] + +[package.extras] +docs = ["Sphinx"] +test = ["objgraph", "psutil"] + +[[package]] +name = "grpcio" +version = "1.59.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.59.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd"}, + {file = "grpcio-1.59.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a"}, + {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0"}, + {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b"}, + {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7"}, + {file = "grpcio-1.59.0-cp310-cp310-win32.whl", hash = "sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211"}, + {file = "grpcio-1.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4"}, + {file = "grpcio-1.59.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09"}, + {file = "grpcio-1.59.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252"}, + {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d"}, + {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9"}, + {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5"}, + {file = "grpcio-1.59.0-cp311-cp311-win32.whl", hash = "sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7"}, + {file = "grpcio-1.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29"}, + {file = "grpcio-1.59.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5"}, + {file = "grpcio-1.59.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66"}, + {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52"}, + {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9"}, + {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10"}, + {file = "grpcio-1.59.0-cp312-cp312-win32.whl", hash = "sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205"}, + {file = "grpcio-1.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88"}, + {file = "grpcio-1.59.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c"}, + {file = "grpcio-1.59.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3"}, + {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a"}, + {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0"}, + {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575"}, + {file = "grpcio-1.59.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897"}, + {file = "grpcio-1.59.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055"}, + {file = "grpcio-1.59.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac"}, + {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce"}, + {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901"}, + {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1"}, + {file = "grpcio-1.59.0-cp38-cp38-win32.whl", hash = "sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4"}, + {file = "grpcio-1.59.0-cp38-cp38-win_amd64.whl", hash = "sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c"}, + {file = "grpcio-1.59.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857"}, + {file = "grpcio-1.59.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996"}, + {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a"}, + {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df"}, + {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0"}, + {file = "grpcio-1.59.0-cp39-cp39-win32.whl", hash = "sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f"}, + {file = "grpcio-1.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81"}, + {file = "grpcio-1.59.0.tar.gz", hash = "sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.59.0)"] + +[[package]] +name = "grpcio-tools" +version = "1.59.0" +description = "Protobuf code generator for gRPC" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-tools-1.59.0.tar.gz", hash = "sha256:aa4018f2d8662ac4d9830445d3d253a11b3e096e8afe20865547137aa1160e93"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:882b809b42b5464bee55288f4e60837297f9618e53e69ae3eea6d61b05ce48fa"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:4499d4bc5aa9c7b645018d8b0db4bebd663d427aabcd7bee7777046cb1bcbca7"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f381ae3ad6a5eb27aad8d810438937d8228977067c54e0bd456fce7e11fdbf3d"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1c684c0d9226d04cadafced620a46ab38c346d0780eaac7448da96bf12066a3"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40cbf712769242c2ba237745285ef789114d7fcfe8865fc4817d87f20015e99a"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1df755951f204e65bf9232a9cac5afe7d6b8e4c87ac084d3ecd738fdc7aa4174"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de156c18b0c638aaee3be6ad650c8ba7dec94ed4bac26403aec3dce95ffe9407"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-win32.whl", hash = "sha256:9af7e138baa9b2895cf1f3eb718ac96fc5ae2f8e31fca405e21e0e5cd1643c52"}, + {file = "grpcio_tools-1.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:f14a6e4f700dfd30ff8f0e6695f944affc16ae5a1e738666b3fae4e44b65637e"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:db030140d0da2368319e2f23655df3baec278c7e0078ecbe051eaf609a69382c"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:eeed386971bb8afc3ec45593df6a1154d680d87be1209ef8e782e44f85f47e64"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:962d1a3067129152cee3e172213486cb218a6bad703836991f46f216caefcf00"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26eb2eebf150a33ebf088e67c1acf37eb2ac4133d9bfccbaa011ad2148c08b42"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2d6da553980c590487f2e7fd3ec9c1ad8805ff2ec77977b92faa7e3ca14e1f"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:335e2f355a0c544a88854e2c053aff8a3f398b84a263a96fa19d063ca1fe513a"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:204e08f807b1d83f5f0efea30c4e680afe26a43dec8ba614a45fa698a7ef0a19"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-win32.whl", hash = "sha256:05bf7b3ed01c8a562bb7e840f864c58acedbd6924eb616367c0bd0a760bdf483"}, + {file = "grpcio_tools-1.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:df85096fcac7cea8aa5bd84b7a39c4cdbf556b93669bb4772eb96aacd3222a4e"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:240a7a3c2c54f77f1f66085a635bca72003d02f56a670e7db19aec531eda8f78"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:6119f62c462d119c63227b9534210f0f13506a888151b9bf586f71e7edf5088b"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:387662bee8e4c0b52cc0f61eaaca0ca583f5b227103f685b76083a3590a71a3e"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0da5861ee276ca68493b217daef358960e8527cc63c7cb292ca1c9c54939af"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f0806de1161c7f248e4c183633ee7a58dfe45c2b77ddf0136e2e7ad0650b1b"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c683be38a9bf4024c223929b4cd2f0a0858c94e9dc8b36d7eaa5a48ce9323a6f"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-win32.whl", hash = "sha256:2ee960904dde12a7fa48e1591a5b3eeae054bdce57bacf9fd26685a98138f5bf"}, + {file = "grpcio_tools-1.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:71cc6db1d66da3bc3730d9937bddc320f7b1f1dfdff6342bcb5741515fe4110b"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:b8e95d921cc2a1521d4750eedefec9f16031457920a6677edebe9d1b2ad6ae60"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:cb63055739808144b541986291679d643bae58755d0eb082157c4d4c04443905"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c4634b3589efa156a8d5860c0a2547315bd5c9e52d14c960d716fe86e0927be"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d970aa26854f535ffb94ea098aa8b43de020d9a14682e4a15dcdaeac7801b27"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:821dba464d84ebbcffd9d420302404db2fa7a40c7ff4c4c4c93726f72bfa2769"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0548e901894399886ff4a4cd808cb850b60c021feb4a8977a0751f14dd7e55d9"}, + {file = "grpcio_tools-1.59.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bb87158dbbb9e5a79effe78d54837599caa16df52d8d35366e06a91723b587ae"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:1d551ff42962c7c333c3da5c70d5e617a87dee581fa2e2c5ae2d5137c8886779"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:4ee443abcd241a5befb05629013fbf2eac637faa94aaa3056351aded8a31c1bc"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:520c0c83ea79d14b0679ba43e19c64ca31d30926b26ad2ca7db37cbd89c167e2"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fc02a6e517c34dcf885ff3b57260b646551083903e3d2c780b4971ce7d4ab7c"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aec8a4ed3808b7dfc1276fe51e3e24bec0eeaf610d395bcd42934647cf902a3"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99b3bde646720bbfb77f263f5ba3e1a0de50632d43c38d405a0ef9c7e94373cd"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51d9595629998d8b519126c5a610f15deb0327cd6325ed10796b47d1d292e70b"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-win32.whl", hash = "sha256:bfa4b2b7d21c5634b62e5f03462243bd705adc1a21806b5356b8ce06d902e160"}, + {file = "grpcio_tools-1.59.0-cp38-cp38-win_amd64.whl", hash = "sha256:9ed05197c5ab071e91bcef28901e97ca168c4ae94510cb67a14cb4931b94255a"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:498e7be0b14385980efa681444ba481349c131fc5ec88003819f5d929646947c"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b519f2ecde9a579cad2f4a7057d5bb4e040ad17caab8b5e691ed7a13b9db0be9"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:ef3e8aca2261f7f07436d4e2111556c1fb9bf1f9cfcdf35262743ccdee1b6ce9"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a7f226b741b2ebf7e2d0779d2c9b17f446d1b839d59886c1619e62cc2ae472"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:784aa52965916fec5afa1a28eeee6f0073bb43a2a1d7fedf963393898843077a"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e312ddc2d8bec1a23306a661ad52734f984c9aad5d8f126ebb222a778d95407d"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:868892ad9e00651a38dace3e4924bae82fc4fd4df2c65d37b74381570ee8deb1"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-win32.whl", hash = "sha256:a4f6cae381f21fee1ef0a5cbbbb146680164311157ae618edf3061742d844383"}, + {file = "grpcio_tools-1.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a10e59cca462208b489478340b52a96d64e8b8b6f1ac097f3e8cb211d3f66c0"}, +] + +[package.dependencies] +grpcio = ">=1.59.0" +protobuf = ">=4.21.6,<5.0dev" +setuptools = "*" + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "jsonpatch" +version = "1.32" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, + {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "jsonschema" +version = "4.19.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, + {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.7.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, + {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, +] + +[package.dependencies] +referencing = ">=0.28.0" + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "memory-profiler" +version = "0.61.0" +description = "A module for monitoring memory usage of a python program" +optional = false +python-versions = ">=3.5" +files = [ + {file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"}, + {file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"}, +] + +[package.dependencies] +psutil = "*" + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "networkx" +version = "3.1" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.8" +files = [ + {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, + {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, +] + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "openmetadata-ingestion" +version = "1.1.7.0" +description = "Ingestion Framework for OpenMetadata" +optional = false +python-versions = ">=3.8" +files = [ + {file = "openmetadata-ingestion-1.1.7.0.tar.gz", hash = "sha256:89df1b23b8614883f3c0fa0823cbb8a8489aca940824c68ccc75a3e2be43b2a3"}, + {file = "openmetadata_ingestion-1.1.7.0-py3-none-any.whl", hash = "sha256:7b091c2ebfa21c5c05827e5293dd623a4b33ab9788f5d34431d773ff4da40ecb"}, +] + +[package.dependencies] +antlr4-python3-runtime = "4.9.2" +avro = ">=1.11,<2.0" +boto3 = ">=1.20,<2.0" +cached-property = "1.5.2" +chardet = "4.0.0" +collate-sqllineage = ">=1.0.4" +commonregex = "*" +croniter = ">=1.3.0,<1.4.0" +cryptography = "*" +email-validator = ">=1.0.3" +google = ">=3.0.0" +google-auth = ">=1.33.0" +grpcio-tools = ">=1.47.2" +idna = ">=2.5,<3" +importlib-metadata = ">=4.13.0" +Jinja2 = ">=2.11.3" +jsonpatch = "1.32" +jsonschema = "*" +memory-profiler = "*" +mypy-extensions = ">=0.4.3" +pydantic = ">=1.10,<2.0" +pymysql = ">=1.0.2" +python-dateutil = ">=2.8.1" +python-jose = ">=3.3,<4.0" +PyYAML = ">=6.0,<7.0" +requests = ">=2.23" +requests-aws4auth = ">=1.1,<2.0" +setuptools = ">=66.0.0,<66.1.0" +sqlalchemy = ">=1.4.0,<2" +tabulate = "0.9.0" +typing-compat = ">=0.1.0,<0.2.0" +typing-extensions = "<=4.5.0" +typing-inspect = "*" +wheel = ">=0.38.4,<0.39.0" + +[package.extras] +airflow = ["apache-airflow (==2.6.3)"] +all = ["GeoAlchemy2 (>=0.12,<1.0)", "Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "adlfs (>=2022.2.0)", "alembic (>=1.10.2,<1.11.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "azure-identity", "azure-identity (>=1.12,<2.0)", "azure-storage-blob", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "cachetools", "chardet (==4.0.0)", "clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)", "collate-sqllineage (>=1.0.4)", "commonregex", "confluent-kafka (==2.1.1)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "cx-Oracle (>=8.3.0,<9)", "dagster-graphql (>=1.1,<2.0)", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "delta-spark (<=2.3.0)", "elasticsearch (==7.13.1)", "email-validator (>=1.0.3)", "fastavro (>=1.2.0)", "gcsfs (==2022.11.0)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "google-cloud", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "google-cloud-storage (==1.43.0)", "grpcio-tools (>=1.47.2)", "hdbcli", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "impyla (>=0.18.0,<0.19.0)", "impyla[kerberos] (>=0.18.0,<0.19.0)", "jsonpatch (==1.32)", "jsonschema", "ldap3 (==2.9.1)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "memory-profiler", "mlflow-skinny (>=1.30,<2.0)", "msal (>=1.2,<2.0)", "mypy-extensions (>=0.4.3)", "neo4j (>=5.3.0,<5.4.0)", "okta (>=2.3,<3.0)", "oracledb (>=1.2,<2.0)", "packaging (==21.3)", "pandas (==1.3.5)", "pinotdb (>=0.3,<1.0)", "presidio-analyzer (==2.2.32)", "presto-types-parser (>=0.0.2)", "protobuf", "psycopg2-binary", "pyarrow (>=10.0,<11.0)", "pyathena (==2.25.2)", "pydantic (>=1.10,<2.0)", "pydomo (>=0.3,<1.0)", "pydruid (>=0.6.5)", "pyhive (>=0.6,<1.0)", "pymongo (>=4.3,<5.0)", "pymysql (>=1.0.2)", "pyodbc (>=4.0.35,<5)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "python-on-whales (==0.55.0)", "python-snappy (>=0.6.1,<0.7.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "s3fs (==0.4.2)", "sasl (>=0.3,<1.0)", "scikit-learn (>=1.0,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "simple-salesforce (==1.11.4)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy (>=1.4.0,<2)", "sqlalchemy-bigquery (>=1.2.2)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-hana", "sqlalchemy-pgspider", "sqlalchemy-pytds (>=0.3,<1.0)", "sqlalchemy-redshift (==0.8.12)", "sqlalchemy-vertica[vertica-python] (>=0.0.5)", "tableau-api-lib (>=0.1,<1.0)", "tabulate (==0.9.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)", "trino[sqlalchemy]", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "websocket-client (>=1.6.1,<1.7.0)", "wheel (>=0.38.4,<0.39.0)"] +amundsen = ["neo4j (>=5.3.0,<5.4.0)"] +athena = ["pyathena (==2.25.2)"] +azure-sso = ["msal (>=1.2,<2.0)"] +azuresql = ["pyodbc (>=4.0.35,<5)"] +backup = ["azure-identity", "azure-storage-blob", "boto3 (>=1.20,<2.0)"] +base = ["Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "chardet (==4.0.0)", "collate-sqllineage (>=1.0.4)", "commonregex", "croniter (>=1.3.0,<1.4.0)", "cryptography", "email-validator (>=1.0.3)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "grpcio-tools (>=1.47.2)", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "jsonpatch (==1.32)", "jsonschema", "memory-profiler", "mypy-extensions (>=0.4.3)", "pydantic (>=1.10,<2.0)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "sqlalchemy (>=1.4.0,<2)", "tabulate (==0.9.0)", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "wheel (>=0.38.4,<0.39.0)"] +bigquery = ["cachetools", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "pyarrow (>=10.0,<11.0)", "sqlalchemy-bigquery (>=1.2.2)"] +clickhouse = ["clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)"] +dagster = ["GeoAlchemy2 (>=0.12,<1.0)", "dagster-graphql (>=1.1,<2.0)", "psycopg2-binary", "pymysql (>=1.0.2)"] +data-insight = ["elasticsearch (==7.13.1)"] +databricks = ["databricks-sdk (>=0.1,<1.0)", "sqlalchemy-databricks (>=0.1,<1.0)"] +datalake-azure = ["adlfs (>=2022.2.0)", "azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] +datalake-gcs = ["boto3 (>=1.20,<2.0)", "gcsfs (==2022.11.0)", "google-cloud-storage (==1.43.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] +datalake-s3 = ["boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)", "s3fs (==0.4.2)"] +db2 = ["ibm-db-sa (>=0.3,<1.0)"] +dbt = ["azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "dbt-artifacts-parser", "google-cloud", "google-cloud-storage (==1.43.0)"] +deltalake = ["delta-spark (<=2.3.0)"] +dev = ["black (==22.3.0)", "datamodel-code-generator (==0.15.0)", "docker", "isort", "pre-commit", "pycln", "pylint", "twine"] +docker = ["python-on-whales (==0.55.0)"] +domo = ["pydomo (>=0.3,<1.0)"] +druid = ["pydruid (>=0.6.5)"] +dynamodb = ["boto3 (>=1.20,<2.0)"] +elasticsearch = ["elasticsearch (==7.13.1)"] +glue = ["boto3 (>=1.20,<2.0)"] +great-expectations = ["great-expectations (>=0.16.0,<0.17.0)"] +hive = ["impyla (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "pyhive (>=0.6,<1.0)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] +impala = ["impyla[kerberos] (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] +kafka = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] +kinesis = ["boto3 (>=1.20,<2.0)"] +ldap-users = ["ldap3 (==2.9.1)"] +looker = ["lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)"] +mlflow = ["alembic (>=1.10.2,<1.11.0)", "mlflow-skinny (>=1.30,<2.0)"] +mongo = ["pandas (==1.3.5)", "pymongo (>=4.3,<5.0)"] +mssql = ["sqlalchemy-pytds (>=0.3,<1.0)"] +mssql-odbc = ["pyodbc (>=4.0.35,<5)"] +mysql = ["pymysql (>=1.0.2)"] +okta = ["okta (>=2.3,<3.0)"] +oracle = ["cx-Oracle (>=8.3.0,<9)", "oracledb (>=1.2,<2.0)"] +pgspider = ["psycopg2-binary", "sqlalchemy-pgspider"] +pii-processor = ["pandas (==1.3.5)", "presidio-analyzer (==2.2.32)", "spacy (==3.5.0)"] +pinotdb = ["pinotdb (>=0.3,<1.0)"] +postgres = ["GeoAlchemy2 (>=0.12,<1.0)", "packaging (==21.3)", "psycopg2-binary", "pymysql (>=1.0.2)"] +powerbi = ["msal (>=1.2,<2.0)"] +presto = ["presto-types-parser (>=0.0.2)", "pyhive (>=0.6,<1.0)"] +pymssql = ["pymssql (==2.2.5)"] +qliksense = ["websocket-client (>=1.6.1,<1.7.0)"] +quicksight = ["boto3 (>=1.20,<2.0)"] +redash = ["packaging (==21.3)"] +redpanda = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] +redshift = ["GeoAlchemy2 (>=0.12,<1.0)", "psycopg2-binary", "sqlalchemy-redshift (==0.8.12)"] +sagemaker = ["boto3 (>=1.20,<2.0)"] +salesforce = ["simple-salesforce (==1.11.4)"] +sap-hana = ["hdbcli", "sqlalchemy-hana"] +singlestore = ["pymysql (>=1.0.2)"] +sklearn = ["scikit-learn (>=1.0,<2.0)"] +snowflake = ["snowflake-sqlalchemy (>=1.4,<2.0)"] +tableau = ["tableau-api-lib (>=0.1,<1.0)"] +test = ["apache-airflow (==2.6.3)", "coverage", "dbt-artifacts-parser", "great-expectations (>=0.16.0,<0.17.0)", "moto (==4.0.8)", "pytest (==7.0.0)", "pytest-cov", "pytest-order"] +trino = ["trino[sqlalchemy]"] +vertica = ["sqlalchemy-vertica[vertica-python] (>=0.0.5)"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "protobuf" +version = "4.24.4" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, + {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, + {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, + {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, + {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, + {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, + {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, + {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, + {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, + {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, + {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, + {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, + {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, +] + +[[package]] +name = "psutil" +version = "5.9.6" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, + {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, + {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, + {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, + {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, + {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, + {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, + {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, + {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, + {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, + {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, + {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "pyasn1" +version = "0.5.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, + {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "1.10.13" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, + {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, + {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, + {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, + {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, + {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, + {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, + {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, + {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymysql" +version = "1.1.0" +description = "Pure Python MySQL Driver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, + {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, +] + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[[package]] +name = "pytest" +version = "7.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-jose" +version = "3.3.0" +description = "JOSE implementation in Python" +optional = false +python-versions = "*" +files = [ + {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, + {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, +] + +[package.dependencies] +ecdsa = "!=0.15" +pyasn1 = "*" +rsa = "*" + +[package.extras] +cryptography = ["cryptography (>=3.4.0)"] +pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] +pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "referencing" +version = "0.30.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, + {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2023.10.3" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.7" +files = [ + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-aws4auth" +version = "1.2.3" +description = "AWS4 authentication for Requests" +optional = false +python-versions = ">=3.3" +files = [ + {file = "requests-aws4auth-1.2.3.tar.gz", hash = "sha256:d4c73c19f37f80d4aa9c5bd4fa376cfd0c69299c48b00a8eb2ae6b0416164fb8"}, + {file = "requests_aws4auth-1.2.3-py2.py3-none-any.whl", hash = "sha256:8070a5207e95fa5fe88e87d9a75f34e768cbab35bb3557ef20cbbf9426dee4d5"}, +] + +[package.dependencies] +requests = "*" +six = "*" + +[package.extras] +httpx = ["httpx"] + +[[package]] +name = "requests-mock" +version = "1.11.0" +description = "Mock out responses from the requests package" +optional = false +python-versions = "*" +files = [ + {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, + {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, +] + +[package.dependencies] +requests = ">=2.3,<3" +six = "*" + +[package.extras] +fixture = ["fixtures"] +test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] + +[[package]] +name = "rpds-py" +version = "0.10.6" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, + {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, + {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, + {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, + {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, + {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, + {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, + {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, + {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, + {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, + {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, + {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, + {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, + {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, + {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, + {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, + {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, + {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, + {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, + {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, + {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, + {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, + {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, + {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, + {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, + {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, + {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, + {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, + {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, + {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, + {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, + {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, + {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, + {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, +] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "s3transfer" +version = "0.7.0" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, + {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[[package]] +name = "setuptools" +version = "66.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-66.0.0-py3-none-any.whl", hash = "sha256:a78d01d1e2c175c474884671dde039962c9d74c7223db7369771fcf6e29ceeab"}, + {file = "setuptools-66.0.0.tar.gz", hash = "sha256:bd6eb2d6722568de6d14b87c44a96fac54b2a45ff5e940e639979a3d1792adb6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sqlalchemy" +version = "1.4.49" +description = "Database Abstraction Library" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca46de16650d143a928d10842939dab208e8d8c3a9a8757600cae9b7c579c5cd"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f23755c384c2969ca2f7667a83f7c5648fcf8b62a3f2bbd883d805454964a800"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8396e896e08e37032e87e7fbf4a15f431aa878c286dc7f79e616c2feacdb366c"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66da9627cfcc43bbdebd47bfe0145bb662041472393c03b7802253993b6b7c90"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-win32.whl", hash = "sha256:9a06e046ffeb8a484279e54bda0a5abfd9675f594a2e38ef3133d7e4d75b6214"}, + {file = "SQLAlchemy-1.4.49-cp312-cp312-win_amd64.whl", hash = "sha256:7cf8b90ad84ad3a45098b1c9f56f2b161601e4670827d6b892ea0e884569bd1d"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc22807a7e161c0d8f3da34018ab7c97ef6223578fcdd99b1d3e7ed1100a5db"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393cd06c3b00b57f5421e2133e088df9cabcececcea180327e43b937b5a7caa5"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ab792ca493891d7a45a077e35b418f68435efb3e1706cb8155e20e86a9013c"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:738d7321212941ab19ba2acf02a68b8ee64987b248ffa2101630e8fccb549e0d"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, + {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlfluff" +version = "2.1.4" +description = "The SQL Linter for Humans" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sqlfluff-2.1.4-py3-none-any.whl", hash = "sha256:3884e375b9a884485263cf156b21ff55a38adadb7be6451e27ee6b8f1a75f018"}, + {file = "sqlfluff-2.1.4.tar.gz", hash = "sha256:86ee197cb6919e50962c4def5f64ba8bafb35b1ffab943b2e87fca137e3df2a2"}, +] + +[package.dependencies] +appdirs = "*" +chardet = "*" +click = "*" +colorama = ">=0.3" +diff-cover = ">=2.5.0" +Jinja2 = "*" +pathspec = "*" +pytest = "*" +pyyaml = ">=5.1" +regex = "*" +tblib = "*" +toml = {version = "*", markers = "python_version < \"3.11\""} +tqdm = "*" +typing-extensions = "*" + +[[package]] +name = "sqlparse" +version = "0.4.3" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, + {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tblib" +version = "2.0.0" +description = "Traceback serialization library." +optional = false +python-versions = ">=3.7" +files = [ + {file = "tblib-2.0.0-py3-none-any.whl", hash = "sha256:9100bfa016b047d5b980d66e7efed952fbd20bd85b56110aaf473cb97d18709a"}, + {file = "tblib-2.0.0.tar.gz", hash = "sha256:a6df30f272c08bf8be66e0775fad862005d950a6b8449b94f7c788731d70ecd7"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-compat" +version = "0.1.0" +description = "Python typing compatibility library" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "typing-compat-0.1.0.tar.gz", hash = "sha256:a7159db80406de342bc128ab315fae3afe1ddc87cbc2df7a023763090fdfe5d2"}, + {file = "typing_compat-0.1.0-py2.py3-none-any.whl", hash = "sha256:a8b94eb34c95982fbd34b8daa46fd78c43db33c075b98c79b6e1d77e8f7dd4d5"}, +] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "urllib3" +version = "2.0.6" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wheel" +version = "0.38.4" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, + {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, +] + +[package.extras] +test = ["pytest (>=3.0.0)"] + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "3417bef1d6d37b2d5e54e6d771526c308e732e5f54b3db4f038b046332bdb180" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml new file mode 100644 index 00000000..613a507c --- /dev/null +++ b/lib/datahub-client/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "data-platform-catalogue" +version = "0.1.0" +description = "Library to integrate the MoJ data platform with the catalogue component." +authors = ["MoJ Data Platform Team "] +license = "MIT" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +openmetadata-ingestion = "^1.1.7.0" + +# 1.5.4 doesn't install for some reason. Either way it will be removed from the next release of the ingestion package. +commonregex = "1.5.3" + +[tool.poetry.group.dev.dependencies] +requests-mock = "^1.11.0" +pytest = "^7.4.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/lib/datahub-client/tests/__init__.py b/lib/datahub-client/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client.py new file mode 100644 index 00000000..0feb189e --- /dev/null +++ b/lib/datahub-client/tests/test_client.py @@ -0,0 +1,196 @@ +import pytest +from data_platform_catalogue.client import CatalogueClient + + +class TestCatalogueClient: + def mock_service_response(self, fqn): + return { + "fullyQualifiedName": fqn, + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "name": "foo", + "serviceType": "Glue", + } + + def mock_database_response(self, fqn): + return { + "fullyQualifiedName": fqn, + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "name": "foo", + "service": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "Glue", + }, + } + + def mock_schema_response(self, fqn): + return { + "fullyQualifiedName": fqn, + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "name": "foo", + "service": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "Glue", + }, + "database": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "", + }, + } + + def mock_table_response(self, fqn): + return { + "fullyQualifiedName": "some-table", + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "name": "foo", + "service": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "Glue", + }, + "database": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "", + }, + "databaseSchema": { + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + "type": "", + }, + "columns": [], + } + + @pytest.fixture + def client(self, requests_mock): + requests_mock.get( + "http://example.com/api/v1/system/version", + json={"version": "1.1.7.0", "revision": "1", "timestamp": 0}, + ) + + return CatalogueClient(jwt_token="abc", api_uri="http://example.com/api") + + def test_create_service(self, client, requests_mock): + requests_mock.put( + "http://example.com/api/v1/services/databaseServices", + json=self.mock_service_response("some-service"), + ) + + fqn = client.create_or_update_database_service() + + assert requests_mock.last_request.json() == { + "name": "data-platform", + "displayName": "Data platform", + "serviceType": "Glue", + "connection": {"config": {}}, + } + assert fqn == "some-service" + + def test_create_database(self, client, requests_mock): + requests_mock.put( + "http://example.com/api/v1/databases", + json=self.mock_database_response("some-db"), + ) + + fqn = client.create_or_update_database( + name="data-product", service_fqn="data-platform" + ) + assert requests_mock.last_request.json() == { + "name": "data-product", + "displayName": None, + "description": None, + "tags": None, + "owner": None, + "service": "data-platform", + "default": False, + "retentionPeriod": None, + "extension": None, + "sourceUrl": None, + } + assert fqn == "some-db" + + def test_create_schema(self, client, requests_mock): + requests_mock.put( + "http://example.com/api/v1/databaseSchemas", + json=self.mock_schema_response("some-schema"), + ) + + fqn = client.create_or_update_schema(name="schema", database_fqn="data-product") + assert requests_mock.last_request.json() == { + "name": "schema", + "displayName": None, + "description": None, + "owner": None, + "database": "data-product", + "tags": None, + "retentionPeriod": None, + "extension": None, + "sourceUrl": None, + } + assert fqn == "some-schema" + + def test_create_table(self, client, requests_mock): + requests_mock.put( + "http://example.com/api/v1/tables", + json=self.mock_table_response("some-table"), + ) + + fqn = client.create_or_update_table( + name="table", + schema_fqn="data-platform.data-product.schema", + column_types={"foo": "string", "bar": "int"}, + ) + assert requests_mock.last_request.json() == { + "name": "table", + "displayName": None, + "description": None, + "tableType": None, + "columns": [ + { + "name": "foo", + "displayName": None, + "dataType": "STRING", + "arrayDataType": None, + "dataLength": None, + "precision": None, + "scale": None, + "dataTypeDisplay": None, + "description": None, + "fullyQualifiedName": None, + "tags": None, + "constraint": None, + "ordinalPosition": None, + "jsonSchema": None, + "children": None, + "customMetrics": None, + "profile": None, + }, + { + "name": "bar", + "displayName": None, + "dataType": "INT", + "arrayDataType": None, + "dataLength": None, + "precision": None, + "scale": None, + "dataTypeDisplay": None, + "description": None, + "fullyQualifiedName": None, + "tags": None, + "constraint": None, + "ordinalPosition": None, + "jsonSchema": None, + "children": None, + "customMetrics": None, + "profile": None, + }, + ], + "tableConstraints": None, + "tablePartition": None, + "tableProfilerConfig": None, + "owner": None, + "databaseSchema": "data-platform.data-product.schema", + "tags": None, + "viewDefinition": None, + "retentionPeriod": None, + "extension": None, + "sourceUrl": None, + "fileFormat": None, + } + assert fqn == "some-table" From 3fdac18f97967ea2237f177dc0fc1bd1240b8ee0 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 20 Oct 2023 15:45:20 +0100 Subject: [PATCH 02/64] Fix typo in package name to match pypi (#2011) * Fix typo in package name to match pypi * Install poetry before setup_python setup_python has a dependency on poetry if using the cache path. --- lib/datahub-client/README.md | 2 +- lib/datahub-client/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 2be4d8b8..fabf84bd 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -10,7 +10,7 @@ so that data products are discoverable. To install the package using `pip`, run: ```shell -pip install data-platform-catalogue +pip install ministryofjustice-data-platform-catalogue ``` ## Topology diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 613a507c..8c824918 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "data-platform-catalogue" +name = "ministryofjustice-data-platform-catalogue" version = "0.1.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] From 014d20b98dd2144b5d125fc49153d619beabc7e2 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 20 Oct 2023 16:03:41 +0100 Subject: [PATCH 03/64] Update paths since name of package has been renamed (#2013) --- lib/datahub-client/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 8c824918..337108b6 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -5,9 +5,10 @@ description = "Library to integrate the MoJ data platform with the catalogue com authors = ["MoJ Data Platform Team "] license = "MIT" readme = "README.md" +packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.10.0" openmetadata-ingestion = "^1.1.7.0" # 1.5.4 doesn't install for some reason. Either way it will be removed from the next release of the ingestion package. From 019540a81e98745b6a6b532790d782914eec9d09 Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 23 Oct 2023 15:06:09 +0100 Subject: [PATCH 04/64] Modify catalogue client to accept full metadata objects and hide internal exceptions (#2014) * Modify client to accept metadata objects This forces us to pass in: - name, description for database/schema/table - retention period at schema/table level - version, owner, email, dpi_required, domain at schema level We can also accept tags at any level. The tags must already exist within an OpenMetadata classification. Email, dpia_required, domain, versions are not yet passed through to the catalogue. These may require use of custom attributes. * Wrap exceptions from OpenMetadata This library is intended to present a catalogue-agnostic API to the rest of the ingestion service, so we should expose our own exception hierarchy instead of passing through OpenMetadata's. * lint * Bump version * Update codeowners for new library --- lib/datahub-client/README.md | 42 +++++-- .../data_platform_catalogue/__init__.py | 4 + .../data_platform_catalogue/client.py | 83 ++++++++++++-- .../data_platform_catalogue/entities.py | 30 +++++ lib/datahub-client/pyproject.toml | 2 +- lib/datahub-client/tests/test_client.py | 107 ++++++++++++++---- 6 files changed, 227 insertions(+), 41 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/entities.py diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index fabf84bd..4c75ea9a 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -25,7 +25,11 @@ pip install ministryofjustice-data-platform-catalogue ## Example usage ```python -from data_platform_catalogue import CatalogueClient +from data_platform_catalogue import ( + CatalogueClient, CatalogueMetadata, + DataProductMetadata, TableMetadata, + CatalogueError +) client = CatalogueClient( jwt_token="***", @@ -34,13 +38,35 @@ client = CatalogueClient( assert client.is_healthy() -service_fqn = client.create_or_update_database_service(name="data_platform") -database_fqn = client.create_or_update_database(name="all_data_products", service_fqn=service_fqn) -schema_fqn = client.create_or_update_database(name="my_data_product", database_fqn=database_fqn) -table_fqn = client.create_or_update_table( - name="my_table", - schema_fqn=schema_fqn, - column_types={"foo": "string", "bar": "int"} +catalogue = CatalogueMetadata( + name = "data_platform", + description = "All data products hosted on the data platform", +) + +data_product = DataProductMetadata( + name = "my_data_product", + description = "bla bla", + version = "v1.0.0", + owner = "7804c127-d677-4900-82f9-83517e51bb94", + email = "justice@justice.gov.uk", + retention_period_in_days = 365, + domain = "legal-aid", + dpia_required = False +) + +table = TableMetadata( + name = "my_table", + description = "bla bla", + column_types = {"foo": "string", "bar": "int"}, + retention_period_in_days = 365 ) + +try: + service_fqn = client.create_or_update_database_service(name="data_platform") + database_fqn = client.create_or_update_database(metadata=catalogue, service_fqn=service_fqn) + schema_fqn = client.create_or_update_schema(metadata=data_product, database_fqn=database_fqn) + table_fqn = client.create_or_update_table(metadata=table, schema_fqn=schema_fqn) +except CatalogueError: + print("oh no") ``` diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py index 9df5f2ed..1fcfe78f 100644 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -1 +1,5 @@ from .client import CatalogueClient # noqa: F401 +from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 +from .entities import CatalogueMetadata # noqa: F401 +from .entities import DataProductMetadata # noqa: F401 +from .entities import TableMetadata # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client.py b/lib/datahub-client/data_platform_catalogue/client.py index f8a1ea1c..c2ba84fd 100644 --- a/lib/datahub-client/data_platform_catalogue/client.py +++ b/lib/datahub-client/data_platform_catalogue/client.py @@ -1,5 +1,6 @@ import json import logging +from http import HTTPStatus from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest from metadata.generated.schema.api.data.createDatabaseSchema import ( @@ -17,7 +18,17 @@ from metadata.generated.schema.security.client.openMetadataJWTClientConfig import ( OpenMetadataJWTClientConfig, ) -from metadata.ingestion.ometa.ometa_api import OpenMetadata +from metadata.generated.schema.type.basic import Duration +from metadata.generated.schema.type.entityReference import EntityReference +from metadata.generated.schema.type.tagLabel import ( + LabelType, + State, + TagLabel, + TagSource, +) +from metadata.ingestion.ometa.ometa_api import APIError, OpenMetadata + +from .entities import CatalogueMetadata, DataProductMetadata, TableMetadata logger = logging.getLogger(__name__) @@ -40,12 +51,28 @@ } +class CatalogueError(Exception): + """ + Base class for all errors. + """ + + +class ReferencedEntityMissing(CatalogueError): + """ + A referenced entity (such as a user or tag) does not yet exist when + attempting to create a new metadata resource in the catalogue. + """ + + class CatalogueClient: """ Client for pushing metadata to the catalogue. Tables in the catalogue are arranged into the following hierarchy: DatabaseService -> Database -> Schema -> Table + + If there is a problem communicating with the catalogue, methods will raise an instance of + CatalogueError. """ def __init__( @@ -94,38 +121,48 @@ def create_or_update_database_service( return response["fullyQualifiedName"] - def create_or_update_database(self, name: str, service_fqn: str): + def create_or_update_database(self, metadata: CatalogueMetadata, service_fqn: str): """ Define a database. There should be one database per data platform catalogue. """ create_db = CreateDatabaseRequest( - name=name, + name=metadata.name, + description=metadata.description, + tags=self._generate_tags(metadata.tags), service=service_fqn, ) return self._create_or_update_entity(create_db) - def create_or_update_schema(self, name: str, database_fqn: str): + def create_or_update_schema(self, metadata: DataProductMetadata, database_fqn: str): """ Define a database schema. There should be one schema per data product. """ - create_schema = CreateDatabaseSchemaRequest(name=name, database=database_fqn) + create_schema = CreateDatabaseSchemaRequest( + name=metadata.name, + description=metadata.description, + owner=EntityReference(id=metadata.owner, type="user"), + tags=self._generate_tags(metadata.tags), + retentionPeriod=self._generate_duration(metadata.retention_period_in_days), + database=database_fqn, + ) return self._create_or_update_entity(create_schema) - def create_or_update_table( - self, name: str, column_types: dict[str, str], schema_fqn: str - ): + def create_or_update_table(self, metadata: TableMetadata, schema_fqn: str): """ Define a table. There can be many tables per data product. """ columns = [ Column(name=k, dataType=DATA_TYPE_MAPPING[v]) - for k, v in column_types.items() + for k, v in metadata.column_types.items() ] create_table = CreateTableRequest( - name=name, + name=metadata.name, + description=metadata.description, + retentionPeriod=self._generate_duration(metadata.retention_period_in_days), + tags=self._generate_tags(metadata.tags), databaseSchema=schema_fqn, columns=columns, ) @@ -133,7 +170,17 @@ def create_or_update_table( def _create_or_update_entity(self, data) -> str: logger.info(f"Creating {data.json()}") - response = self.metadata.create_or_update(data=data) + + try: + response = self.metadata.create_or_update(data=data) + except APIError as exception: + if exception.status_code == HTTPStatus.NOT_FOUND: + raise ReferencedEntityMissing from exception + else: + raise CatalogueError from exception + except Exception as exception: + raise CatalogueError from exception + return response.dict()["fullyQualifiedName"] def delete_database_service(self, fqn: str): @@ -159,3 +206,17 @@ def delete_table(self, fqn: str): Delete a table. """ raise NotImplementedError + + def _generate_tags(self, tags: list[str]): + return [ + TagLabel( + tagFQN=tag, + labelType=LabelType.Automated, + source=TagSource.Classification, + state=State.Confirmed, + ) + for tag in tags + ] + + def _generate_duration(self, duration_in_days: int): + return Duration.parse_obj(f"P{duration_in_days}D") diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py new file mode 100644 index 00000000..e5b7b12a --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass, field + + +@dataclass +class CatalogueMetadata: + name: str + description: str + tags: list[str] = field(default_factory=list) + + +@dataclass +class DataProductMetadata: + name: str + description: str + version: str + owner: str + email: str + retention_period_in_days: int + domain: str + dpia_required: bool + tags: list[str] = field(default_factory=list) + + +@dataclass +class TableMetadata: + name: str + description: str + column_types: dict[str, str] + retention_period_in_days: int + tags: list[str] = field(default_factory=list) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 337108b6..4887d5a4 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.1.0" +version = "0.1.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client.py index 0feb189e..0f92f7eb 100644 --- a/lib/datahub-client/tests/test_client.py +++ b/lib/datahub-client/tests/test_client.py @@ -1,5 +1,10 @@ import pytest -from data_platform_catalogue.client import CatalogueClient +from data_platform_catalogue.client import CatalogueClient, ReferencedEntityMissing +from data_platform_catalogue.entities import ( + CatalogueMetadata, + DataProductMetadata, + TableMetadata, +) class TestCatalogueClient: @@ -57,6 +62,36 @@ def mock_table_response(self, fqn): "columns": [], } + @pytest.fixture + def catalogue(self): + return CatalogueMetadata( + name="data_platform", + description="All data products hosted on the data platform", + ) + + @pytest.fixture + def data_product(self): + return DataProductMetadata( + name="my_data_product", + description="bla bla", + version="v1.0.0", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="legal-aid", + dpia_required=False, + tags=["test"], + ) + + @pytest.fixture + def table(self): + return TableMetadata( + name="my_table", + description="bla bla", + column_types={"foo": "string", "bar": "int"}, + retention_period_in_days=365, + ) + @pytest.fixture def client(self, requests_mock): requests_mock.get( @@ -82,20 +117,20 @@ def test_create_service(self, client, requests_mock): } assert fqn == "some-service" - def test_create_database(self, client, requests_mock): + def test_create_database(self, client, requests_mock, catalogue): requests_mock.put( "http://example.com/api/v1/databases", json=self.mock_database_response("some-db"), ) fqn = client.create_or_update_database( - name="data-product", service_fqn="data-platform" + metadata=catalogue, service_fqn="data-platform" ) assert requests_mock.last_request.json() == { - "name": "data-product", + "name": "data_platform", "displayName": None, - "description": None, - "tags": None, + "description": "All data products hosted on the data platform", + "tags": [], "owner": None, "service": "data-platform", "default": False, @@ -105,41 +140,59 @@ def test_create_database(self, client, requests_mock): } assert fqn == "some-db" - def test_create_schema(self, client, requests_mock): + def test_create_schema(self, client, requests_mock, data_product): requests_mock.put( "http://example.com/api/v1/databaseSchemas", json=self.mock_schema_response("some-schema"), ) - fqn = client.create_or_update_schema(name="schema", database_fqn="data-product") + fqn = client.create_or_update_schema( + metadata=data_product, database_fqn="data-product" + ) assert requests_mock.last_request.json() == { - "name": "schema", + "name": "my_data_product", "displayName": None, - "description": None, - "owner": None, + "description": "bla bla", + "owner": { + "deleted": None, + "description": None, + "displayName": None, + "fullyQualifiedName": None, + "href": None, + "id": "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + "name": None, + "type": "user", + }, "database": "data-product", - "tags": None, - "retentionPeriod": None, + "tags": [ + { + "description": None, + "href": None, + "labelType": "Automated", + "source": "Classification", + "state": "Confirmed", + "tagFQN": "test", + } + ], + "retentionPeriod": "P365D", "extension": None, "sourceUrl": None, } assert fqn == "some-schema" - def test_create_table(self, client, requests_mock): + def test_create_table(self, client, requests_mock, table): requests_mock.put( "http://example.com/api/v1/tables", json=self.mock_table_response("some-table"), ) fqn = client.create_or_update_table( - name="table", - schema_fqn="data-platform.data-product.schema", - column_types={"foo": "string", "bar": "int"}, + metadata=table, schema_fqn="data-platform.data-product.schema" ) assert requests_mock.last_request.json() == { - "name": "table", + "name": "my_table", "displayName": None, - "description": None, + "description": "bla bla", "tableType": None, "columns": [ { @@ -186,11 +239,23 @@ def test_create_table(self, client, requests_mock): "tableProfilerConfig": None, "owner": None, "databaseSchema": "data-platform.data-product.schema", - "tags": None, + "tags": [], "viewDefinition": None, - "retentionPeriod": None, + "retentionPeriod": "P365D", "extension": None, "sourceUrl": None, "fileFormat": None, } assert fqn == "some-table" + + def test_404_handling(self, client, requests_mock, table): + requests_mock.put( + "http://example.com/api/v1/tables", + status_code=404, + json={"code": "something", "message": "something"}, + ) + + with pytest.raises(ReferencedEntityMissing): + client.create_or_update_table( + metadata=table, schema_fqn="data-platform.data-product.schema" + ) From 61f3bc5043a44222d87c9ede12a67a6f2afef654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:26:29 +0100 Subject: [PATCH 05/64] Bump urllib3 from 2.0.6 to 2.0.7 in /python-libraries/data-platform-catalogue (#1997) Bump urllib3 in /python-libraries/data-platform-catalogue Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index bacd1a24..8c63ee53 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -2006,13 +2006,13 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] From 5df2456a4c48305b992456fa81969180e1f393fb Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:21:52 +0000 Subject: [PATCH 06/64] add functionality to `ministryofjustice-data-platform-catalogue` for metadata/schema pushes to openmetadata (#2176) * added `get_user_id` method and pass in column descriptions * add from dict methods to entities * add tests for get_user_id and new entity methods * upversion poetry * remove unused import from client * typo * correct type please * right upversion * lint * remove commented code * making entity args non optional --- .../data_platform_catalogue/client.py | 27 +- .../data_platform_catalogue/entities.py | 54 +- lib/datahub-client/poetry.lock | 766 +++++++++--------- lib/datahub-client/pyproject.toml | 6 +- lib/datahub-client/tests/test_client.py | 28 +- lib/datahub-client/tests/test_entities.py | 67 ++ 6 files changed, 534 insertions(+), 414 deletions(-) create mode 100644 lib/datahub-client/tests/test_entities.py diff --git a/lib/datahub-client/data_platform_catalogue/client.py b/lib/datahub-client/data_platform_catalogue/client.py index c2ba84fd..282dd1bc 100644 --- a/lib/datahub-client/data_platform_catalogue/client.py +++ b/lib/datahub-client/data_platform_catalogue/client.py @@ -15,6 +15,7 @@ from metadata.generated.schema.entity.services.databaseService import ( DatabaseServiceType, ) +from metadata.generated.schema.entity.teams.user import User from metadata.generated.schema.security.client.openMetadataJWTClientConfig import ( OpenMetadataJWTClientConfig, ) @@ -153,10 +154,16 @@ def create_or_update_table(self, metadata: TableMetadata, schema_fqn: str): """ Define a table. There can be many tables per data product. + columns are expected to be a list of dicts in the format + {"name": "column1", "type": "string", "description": "just an example"} """ columns = [ - Column(name=k, dataType=DATA_TYPE_MAPPING[v]) - for k, v in metadata.column_types.items() + Column( + name=column["name"], + dataType=DATA_TYPE_MAPPING[column["type"]], + description=column["description"], + ) + for column in metadata.column_details ] create_table = CreateTableRequest( name=metadata.name, @@ -183,6 +190,15 @@ def _create_or_update_entity(self, data) -> str: return response.dict()["fullyQualifiedName"] + def get_user_id(self, user_email: str): + """ + returns the user id from openmetadata when given a user's email + """ + username = user_email.split("@")[0] + user_id = self.metadata.get_by_name(entity=User, fqn=username).id + + return user_id + def delete_database_service(self, fqn: str): """ Delete a database service. @@ -218,5 +234,8 @@ def _generate_tags(self, tags: list[str]): for tag in tags ] - def _generate_duration(self, duration_in_days: int): - return Duration.parse_obj(f"P{duration_in_days}D") + def _generate_duration(self, duration_in_days: int | None): + if duration_in_days is None: + return None + else: + return Duration.parse_obj(f"P{duration_in_days}D") diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index e5b7b12a..5f5fbfbe 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from typing import Any @dataclass @@ -20,11 +21,60 @@ class DataProductMetadata: dpia_required: bool tags: list[str] = field(default_factory=list) + @staticmethod + def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): + """ + Expects a dict containing data product metatdata information as per the + required fields in the json schema at + https://github.com/ministryofjustice/modernisation-platform-environments/tree/main/terraform/environments/data-platform/data-product-metadata-json-schema + + and should be pass version and owner id as in openmetadata. + + Then populates a DataProductMetadata object with the given data. + """ + new_metadata = DataProductMetadata( + name=metadata["name"], + description=metadata["description"], + version=version, + owner=owner_id, + email=metadata["email"], + retention_period_in_days=metadata["retentionPeriod"], + domain=metadata["domain"], + dpia_required=metadata["dpiaRequired"], + tags=metadata.get("tags", []), + ) + + return new_metadata + @dataclass class TableMetadata: name: str description: str - column_types: dict[str, str] - retention_period_in_days: int + column_details: list + retention_period_in_days: int | None tags: list[str] = field(default_factory=list) + + @staticmethod + def from_data_product_schema_dict( + metadata: dict[str, Any], table_name, retention_period: int | None = None + ): + """ + Expects a dict containing data product table schema information as per the + required fields in the json schema at + https://github.com/ministryofjustice/modernisation-platform-environments/tree/main/terraform/environments/data-platform/data-product-table-schema-json-schema + + and should be passed table name and optionally a retention period + + Then populates a TableMetadata object with the given data. + """ + + new_metadata = TableMetadata( + name=table_name, + description=metadata["tableDescription"], + column_details=metadata["columns"], + retention_period_in_days=retention_period, + tags=metadata.get("tags", []), + ) + + return new_metadata diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 8c63ee53..858010da 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -73,17 +73,17 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.28.64" +version = "1.28.76" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.7" files = [ - {file = "boto3-1.28.64-py3-none-any.whl", hash = "sha256:a99150a30c038c73e89662836820a8cce914afab5ea377942a37c484b85f4438"}, - {file = "boto3-1.28.64.tar.gz", hash = "sha256:a5cf93b202568e9d378afdc84be55a6dedf11d30156289fe829e23e6d7dccabb"}, + {file = "boto3-1.28.76-py3-none-any.whl", hash = "sha256:85e2fa361ad3210d30800bad311688261f2673a9b301e0edab56463d89609761"}, + {file = "boto3-1.28.76.tar.gz", hash = "sha256:d18688bc5d688decf3cc404430a3ac3ec317be653cdcfbc51104c01f38a66434"}, ] [package.dependencies] -botocore = ">=1.31.64,<1.32.0" +botocore = ">=1.31.76,<1.32.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.7.0,<0.8.0" @@ -92,13 +92,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.64" +version = "1.31.76" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.7" files = [ - {file = "botocore-1.31.64-py3-none-any.whl", hash = "sha256:7b709310343a5b430ec9025b2e17c0bac6b16c05f1ac1d9521dece3f10c71bac"}, - {file = "botocore-1.31.64.tar.gz", hash = "sha256:d8eb4b724ac437343359b318d73de0cfae0fecb24095827e56135b0ad6b44caf"}, + {file = "botocore-1.31.76-py3-none-any.whl", hash = "sha256:74e0a4515d61b2860b24dc208ca89a68d79dc00147125d531746d3ba808822ad"}, + {file = "botocore-1.31.76.tar.gz", hash = "sha256:479abb5a1ee03eb00faa1ea176bc595b2f46f7494777807681a9df45ed99ea18"}, ] [package.dependencies] @@ -122,13 +122,13 @@ files = [ [[package]] name = "cachetools" -version = "5.3.1" +version = "5.3.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, - {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, ] [[package]] @@ -219,101 +219,101 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -387,34 +387,34 @@ python-dateutil = "*" [[package]] name = "cryptography" -version = "41.0.4" +version = "41.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, + {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, + {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, + {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, + {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, + {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, + {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, + {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, + {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, + {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, ] [package.dependencies] @@ -489,13 +489,13 @@ gmpy2 = ["gmpy2"] [[package]] name = "email-validator" -version = "2.0.0.post2" +version = "2.1.0.post1" description = "A robust email address syntax and deliverability validation library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "email_validator-2.0.0.post2-py3-none-any.whl", hash = "sha256:2466ba57cda361fb7309fd3d5a225723c788ca4bbad32a0ebd5373b99730285c"}, - {file = "email_validator-2.0.0.post2.tar.gz", hash = "sha256:1ff6e86044200c56ae23595695c54e9614f4a9551e0e393614f764860b3d7900"}, + {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"}, + {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"}, ] [package.dependencies] @@ -532,13 +532,13 @@ beautifulsoup4 = "*" [[package]] name = "google-auth" -version = "2.23.3" +version = "2.23.4" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.23.3.tar.gz", hash = "sha256:6864247895eea5d13b9c57c9e03abb49cb94ce2dc7c58e91cba3248c7477c9e3"}, - {file = "google_auth-2.23.3-py2.py3-none-any.whl", hash = "sha256:a8f4608e65c244ead9e0538f181a96c6e11199ec114d41f1d7b1bffa96937bda"}, + {file = "google-auth-2.23.4.tar.gz", hash = "sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3"}, + {file = "google_auth-2.23.4-py2.py3-none-any.whl", hash = "sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2"}, ] [package.dependencies] @@ -555,73 +555,68 @@ requests = ["requests (>=2.20.0,<3.0.0.dev0)"] [[package]] name = "greenlet" -version = "3.0.0" +version = "3.0.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, - {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, - {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, - {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, - {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, - {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, - {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, - {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, - {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, - {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, - {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, - {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, - {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, - {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, - {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, - {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, - {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, - {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, + {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, + {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, + {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, + {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, + {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, + {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, + {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, + {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, + {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, + {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, + {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, + {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, + {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, + {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, + {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, + {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, + {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, + {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, + {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, + {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, + {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, + {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, + {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, + {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, + {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, + {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, + {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, ] [package.extras] @@ -630,135 +625,135 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.59.0" +version = "1.59.2" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-1.59.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd"}, - {file = "grpcio-1.59.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539"}, - {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf"}, - {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a"}, - {file = "grpcio-1.59.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0"}, - {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b"}, - {file = "grpcio-1.59.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7"}, - {file = "grpcio-1.59.0-cp310-cp310-win32.whl", hash = "sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211"}, - {file = "grpcio-1.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4"}, - {file = "grpcio-1.59.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09"}, - {file = "grpcio-1.59.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589"}, - {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe"}, - {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252"}, - {file = "grpcio-1.59.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d"}, - {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9"}, - {file = "grpcio-1.59.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5"}, - {file = "grpcio-1.59.0-cp311-cp311-win32.whl", hash = "sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7"}, - {file = "grpcio-1.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29"}, - {file = "grpcio-1.59.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5"}, - {file = "grpcio-1.59.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588"}, - {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd"}, - {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66"}, - {file = "grpcio-1.59.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52"}, - {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9"}, - {file = "grpcio-1.59.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10"}, - {file = "grpcio-1.59.0-cp312-cp312-win32.whl", hash = "sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205"}, - {file = "grpcio-1.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88"}, - {file = "grpcio-1.59.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c"}, - {file = "grpcio-1.59.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0"}, - {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3"}, - {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3"}, - {file = "grpcio-1.59.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a"}, - {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0"}, - {file = "grpcio-1.59.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575"}, - {file = "grpcio-1.59.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897"}, - {file = "grpcio-1.59.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055"}, - {file = "grpcio-1.59.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c"}, - {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74"}, - {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac"}, - {file = "grpcio-1.59.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce"}, - {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901"}, - {file = "grpcio-1.59.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1"}, - {file = "grpcio-1.59.0-cp38-cp38-win32.whl", hash = "sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4"}, - {file = "grpcio-1.59.0-cp38-cp38-win_amd64.whl", hash = "sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c"}, - {file = "grpcio-1.59.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857"}, - {file = "grpcio-1.59.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180"}, - {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6"}, - {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996"}, - {file = "grpcio-1.59.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a"}, - {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df"}, - {file = "grpcio-1.59.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0"}, - {file = "grpcio-1.59.0-cp39-cp39-win32.whl", hash = "sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f"}, - {file = "grpcio-1.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81"}, - {file = "grpcio-1.59.0.tar.gz", hash = "sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f"}, + {file = "grpcio-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:d2fa68a96a30dd240be80bbad838a0ac81a61770611ff7952b889485970c4c71"}, + {file = "grpcio-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:cf0dead5a2c5a3347af2cfec7131d4f2a2e03c934af28989c9078f8241a491fa"}, + {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e420ced29b5904cdf9ee5545e23f9406189d8acb6750916c2db4793dada065c6"}, + {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b230028a008ae1d0f430acb227d323ff8a619017415cf334c38b457f814119f"}, + {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4a3833c0e067f3558538727235cd8a49709bff1003200bbdefa2f09334e4b1"}, + {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6b25ed37c27e652db01be341af93fbcea03d296c024d8a0e680017a268eb85dd"}, + {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73abb8584b0cf74d37f5ef61c10722adc7275502ab71789a8fe3cb7ef04cf6e2"}, + {file = "grpcio-1.59.2-cp310-cp310-win32.whl", hash = "sha256:d6f70406695e3220f09cd7a2f879333279d91aa4a8a1d34303b56d61a8180137"}, + {file = "grpcio-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:3c61d641d4f409c5ae46bfdd89ea42ce5ea233dcf69e74ce9ba32b503c727e29"}, + {file = "grpcio-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:3059668df17627f0e0fa680e9ef8c995c946c792612e9518f5cc1503be14e90b"}, + {file = "grpcio-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:72ca2399097c0b758198f2ff30f7178d680de8a5cfcf3d9b73a63cf87455532e"}, + {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c978f864b35f2261e0819f5cd88b9830b04dc51bcf055aac3c601e525a10d2ba"}, + {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9411e24328a2302e279e70cae6e479f1fddde79629fcb14e03e6d94b3956eabf"}, + {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb7e0fe6ad73b7f06d7e2b689c19a71cf5cc48f0c2bf8608469e51ffe0bd2867"}, + {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2504eed520958a5b77cc99458297cb7906308cb92327f35fb7fbbad4e9b2188"}, + {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2171c39f355ba5b551c5d5928d65aa6c69807fae195b86ef4a7d125bcdb860a9"}, + {file = "grpcio-1.59.2-cp311-cp311-win32.whl", hash = "sha256:d2794f0e68b3085d99b4f6ff9c089f6fdd02b32b9d3efdfbb55beac1bf22d516"}, + {file = "grpcio-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:2067274c88bc6de89c278a672a652b4247d088811ece781a4858b09bdf8448e3"}, + {file = "grpcio-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:535561990e075fa6bd4b16c4c3c1096b9581b7bb35d96fac4650f1181e428268"}, + {file = "grpcio-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:a213acfbf186b9f35803b52e4ca9addb153fc0b67f82a48f961be7000ecf6721"}, + {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:6959fb07e8351e20501ffb8cc4074c39a0b7ef123e1c850a7f8f3afdc3a3da01"}, + {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e82c5cf1495244adf5252f925ac5932e5fd288b3e5ab6b70bec5593074b7236c"}, + {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023088764012411affe7db183d1ada3ad9daf2e23ddc719ff46d7061de661340"}, + {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:da2d94c15f88cd40d7e67f7919d4f60110d2b9d5b1e08cf354c2be773ab13479"}, + {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6009386a2df66159f64ac9f20425ae25229b29b9dd0e1d3dd60043f037e2ad7e"}, + {file = "grpcio-1.59.2-cp312-cp312-win32.whl", hash = "sha256:75c6ecb70e809cf1504465174343113f51f24bc61e22a80ae1c859f3f7034c6d"}, + {file = "grpcio-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:cbe946b3e6e60a7b4618f091e62a029cb082b109a9d6b53962dd305087c6e4fd"}, + {file = "grpcio-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:f8753a6c88d1d0ba64302309eecf20f70d2770f65ca02d83c2452279085bfcd3"}, + {file = "grpcio-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:f1ef0d39bc1feb420caf549b3c657c871cad4ebbcf0580c4d03816b0590de0cf"}, + {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c93f4abbb54321ee6471e04a00139c80c754eda51064187963ddf98f5cf36a4"}, + {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08d77e682f2bf730a4961eea330e56d2f423c6a9b91ca222e5b1eb24a357b19f"}, + {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff16d68bf453275466a9a46739061a63584d92f18a0f5b33d19fc97eb69867c"}, + {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4abb717e320e74959517dc8e84a9f48fbe90e9abe19c248541e9418b1ce60acd"}, + {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36f53c2b3449c015880e7d55a89c992c357f176327b0d2873cdaaf9628a37c69"}, + {file = "grpcio-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cc3e4cd087f07758b16bef8f31d88dbb1b5da5671d2f03685ab52dece3d7a16e"}, + {file = "grpcio-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:27f879ae604a7fcf371e59fba6f3ff4635a4c2a64768bd83ff0cac503142fef4"}, + {file = "grpcio-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:7cf05053242f61ba94014dd3a986e11a083400a32664058f80bf4cf817c0b3a1"}, + {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e1727c1c0e394096bb9af185c6923e8ea55a5095b8af44f06903bcc0e06800a2"}, + {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d573e70a6fe77555fb6143c12d3a7d3fa306632a3034b4e7c59ca09721546f8"}, + {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31176aa88f36020055ace9adff2405a33c8bdbfa72a9c4980e25d91b2f196873"}, + {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11168ef43e4a43ff1b1a65859f3e0ef1a173e277349e7fb16923ff108160a8cd"}, + {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:53c9aa5ddd6857c0a1cd0287225a2a25873a8e09727c2e95c4aebb1be83a766a"}, + {file = "grpcio-1.59.2-cp38-cp38-win32.whl", hash = "sha256:3b4368b33908f683a363f376dfb747d40af3463a6e5044afee07cf9436addf96"}, + {file = "grpcio-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:0a754aff9e3af63bdc4c75c234b86b9d14e14a28a30c4e324aed1a9b873d755f"}, + {file = "grpcio-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:1f9524d1d701e399462d2c90ba7c193e49d1711cf429c0d3d97c966856e03d00"}, + {file = "grpcio-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:f93dbf58f03146164048be5426ffde298b237a5e059144847e4940f5b80172c3"}, + {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6da6dea3a1bacf99b3c2187e296db9a83029ed9c38fd4c52b7c9b7326d13c828"}, + {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5f09cffa619adfb44799fa4a81c2a1ad77c887187613fb0a8f201ab38d89ba1"}, + {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35aa9657f5d5116d23b934568e0956bd50c615127810fffe3ac356a914c176a"}, + {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:74100fecaec8a535e380cf5f2fb556ff84957d481c13e54051c52e5baac70541"}, + {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:128e20f57c5f27cb0157e73756d1586b83c1b513ebecc83ea0ac37e4b0e4e758"}, + {file = "grpcio-1.59.2-cp39-cp39-win32.whl", hash = "sha256:686e975a5d16602dc0982c7c703948d17184bd1397e16c8ee03511ecb8c4cdda"}, + {file = "grpcio-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:242adc47725b9a499ee77c6a2e36688fa6c96484611f33b1be4c57ab075a92dd"}, + {file = "grpcio-1.59.2.tar.gz", hash = "sha256:d8f9cd4ad1be90b0cf350a2f04a38a36e44a026cac1e036ac593dc48efe91d52"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.59.0)"] +protobuf = ["grpcio-tools (>=1.59.2)"] [[package]] name = "grpcio-tools" -version = "1.59.0" +version = "1.59.2" description = "Protobuf code generator for gRPC" optional = false python-versions = ">=3.7" files = [ - {file = "grpcio-tools-1.59.0.tar.gz", hash = "sha256:aa4018f2d8662ac4d9830445d3d253a11b3e096e8afe20865547137aa1160e93"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:882b809b42b5464bee55288f4e60837297f9618e53e69ae3eea6d61b05ce48fa"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:4499d4bc5aa9c7b645018d8b0db4bebd663d427aabcd7bee7777046cb1bcbca7"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f381ae3ad6a5eb27aad8d810438937d8228977067c54e0bd456fce7e11fdbf3d"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1c684c0d9226d04cadafced620a46ab38c346d0780eaac7448da96bf12066a3"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40cbf712769242c2ba237745285ef789114d7fcfe8865fc4817d87f20015e99a"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1df755951f204e65bf9232a9cac5afe7d6b8e4c87ac084d3ecd738fdc7aa4174"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:de156c18b0c638aaee3be6ad650c8ba7dec94ed4bac26403aec3dce95ffe9407"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-win32.whl", hash = "sha256:9af7e138baa9b2895cf1f3eb718ac96fc5ae2f8e31fca405e21e0e5cd1643c52"}, - {file = "grpcio_tools-1.59.0-cp310-cp310-win_amd64.whl", hash = "sha256:f14a6e4f700dfd30ff8f0e6695f944affc16ae5a1e738666b3fae4e44b65637e"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:db030140d0da2368319e2f23655df3baec278c7e0078ecbe051eaf609a69382c"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:eeed386971bb8afc3ec45593df6a1154d680d87be1209ef8e782e44f85f47e64"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:962d1a3067129152cee3e172213486cb218a6bad703836991f46f216caefcf00"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26eb2eebf150a33ebf088e67c1acf37eb2ac4133d9bfccbaa011ad2148c08b42"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b2d6da553980c590487f2e7fd3ec9c1ad8805ff2ec77977b92faa7e3ca14e1f"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:335e2f355a0c544a88854e2c053aff8a3f398b84a263a96fa19d063ca1fe513a"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:204e08f807b1d83f5f0efea30c4e680afe26a43dec8ba614a45fa698a7ef0a19"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-win32.whl", hash = "sha256:05bf7b3ed01c8a562bb7e840f864c58acedbd6924eb616367c0bd0a760bdf483"}, - {file = "grpcio_tools-1.59.0-cp311-cp311-win_amd64.whl", hash = "sha256:df85096fcac7cea8aa5bd84b7a39c4cdbf556b93669bb4772eb96aacd3222a4e"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:240a7a3c2c54f77f1f66085a635bca72003d02f56a670e7db19aec531eda8f78"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:6119f62c462d119c63227b9534210f0f13506a888151b9bf586f71e7edf5088b"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:387662bee8e4c0b52cc0f61eaaca0ca583f5b227103f685b76083a3590a71a3e"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0da5861ee276ca68493b217daef358960e8527cc63c7cb292ca1c9c54939af"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f0806de1161c7f248e4c183633ee7a58dfe45c2b77ddf0136e2e7ad0650b1b"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c683be38a9bf4024c223929b4cd2f0a0858c94e9dc8b36d7eaa5a48ce9323a6f"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-win32.whl", hash = "sha256:2ee960904dde12a7fa48e1591a5b3eeae054bdce57bacf9fd26685a98138f5bf"}, - {file = "grpcio_tools-1.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:71cc6db1d66da3bc3730d9937bddc320f7b1f1dfdff6342bcb5741515fe4110b"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-linux_armv7l.whl", hash = "sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:b8e95d921cc2a1521d4750eedefec9f16031457920a6677edebe9d1b2ad6ae60"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:cb63055739808144b541986291679d643bae58755d0eb082157c4d4c04443905"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c4634b3589efa156a8d5860c0a2547315bd5c9e52d14c960d716fe86e0927be"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d970aa26854f535ffb94ea098aa8b43de020d9a14682e4a15dcdaeac7801b27"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:821dba464d84ebbcffd9d420302404db2fa7a40c7ff4c4c4c93726f72bfa2769"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0548e901894399886ff4a4cd808cb850b60c021feb4a8977a0751f14dd7e55d9"}, - {file = "grpcio_tools-1.59.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bb87158dbbb9e5a79effe78d54837599caa16df52d8d35366e06a91723b587ae"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:1d551ff42962c7c333c3da5c70d5e617a87dee581fa2e2c5ae2d5137c8886779"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:4ee443abcd241a5befb05629013fbf2eac637faa94aaa3056351aded8a31c1bc"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:520c0c83ea79d14b0679ba43e19c64ca31d30926b26ad2ca7db37cbd89c167e2"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fc02a6e517c34dcf885ff3b57260b646551083903e3d2c780b4971ce7d4ab7c"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aec8a4ed3808b7dfc1276fe51e3e24bec0eeaf610d395bcd42934647cf902a3"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99b3bde646720bbfb77f263f5ba3e1a0de50632d43c38d405a0ef9c7e94373cd"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51d9595629998d8b519126c5a610f15deb0327cd6325ed10796b47d1d292e70b"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-win32.whl", hash = "sha256:bfa4b2b7d21c5634b62e5f03462243bd705adc1a21806b5356b8ce06d902e160"}, - {file = "grpcio_tools-1.59.0-cp38-cp38-win_amd64.whl", hash = "sha256:9ed05197c5ab071e91bcef28901e97ca168c4ae94510cb67a14cb4931b94255a"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:498e7be0b14385980efa681444ba481349c131fc5ec88003819f5d929646947c"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b519f2ecde9a579cad2f4a7057d5bb4e040ad17caab8b5e691ed7a13b9db0be9"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:ef3e8aca2261f7f07436d4e2111556c1fb9bf1f9cfcdf35262743ccdee1b6ce9"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a7f226b741b2ebf7e2d0779d2c9b17f446d1b839d59886c1619e62cc2ae472"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:784aa52965916fec5afa1a28eeee6f0073bb43a2a1d7fedf963393898843077a"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e312ddc2d8bec1a23306a661ad52734f984c9aad5d8f126ebb222a778d95407d"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:868892ad9e00651a38dace3e4924bae82fc4fd4df2c65d37b74381570ee8deb1"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-win32.whl", hash = "sha256:a4f6cae381f21fee1ef0a5cbbbb146680164311157ae618edf3061742d844383"}, - {file = "grpcio_tools-1.59.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a10e59cca462208b489478340b52a96d64e8b8b6f1ac097f3e8cb211d3f66c0"}, + {file = "grpcio-tools-1.59.2.tar.gz", hash = "sha256:75905266cf90f1866b322575c2edcd4b36532c33fc512bb1b380dc58d84b1030"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b2885c0e2c9a97bde33497a919032afbd8b5c6dc2f8d4dd4198e77226e0de05"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f410375830a9bb7140a07da4d75bf380e0958377bed50d77d1dae302de4314e"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e21fc172522d2dda815223a359b2aca9bc317a1b5e5dea5a58cd5079333af133"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:072a7ce979ea4f7579c3c99fcbde3d1882c3d1942a3b51d159f67af83b714cd8"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38f8edb2909702c2478b52f6213982c21e4f66f739ac953b91f97863ba2c06a"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12fdee2de80d83eadb1294e0f8a0cb6cefcd2e4988ed680038ab09cd04361ee4"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a3cb707da722a0b6c4021fc2cc1c005a8d4037d8ad0252f93df318b9b8a6b4f3"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-win32.whl", hash = "sha256:ec2fbb02ebb9f2ae1b1c69cccf913dee8c41f5acad94014d3ce11b53720376e3"}, + {file = "grpcio_tools-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0dc271a200dbab6547b2c73fcbdb7efe94c31cb633aa20d073f7cf4493493e1"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:d634b65cc8ee769edccf1647d8a16861a27e0d8cbd787c711168d2c5e9bddbd1"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:b0b712acec00a9cbc2204c271d638062a2cb8ce74f25d158b023ff6e93182659"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:dd5c78f8e7c6e721b9009c92481a0e3b30a9926ef721120723a03b8a34a34fb9"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:724f4f0eecc17fa66216eebfff145631070f04ed7fb4ddf7a7d1c4f954ecc2a1"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec33ddee691e60511e2a7c793aad4cf172ae20e08d95c786cbba395f6203a7"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa1b9dee7811fad081816e884d063c4dd4946dba61aa54243b4c76c311090c48"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba8dba19e7b2b6f7369004533866f222ba483b9e14d2d152ecf9339c0df1283a"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-win32.whl", hash = "sha256:df35d145bc2f6e5f57b74cb69f66526675a5f2dcf7d54617ce0deff0c82cca0a"}, + {file = "grpcio_tools-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:99ddc0f5304071a355c261ae49ea5d29b9e9b6dcf422dfc55ada70a243e27e8f"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:670f5889853215999eb3511a623dd7dff01b1ce1a64610d13366e0fd337f8c79"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:1e949e66d4555ce319fd7acef90df625138078d8729c4dc6f6a9f05925034433"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:09d809ca88999b2578119683f9f0f6a9b42de95ea21550852114a1540b6a642c"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0925545180223fabd6da9b34513efac83aa16673ef8b1cb0cc678e8cf0923c"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2ccb59dfbf2ebd668a5a7c4b7bb2b859859641d2b199114b557cd045aac6102"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:12cc7698fad48866f68fdef831685cb31ef5814ac605d248c4e5fc964a6fb3f6"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:55c401599d5093c4cfa83b8f0ee9757b4d6d3029b10bd67be2cffeada7a44961"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-win32.whl", hash = "sha256:896f5cdf58f658025a4f7e4ea96c81183b4b6a4b1b4d92ae66d112ac91f062f1"}, + {file = "grpcio_tools-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:b53db1523015a3acda75722357df6c94afae37f6023800c608e09a5c05393804"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:d08b398509ea4d544bcecddd9a21f59dc556396916c3915904cac206af2db72b"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:09749e832e06493841000275248b031f7154665900d1e1b0e42fc17a64bf904d"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:e972746000aa192521715f776fab617a3437bed29e90fe0e0fd0d0d6f498d7d4"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cbeeb3d8ec4cb25c92e17bfbdcef3c3669e85c5ee787a6e581cb942bc0ae2b88"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed8e6632d8d839456332d97b96db10bd2dbf3078e728d063394ac2d54597ad80"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:531f87c8e884c6a2e58f040039dfbfe997a4e33baa58f7c7d9993db37b1f5ad0"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:feca316e17cfead823af6eae0fc20c0d5299a94d71cfb7531a0e92d050a5fb2f"}, + {file = "grpcio_tools-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41b5dd6a06c2563ac3b3adda6d875b15e63eb7b1629e85fc9af608c3a76c4c82"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:7ec536cdae870a74080c665cfb1dca8d0784a931aa3c26376ef971a3a51b59d4"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:9c106ebbed0db446f59f0efe5c3fce33a0a21bf75b392966585e4b5934891b92"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:32141ef309543a446337e934f0b7a2565a6fca890ff4e543630a09ef72c8d00b"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f2ce5ecd63c492949b03af73b1dd6d502c567cc2f9c2057137e518b0c702a01"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9ce2a209871ed1c5ae2229e6f4f5a3ea96d83b7871df5d9773d72a72545683"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7f0e26af7c07bfa906c91ca9f5932514928a7f032f5f20aecad6b5541037de7e"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:48782727c5cff8b8c96e028a8a58614ff6a37eadc0db85866516210c7aafe9ae"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-win32.whl", hash = "sha256:4a1810bc5de51cc162a19ed3c11da8ddc64d8cfcba049ef337c20fcb397f048b"}, + {file = "grpcio_tools-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:3cf9949a2aadcece3c1e0dd59249aea53dbfc8cc94f7d707797acd67cf6cf931"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:f52e0ce8f2dcf1f160c847304016c446075a83ab925d98933d4681bfa8af2962"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:eb597d6bf9f5bfa54d00546e828f0d4e2c69250d1bc17c27903c0c7b66372135"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:17ef468836d7cf0b2419f4d5c7ac84ec2d598a1ae410773585313edacf7c393e"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dee5f7e7a56177234e61a483c70ca2ae34e73128372c801bb7039993870889f1"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f50ff312b88918c5a6461e45c5e03869749a066b1c24a7327e8e13e117efe4fc"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a85da4200295ee17e3c1ae068189a43844420ed7e9d531a042440f52de486dfb"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f518f22a3082de00f0d7a216e96366a87e6973111085ba1603c3bfa7dba2e728"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-win32.whl", hash = "sha256:6e735a26e8ea8bb89dc69343d1d00ea607449c6d81e21f339ee118562f3d1931"}, + {file = "grpcio_tools-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:3491cb69c909d586c23d7e6d0ac87844ca22f496f505ce429c0d3301234f2cf3"}, ] [package.dependencies] -grpcio = ">=1.59.0" +grpcio = ">=1.59.2" protobuf = ">=4.21.6,<5.0dev" setuptools = "*" @@ -858,13 +853,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.19.2" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.19.2-py3-none-any.whl", hash = "sha256:eee9e502c788e89cb166d4d37f43084e3b64ab405c795c03d343a4dbc2c810fc"}, + {file = "jsonschema-4.19.2.tar.gz", hash = "sha256:c9ff4d7447eed9592c23a12ccee508baf0dd0d59650615e847feb6cdca74f392"}, ] [package.dependencies] @@ -918,16 +913,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -987,21 +972,21 @@ files = [ [[package]] name = "networkx" -version = "3.1" +version = "3.2.1" description = "Python package for creating and manipulating graphs and networks" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, ] [package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "openmetadata-ingestion" @@ -1153,24 +1138,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "protobuf" -version = "4.24.4" +version = "4.25.0" description = "" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, - {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, - {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, - {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, - {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, - {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, - {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, - {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, - {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, - {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, - {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, + {file = "protobuf-4.25.0-cp310-abi3-win32.whl", hash = "sha256:5c1203ac9f50e4853b0a0bfffd32c67118ef552a33942982eeab543f5c634395"}, + {file = "protobuf-4.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:c40ff8f00aa737938c5378d461637d15c442a12275a81019cc2fef06d81c9419"}, + {file = "protobuf-4.25.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:cf21faba64cd2c9a3ed92b7a67f226296b10159dbb8fbc5e854fc90657d908e4"}, + {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:32ac2100b0e23412413d948c03060184d34a7c50b3e5d7524ee96ac2b10acf51"}, + {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:683dc44c61f2620b32ce4927de2108f3ebe8ccf2fd716e1e684e5a50da154054"}, + {file = "protobuf-4.25.0-cp38-cp38-win32.whl", hash = "sha256:1a3ba712877e6d37013cdc3476040ea1e313a6c2e1580836a94f76b3c176d575"}, + {file = "protobuf-4.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:b2cf8b5d381f9378afe84618288b239e75665fe58d0f3fd5db400959274296e9"}, + {file = "protobuf-4.25.0-cp39-cp39-win32.whl", hash = "sha256:63714e79b761a37048c9701a37438aa29945cd2417a97076048232c1df07b701"}, + {file = "protobuf-4.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:d94a33db8b7ddbd0af7c467475fb9fde0c705fb315a8433c0e2020942b863a1f"}, + {file = "protobuf-4.25.0-py3-none-any.whl", hash = "sha256:1a53d6f64b00eecf53b65ff4a8c23dc95df1fa1e97bb06b8122e5a64f49fc90a"}, + {file = "protobuf-4.25.0.tar.gz", hash = "sha256:68f7caf0d4f012fd194a301420cf6aa258366144d814f358c5b32558228afa7c"}, ] [[package]] @@ -1320,13 +1303,13 @@ rsa = ["cryptography"] [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -1783,66 +1766,43 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.49" +version = "1.4.50" description = "Database Abstraction Library" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca46de16650d143a928d10842939dab208e8d8c3a9a8757600cae9b7c579c5cd"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f23755c384c2969ca2f7667a83f7c5648fcf8b62a3f2bbd883d805454964a800"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8396e896e08e37032e87e7fbf4a15f431aa878c286dc7f79e616c2feacdb366c"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66da9627cfcc43bbdebd47bfe0145bb662041472393c03b7802253993b6b7c90"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win32.whl", hash = "sha256:9a06e046ffeb8a484279e54bda0a5abfd9675f594a2e38ef3133d7e4d75b6214"}, - {file = "SQLAlchemy-1.4.49-cp312-cp312-win_amd64.whl", hash = "sha256:7cf8b90ad84ad3a45098b1c9f56f2b161601e4670827d6b892ea0e884569bd1d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc22807a7e161c0d8f3da34018ab7c97ef6223578fcdd99b1d3e7ed1100a5db"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:393cd06c3b00b57f5421e2133e088df9cabcececcea180327e43b937b5a7caa5"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ab792ca493891d7a45a077e35b418f68435efb3e1706cb8155e20e86a9013c"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:738d7321212941ab19ba2acf02a68b8ee64987b248ffa2101630e8fccb549e0d"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"}, + {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"}, ] [package.dependencies] greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] @@ -1916,13 +1876,13 @@ widechars = ["wcwidth"] [[package]] name = "tblib" -version = "2.0.0" +version = "3.0.0" description = "Traceback serialization library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tblib-2.0.0-py3-none-any.whl", hash = "sha256:9100bfa016b047d5b980d66e7efed952fbd20bd85b56110aaf473cb97d18709a"}, - {file = "tblib-2.0.0.tar.gz", hash = "sha256:a6df30f272c08bf8be66e0775fad862005d950a6b8449b94f7c788731d70ecd7"}, + {file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"}, + {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"}, ] [[package]] @@ -2052,5 +2012,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "3417bef1d6d37b2d5e54e6d771526c308e732e5f54b3db4f038b046332bdb180" +python-versions = "^3.10.0" +content-hash = "d01687366c4750047425c9b7d9248b91b85fd277c9949d7149519b2a0c9c2892" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 4887d5a4..f5d5a576 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.1.1" +version = "0.2.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" @@ -9,7 +9,9 @@ packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] python = "^3.10.0" -openmetadata-ingestion = "^1.1.7.0" +# pinned to this version as was installing a later vesion and giving errors, seems a breaking +# change introduced inadvertently +openmetadata-ingestion = "1.1.7.0" # 1.5.4 doesn't install for some reason. Either way it will be removed from the next release of the ingestion package. commonregex = "1.5.3" diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client.py index 0f92f7eb..f76c14ac 100644 --- a/lib/datahub-client/tests/test_client.py +++ b/lib/datahub-client/tests/test_client.py @@ -62,6 +62,12 @@ def mock_table_response(self, fqn): "columns": [], } + def mock_user_response(self, fqn): + return { + "email": "justice@justice.gov.uk", + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + } + @pytest.fixture def catalogue(self): return CatalogueMetadata( @@ -88,7 +94,10 @@ def table(self): return TableMetadata( name="my_table", description="bla bla", - column_types={"foo": "string", "bar": "int"}, + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], retention_period_in_days=365, ) @@ -204,7 +213,7 @@ def test_create_table(self, client, requests_mock, table): "precision": None, "scale": None, "dataTypeDisplay": None, - "description": None, + "description": "a", "fullyQualifiedName": None, "tags": None, "constraint": None, @@ -223,7 +232,7 @@ def test_create_table(self, client, requests_mock, table): "precision": None, "scale": None, "dataTypeDisplay": None, - "description": None, + "description": "b", "fullyQualifiedName": None, "tags": None, "constraint": None, @@ -259,3 +268,16 @@ def test_404_handling(self, client, requests_mock, table): client.create_or_update_table( metadata=table, schema_fqn="data-platform.data-product.schema" ) + + def test_get_user_id(self, requests_mock, client): + requests_mock.get( + "http://example.com/api/v1/users/name/justice", + json={ + "email": "justice@justice.gov.uk", + "name": "justice", + "id": "39b855e3-84a5-491e-b9a5-c411e626e340", + }, + ) + user_id = client.get_user_id("justice@justice.gov.uk") + + assert "39b855e3-84a5-491e-b9a5-c411e626e340" in str(user_id) diff --git a/lib/datahub-client/tests/test_entities.py b/lib/datahub-client/tests/test_entities.py new file mode 100644 index 00000000..d425aeae --- /dev/null +++ b/lib/datahub-client/tests/test_entities.py @@ -0,0 +1,67 @@ +import pytest +from data_platform_catalogue.entities import DataProductMetadata, TableMetadata + + +@pytest.fixture +def data_product(): + return DataProductMetadata( + name="my_data_product", + description="bla bla", + version="v1.0.0", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="legal-aid", + dpia_required=False, + tags=["test"], + ) + + +@pytest.fixture +def table(): + return TableMetadata( + name="my_table", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=None, + tags=["test"], + ) + + +def test_from_data_product_metadata_dict(data_product): + data_product2 = DataProductMetadata.from_data_product_metadata_dict( + { + "name": "my_data_product", + "description": "bla bla", + "domain": "legal-aid", + "dataProductOwner": "justice@justice.gov.uk", + "dataProductOwnerDisplayName": "justice", + "email": "justice@justice.gov.uk", + "status": "draft", + "dpiaRequired": False, + "retentionPeriod": 365, + "tags": ["test"], + }, + "v1.0.0", + "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + ) + assert data_product2 == data_product + + +def test_from_data_product_schema_dict(table): + table2 = TableMetadata.from_data_product_schema_dict( + { + "tableDescription": "bla bla", + "columns": [ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + "tags": ["test"], + }, + "my_table", + ) + + assert table2 == table From e3a0bfe37c4c9ab635ccd188a4147261d67b1a6a Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:46:05 +0000 Subject: [PATCH 07/64] Dpl add owner attribute to create database (#2277) * add owner attribute to `CatalogueMetadata` entity * pass owner through `create_or_update_database()` * updates tests * upversion poetry * fix test * udate readme and diagram * update docstrings * correction readme example --- lib/datahub-client/README.md | 27 ++- .../data_platform_catalogue/client.py | 12 +- .../data_platform_catalogue/entities.py | 1 + lib/datahub-client/diagram.png | Bin 227322 -> 235822 bytes lib/datahub-client/poetry.lock | 216 +++++++++--------- lib/datahub-client/pyproject.toml | 2 +- lib/datahub-client/tests/test_client.py | 12 +- 7 files changed, 146 insertions(+), 124 deletions(-) diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 4c75ea9a..77406b1c 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -15,10 +15,9 @@ pip install ministryofjustice-data-platform-catalogue ## Topology -- Each internal data platform catalogue is mapped to a database in the - OpenMetadata catalogue -- Each data product is mapped to a schema -- Each table is mapped to a table +- Each moj data product is mapped to a database in the OpenMetadata catalogue +- We populate the schema level in openmetdata with a generic entry of `Tables` +- Each table is mapped to a table in openmetadata ![Topology diagram](./diagram.png) @@ -39,11 +38,6 @@ client = CatalogueClient( assert client.is_healthy() -catalogue = CatalogueMetadata( - name = "data_platform", - description = "All data products hosted on the data platform", -) - data_product = DataProductMetadata( name = "my_data_product", description = "bla bla", @@ -55,6 +49,17 @@ data_product = DataProductMetadata( dpia_required = False ) +data_product_schema = DataProductMetadata( + name = "Tables", + description = "All the tables contained within my_data_product", + version = "v1.0.0", + owner = "7804c127-d677-4900-82f9-83517e51bb94", + email = "justice@justice.gov.uk", + retention_period_in_days = 365, + domain = "legal-aid", + dpia_required = False +) + table = TableMetadata( name = "my_table", description = "bla bla", @@ -64,8 +69,8 @@ table = TableMetadata( try: service_fqn = client.create_or_update_database_service(name="data_platform") - database_fqn = client.create_or_update_database(metadata=catalogue, service_fqn=service_fqn) - schema_fqn = client.create_or_update_schema(metadata=data_product, database_fqn=database_fqn) + database_fqn = client.create_or_update_database(metadata=data_product, service_fqn=service_fqn) + schema_fqn = client.create_or_update_schema(metadata=data_product_schema, database_fqn=database_fqn) table_fqn = client.create_or_update_table(metadata=table, schema_fqn=schema_fqn) except CatalogueError: print("oh no") diff --git a/lib/datahub-client/data_platform_catalogue/client.py b/lib/datahub-client/data_platform_catalogue/client.py index 282dd1bc..634c2ad7 100644 --- a/lib/datahub-client/data_platform_catalogue/client.py +++ b/lib/datahub-client/data_platform_catalogue/client.py @@ -122,23 +122,29 @@ def create_or_update_database_service( return response["fullyQualifiedName"] - def create_or_update_database(self, metadata: CatalogueMetadata, service_fqn: str): + def create_or_update_database( + self, metadata: CatalogueMetadata | DataProductMetadata, service_fqn: str + ): """ Define a database. - There should be one database per data platform catalogue. + There should be one database per data product. """ create_db = CreateDatabaseRequest( name=metadata.name, description=metadata.description, tags=self._generate_tags(metadata.tags), service=service_fqn, + owner=EntityReference(id=metadata.owner, type="user"), ) return self._create_or_update_entity(create_db) def create_or_update_schema(self, metadata: DataProductMetadata, database_fqn: str): """ Define a database schema. - There should be one schema per data product. + There should be one schema per data product and for now flexibility is retained + and metadata is of type DataProductMetadata but we'd expect a uniform name and + description for each data product, e.g: + name="Tables", description="All the tables contained within {data_product_name}" """ create_schema = CreateDatabaseSchemaRequest( name=metadata.name, diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index 5f5fbfbe..74b953c0 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -6,6 +6,7 @@ class CatalogueMetadata: name: str description: str + owner: str tags: list[str] = field(default_factory=list) diff --git a/lib/datahub-client/diagram.png b/lib/datahub-client/diagram.png index de5e82758dd7027968521747af7d7c7ebe84b88d..827fdceb8889473f89075b14591b46a6dfcadbc4 100644 GIT binary patch literal 235822 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

tQISOe^FkV^ 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

7;2 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

8vT(iu7`;CNX3xN1l^0}vRG&CSjC ziUeOvAj#;aBa0D1YckI7^Qj$H;yoA*PwqNX!F}vBw*-)SBh2wk^#`MeonQIuAZdZ9p6}sIL z_>joB?dbbckOyw4=lnA-iI-wd3`n?WRn_i~1|GV`gd{pTa5^KUS5B#4cU894UCdF^8ps z{hxDs4~8HxxeKP)mciDT=@q{Tk->d=hl@>*Bdw4{gUXI^LaM#+#**9}Ql>%VICu`n zLohp2A|Y^1@ll&VQoNZ!0Keg&6AJv(c5y#ZdlO77Njv3AMGbouLCC0gZ@5*EN9H8b z*x29@&Jlb2UZm2T_<-bpC_(qx^m$)PC)S3qrxix&5(e&oBMV2wSM_IsbzvC?1bCr4mls&=(7* zALojU$S$7YV;4y7bId(EXTOUjCKMpOMr((`RuVH$}x<%>B2`M>`)O z^yHN6KfY`Q?F3nX<}gO$9}pmd$aNyPKP~k9WZT4x!-InK2`h^n(jKCJuqj!(M&m<` zW4x>4gYv9-u+mcTMmRwROV4#D%Sg2c3xPBBxUrY{)eQZ6plmhq1L+Z?JhFIkp^az# zo<~IgHJ42*yLiI?^FlAZzPdW-#8hVI`5ZPEnr??3E@T7}2{)}Y+;>xUt^4gazHynH zU_?|vyK_eZTY@ENuIv!>C0s>IKNG1Q->;%Eer#+Rieh!kbJi(Y4kkyR9!i_n?}qvh zyPEj;28oMffAVB(3h?0Do4&)YncCQE{<%NAAI?W%zbHe3Ts27XQ}f?K*%hz*p+f;7 z7{DQYRX4c#?%p_<5g*!L(lb|x6)l+mT+3&$BlzdYRLcLCZ8hQA0&uDJf^G8f4;hi+ z#RL<1K_<%6kg+FI+%xgJ&L^lydI=Lj18?0H4{9JfI-EQI7Z-Hd`ZVM2Ciwk5i zsb*69M*S}f`fsz(Ox9O|7aD-MXHE(nE^-=CHhvmLuZ}@@efPqfPZ3U zJaVEI^{4qEN$qh{>~79Mm6G3A%v=c<#0$3kEv=a~@?gc%3=%>2Z@`d5rxtK1MtqvV zsplW4)=JY`DFo|Oit8r)--Cak`|lMu=$$c4BKz``CvW)i_KYqAD}q_@n<6}m#3a+H zP%7k45-O{|>}o_n+kemAmf%Pb8MPk$i-mv*y~W3e`d6say*eN)&Io~QaC)ZL8fQTr zGiCy?JrNypn8ByYQiOSe90Tu)3B=#BxS}!Lsik!uP+s)uY>LBFF1m3}Uk0EW^VTa= zp^E*KPRoB8kc|db#SP9oQ4u|Wu0^I77HE9yv*XhRHUH^aGbJOC$th;)7vyx-C9tzQ ziT}^vYE6K_bbAu)cTeijVZ|M0xydb5s{I(*CM&V5Orou97YRZv*vB|hzHwj_cjs(; zZ9bIy@Ap5D!HgHraWe!;#TH!oZKDsvk0gpONZpn~8({OTd}qHM&9`&Q*y~Z0+9>fS za|0HV&HO*M-Z46|?fU|)*tXHJZM$Ndoup&iPCDsyY}@MCwmY_M8!z{F@BfYW;f*uu zeBD*M&RKiSx#pTHlnaDzkmJ4tG*5%e{V)?GO@z^d$+Y~9k8ltzTA5FlddJXlNI{kl zQ&A*!uw9I}ppt{;cZ{couv^EE4NQy5ei`tl8ozH z(o44}HT5Hp1hQig&es#MNRNGblnqh0Y#m#*;#%l5zn)I&*o2Q($ z3#E78byz;+UecO~GRTps^^VoFp({XA(){_u>-5YDMS9cRIm<`wBr{(M*snUbk$0_@ zMiB|biA@Xb@$Jr3WI3LR!IAbzyfPV)aG2uS?(8D_xkA(?WjS{$HvLy*>P68=*B^iWNz>`WeUrWYQu~msiv?z~~o*l(4^)*5pr&KrWF|BhJdm3Oy`5In5{-;O2nHYGZfKME92p?Y% zt7#2`3w~W>IKw(X+)^2dRpZtEi=jpN$!YT6Qg!v$F}>y}dl(O1T3QubIF=;kgjUEP z9C-@k_>7T!jK{=z_;x@p|$p8(gb-vff=Kl2T;fdS+M;wzhUv3jWgqow>1 zeXrb}nRpZCJ?5|8mJI16H)ieOIGb{`3t|eQ#66f=4bXbJ# zNnvBELl2^<0EUB9nZ%!f+C1Kxji69lVGn zexpWVX_@><^5Sq3Cx?`>rDh!5ESYKoU1>fmnTIMQrY(gu7b7t6xJC%Hby`m8+8t_e z1`+uedjpVnVW!{kg{iel_+$fOOJ5IZOc})@=#DoR$8~fSjU2^u4F<4qUdbFTvLgrd zz$JSSU;4{pB^?_ap%RInMU`!1sKj+OGl_;9MRoWutbKq$JWB+uA(p5oJ9q(Bizxfv z4?8+QXx;~=|J#6Md!avN zq-q3hT(&3$s4=%)L0c_k;?f^Z?R%N5x->*Ov>2ZobQ9^g{Ky$p!X;JULDLXWQnuAm zKs}GdeoUi3FQ>%*%RzZR0j{g?g~9WvKwcC+A!kXpB1;;ZzZ?YdE!`dY2w!@NPB3Fy zOwUjujlh_QZ$3I;J%Svlfi{Z#PY*z4P7CU|)GDSOz z`WF_xLP9;z0Bi&3p;R;JxeT^$GBeWw%dz#8~7LfZK2Y$dce1wtu(6R3uThN zivVLnr8*)B+-f2P%uRGZR}Ypk^~m@BLs?#MP;~nHOVGsSU&3RaUZ)cW(Io=HG!n2X zJjRwo#KafQQKOIo>9XA9b>#(c{%hz#<)saIaBQS7Kt?WWl_lx$uQvkvb5tAaSdIdA zW8=eS*>^{9X=IOnp`tU)Q6le z6(iS}nF}$iEM!&ZaY%%+1@j)dHST{C7@q_%F9^UT_z1T1w)@2S225uQaail}dHZ0_ z#!8f3ifoYJJDYSVB7ToMXt#SF)ZjU|ae%8Evh8l=U0YF+Iwr%LSXp_wViL%CJW@K@ z@$s>C^u=?7qT4_D+E>1;!IjfI?{CMOwfrX;^!Ed}ze!{rGqIzt^o?+89B8RC!{Byf z+D|b~cA4Aa=}mnt%Ge0Z6d3CSjumt$!zlJfU;$xLeoXYt1wa*us`96V6$2Cv{!~~~ z8Q2Rt$>iJ;A66_wL-!{bst^~BmIhv!MQRXcUTp$=#Qjo@suEw znLVK6a4s4dFE3u}3h-$hClc(3bmjZqd4cH4hrPbtN;43X?n#Q2UXu;H+dx^B3@X4E z8ZF#qT%r4-*m(EqA3jw9zX}Z5|V~C#Dk-^m_*)XAt>BI zcV!9~RRX=j@ImtqqKIvhUSV&+b3?#e`}+^N65Q@4gxGQgWdfszbU|#!jy$zI-J8Xr z-5WUkAg`YEk~`>n%$=KCv`@M0ePC(~C*m{znFhnR^Rv&imjn$kIy5{S`;Cm8oE$Of zeg9|=jKvi-PeD;nA2D8cUq{t*b_O%?udxJBSh%ySu|~0|&bd{!SMML7{RLcu@N0~7 z2o{~a$+z}*BmzyI_)Piyuqn9tz9DYcj1h!Z+IPv|Ty~v!%CAHe1L1x#hu0ndeAYrD zFo=aTgEq~DpC!Bahlcwi;tU)Oqd~5oaxM`re)wO;$fa|Pyg$C{WqIGgXf+?9SyCuDEgJaAIXXI-j351Vyr0(?I~4r%7#U|- z9en9w9XmcTe8l_}3H{V$O)Z)bo^3x&5(fp4H=~5kN8O#k$KCxeKD}~4adrO1S+@i= z1?%1Y0UsXszDu!#@hu&URV3pa#0+9u!1qb092*LWlE;GYho-=%XYn`W5X3 z$!dk2&qfVs01jAFAiXn8`i(eb8{!LO`dPD&yoI+ZI$-@5#vq^`paF{{i+Jwb04I?B z8`H7<2fuM=wt4nZ5;TfotwV2N2Ch$U95(lusv6E;3<#UthAN~!T&fCD*&JW3 zG-H`JNn-;l}VfLA8v7(>GWT0vGiILr_&1vJJy@MaeBY=3SFyN!CSi`P}3n=hYR85z+!&=W=jr-lqB z&t7Ezo+vJcHzGF|fL z>*nT{1xJwS&31eul&exvHVOEm+Wfq-wsyq%VNlt^SGNaL`RK3B(Fnn4Ubp{+$gih6 zXLuQ^ss{kEQbsi?6?LFbI)>j7+2?>~>D^_LZxz|*_-`z|@eg=CY=l=)sTjfC;AlGT92j+FL%%id~RWg}u3*-+`GeFe@0t zDb8l7`%;XzlN$E~|Ki4?*X}=JIH9De2=qgs9=@um zHDBO@=G3@2`d_g4H)Q-5IE?X+I05ni8)3F`X$!W>==S@C)teKmYN;O*McNFhJT4I+ zbDdN1AMN#vaxRH`b0#f2qhoucjCwG@mcNTd5kPIvv<$xwkDF%|!D zT+?=dH6(rdoTBg^wQ_Z%*GFv}(D!p{z~(d4dvRJYA;3)F^_#ihQEu2A-*y2oen0RR zV*-h27*!1g8gzGQt+J{nnVB(&*>wym5T zU6UDyDem+-f3#rNe^1-LZ~njcBRHrR1b?)C)H&^|iQC_JoJS9rEiE1_#xJtm)-z%- zhLr5txrh+)9{mW1o% z^*x_4R0QrO>2ntOD$qWl*I=%|axf5s*${T$@1v5p<^N5S|G#(F7%+jo)*y7x)CnY$ zmZ#dv208U=T`(((F>-cxaY!VblGTxTe2_mx?yK#moE6EBg6;+p(h++p#?i6FjhRh` zX%50+JVivz8*a?te7eJQ62TlQq%}c7|lg#rAiVJ0c zz;Fid`Z(+Kti=RNPBymTOFy@CaHDds84|QV;b21SnChtQZc|_&tx4?GPtZUhtY}NH z@|8T|z-d5^SV=p0MLpi)`80&t_t7?BvLIY9FLg8P|IhlnLS44~yHKMQ3|RaW6p&=F z#OrG_GceN~jhX;izx9{}KFN{<)eu?O+mf!n{V7&I8H02}vVo`4LZv=!)7lH|kuB1UyLDozF~3EH z=0_>p0w+y}x^$z@WI_*&zo`+8zntSF3XazDQmcSCXKB;^YwiEZ9>J2qFzM1sR{YJ8 zWc@;^Bic$=iWPNCaXEgI7zg9w1^^)TrP77EC)@w8Br^0BbuiG&2Zgg+H^6s2o;{R* zLL|D}H`H40!IsH$u2bKwjPkgMJ z_nSFB5Dq@Z6`$OF!TjlA9DYb@EIQ(8Whu%4J`njO@K5?~6A!ugcQPc!i^QHcsezy` zbmFt?zi7an{CGEOYx-J$YT&b5Dbq2=RU--DvQlsg5)TPs$tl3@ZqrYq)2GoHZ7d1S zK#Fv6^=GW6TFen0{2ibDc2~?|J!g-Ma;D8KR9joe9%X^S*T=Z2OhT((;UDE8#xlcd zdH@8RFkA#dOBLPuK6;gyx=zfeC zE64=1)1I45-YYJ{%Pv>^2O?LpJM2w`S1CS%&Exte-%yP+vz4KgWX5>upb__v)IEl; zbh*hs$3RWd8jh}Ha){$5U*dP1hQ5!ufOfsmhW9DLx+Wz({RE#b2iO}~Nyzm6A;vK8 z694UppFm;Km#Z8BZBWGv%npVL7;q|+Q=_TH&dMqgO_-ASwxrRH zG*6yvPz3U9mFj#P9R zvT1xc#9aQJc4RB~?zcBL_1Cvp0!H+T_1PeM6?Z z2MBSgNhDbM-T~U0MyX%BfmF~SS%qqUX;d@{Tew4oJ1NlImT?)C^AEk}%4IkN``T_x z>A|-Wh37gRF4-J!oOxpi6CMURN?&V4(lq9GWxfI2rG)~ugn_sT45gy8WV#77J#Qv3 z2joC5PAyOmqa@1+69~mBPzN1B@TJaNn02GiUBD%~7tzN(cTqIsH;Tw@E=pL9{&9O( zv@es2n^B%%_FRR9Cd)1vXpwjHcZGP8@Z$xkYT^tJds_;>!aQ;Jso)7?YTiu;Lo@EL zWFP}$KgdLwX(~Q0&ah_>I+fX}hz}BP^RoRmitnmBC@$N8xGxlG{_f?9bHGbzLbS!LX zSs2f*Gr{?A|4<}^T*Z>c@~+1!q@NGd%Gkyw#;haL=_}1V@+%{|2WbHg9cC-j z-L9>)>t#J(1h2VdfF&t7BqD*L{$}^y|5M3AYNJCuvN1;|S8gQYDigjj?ZGBzY;c1z*B%qxa@!t0B30kis{b>Z{aSl+54qMbdgFbAc_pR>YE{B z<5Cl8VfqGV6sdtzEs@jrD*FQXPJF!f(GBu|b}3Ppttoj=25JOp(Z1o7wOx(TQGo-% zWAuNU>BZ`KfOO`#i|(KEuU`-=yxz48w>IxyOd^8;c!$Bplb6OY`WOMly5RPMuhb|^ zeuRHh96SjhH(IUuY#V%B2QdFl&_0LCw;tG@(F_AKhep&MyNZY0Op(wRf(bG0G3m{>pqR%h#zM^ zBLASaZsJSAC;~icszH5A!%@LYVwoN^rI4g}$zm!CQ3}|_TA0&z@B+Q1MvP-}nrv(n zlH`6XKiVdOs|3dJ@u?|G{E!}8!Uv|u&;t8r$znj@w1{)>^UL5y=ah8u{1rPmOq+N0 zCqW+A*J_?Ll5FSa54n5=J*pdmfYwgG#MJYUlJb!jw&7$&RaY#v3o(2p(S1r$K-t>i zQU6dDK@?WDs%bB<(+DYubs1~G2<=nDBNH<8YG8SL02$;H3*y*-g#Met$Eb8tGXfL7 zKlHqHjXFAnW;c0em+B|o@Q>)BUNHQ1m~3c=tU!-|@suM2SO#>3=`yDGW$ZVLT_19J zn(!VYqClEyN)o%UkGh!W`w3sL0} z$M%yAC|DITay@KP*WV|@U`QSKbmipaUUtC_tE)RMvNrYpsrjGrL3w(1o)wt&u+H~2 zT2seMpJnOjXwWj#WyueC#zv0AS0IeU2lfU&)oq}4WLEEgi1b8yf(v2(r0s&{VuLr> z12M-HEQ?)f8Y?hT!kCl9Eo{If4*NwvUjZ{{YaM|(DwCw>pcx2YEe&ENgE6Ftf$XsL zHwVoWvQ*4Vq*uN@z_ifUFZM6#Kv8z|w{3O(ImkRmlQQ_T%9sJfV|Rz=eZIXD!jMmI z=F635?U+0cRwxTV;Ha+l8YV0}Jd_LtS%)CKLhvAgxJ7~EV+Gf_z3MgcGDx{MIV$;8 z^n8xTeZNueD$!2USd`Jh=;qW%q`p7!C2S4I>isc`l#;&g&*;VW)cr0YyC~%8ooTV* zk0sD9TRQ~~S_0PXSwgHeGx$U1<;fa|6CZAnRHZ~rAF{rpAa|&2?XO6iYKHqoi@*F2 z7gl6s%ASJ*kuy_MsSTcbv1;hOJKl~J{E}Rl=-oFr4~Acf+8K*8J=4a&T8Kgs@!%Z^lLGke$8A!RxL>Ze zM!r?<Zr@Az_XiI<+%JM8E-aG$8uNy8pbw@$Kmhjzt)X3p+j<725 z)7o{tHzp+6B)zCKX`7R^b8{QTvlDbyD!?%~P`jVvnXIyD$l!}eu9zC;EXTT~Hsj1fITai!mFTFaLcwkF@ zM^Mb|jkZ*qw-fT$h-BW+!5_hn-u&xOWhmV4*%bubHE za!p?XGUjXJ%ex}#VSJCzWI(cJ_a0X5k_QyoJ*+m;Ypsu2Ja$GXgUe{Xg+(WD2y|dK zUy}`oy5JdF+80)6H;9~A;U!!^r%<-=v*x>df-fG0dVF^`ho+K4d>xBO>{j^&I~h%cePJW5=^v!DJGL%_+?CM}j2yRKkZ)E=|O!UGl4* zv3ZLRC=DFs^=boeuhwiF2RrcK=pzi33&eIBqswmjS!ra{pvqSxK_j>lD4tX~`uV_d z1qZjlf;6eN73<*MNroU5K*&M12G~1sR@}dm5eq@SkId-0S|j;i($JVHb5_)MhxdEDM7Kmj(uhi9K$3LY9Jlt1P?Emq76*3Wef z!`VP8Xr)43GU~sq=C4~sM!47%M2=$|MSC-UvO`+V2RlzAHAL6h1+eMC7Hq2;<*mf0 z4T4W#zi^BdRJrf6lxeU-lW+chLPfLbPbaL;FJY|O71axyo2ZU$O=MSo9*3mvbPhI= z8+ZzOsX=BpsCd_}mi3#BTkml&e8pe-23S)clMik`;PwNboRv?ZK%w9_OQQVWs?lshU^ihOMM5n*ikqq>ReE7Ra5#@sIXdvY%jqM;BRn67v7aWot&-VJa;-yOVoh z@SUQY%P+?A2fkX)R_o5ly)uH{RZ;|nNg%E)6%|ZO+gA?;If7t$Y@qR&Nw>xju|g5a zuc%y9Ablo+1jE8|i|-)-gxSOHh3Eq01%}fi51TL62bnmX_uQtHaI)KJi|L27I~eJk07 z2Mo735ksA8u~9u|KvBLkEU3W!*e;TwTWIm!0%+QLB6W#vW%c)P+9DLJAP}iZFTc|k zSn8+N8wryaPHHNJ>pgzCa55XfPU4f{41G6Zr%QA;df=YU8-?v@7jgKsG2Q%w z20p9AGCAY%x?NxGSgf`X63S9qh8n2nDK$H*gh+pZuE#Bnd#zUTw%wq7tDh>Cn5DalveR3xGn&`d!u@f#LL;X9mSNZRW&Jf3NaI#kaL zMlyqS?WF5*eV~phY5MMzH*XXTuGk`-vE0A;_xX$;Pa|oP=-OPM38!cD+11>FWM$ zecv`K%9Ve&EDS<55AL0`Cm-^ZEeF?$<2u#xIIQ=$K}{*2P3^Ysle~w8?oV`XnR@iW z{Wvz|`@l*~%NVc7B;KXX|1`Y0Ut$~;!}=pLcdAnjZx_;F^JB6}6~S~8>`?}HFy_+t z)2r2RHVTDz-oJytZbM@=d7NwHOS^!?v!GVH?IwdR1s-H11u%-h5DCkh4ISO8j!wMt zSB0piS^oBucqzBeCq8EZilII_1)cv9!?mRORJO85TVbh%B9zj?PC!;t8_pOKe=ZJY zp6MfMGL3tcKD6f^0Oe}Z4mpQMv%SeCT;$^5cVWTH>hZIg86+bZ@EMj_BmU%I9RRqy z@*{X4cQI*lB(xDPUK|ImQ(#gc{TdBurXOC)u}Y7*2Bn0kNd?DENb4S0@u|G9Rtk_P zfeZED?^^guLl`^f~_0n^2HnJraFx#TAEeOSYLx<`~#-JU|^7; zEf`5i$o3#mMMPva{7OKK@tCT%2p?&5;?(HMA8A`R#+=+fr`#Tz+@dFIYbxR${z^Z- ztz4X)o&EDUJ3~|83wQ>$!5uEQEaP7}6%jcwaw(;MI3leD$VqX?@YJLPC$-%bN536= za{1xLe{XfKw&H1j!rv?uBx0!sXN->b{S@J%iJJZkV`h@)jo*}_zIfN=%+;>(X0+zM zu(77e(e+qQ=M)&T`bOO`bIb_j_0DeB@GQK4G}p}BD9}iSbYGb78sG|ZNaQ-*2bn;@ zMvVgX9)fYdIs-dlL#Eyme;|{eksn;9;{kD8xnj8m`LSZd^9)ToqsszJ(CoC z%C+goKR=tD`eUI|wBkE1(|R5HWxhIgRuHe?h&`h^;H%B=kY}@AJCLP)pYVA740o_g zZOXw0XNn3-vuedqf@vhX!sN#x114XJYX_cIMN(uz2Fxo&Kq{7-$hm z_#Mrpk6p)RELlS*BHTb&;RLx--du}mu zL88fjXG%=PI!OFhZy#)iY8zy`PD5lIT;%nM7A6p-pdT0*V3uDuzJMU&_orXUOnfl#w8DWU&D-)CKEzlJ`!&TWWeJ&dqx;lCv`3W#Z zIiaChwA_E#*m;$&dpu@7jMrHml~>-Wt%U~a2AB6}H>(>CKKzaLOF&4SYKB=2dK5G? z%kpjAuS?|(O<@q|qNoCbNwwBvS=4k)rTMn4KD@cd-(MO7Gv^0B0({H4T=L{T^TpMj zEejF0RF+wo-g7=WPzD|xgoXqn&`AaDI$8$@F{@{^1Cv%9B!nB~K4y2<6>c{^H%x54 zC~PZ$;ADbDj@hv5=M=>wia`hkdmA4oamF+m?2~?HzGgx)C{Lrfo@Cfk<2MET_xHrR zvwbO1sZosBE;qBV^Opmj)z7Y1u6!7fB*+!PC96a;xq51%!J?PB>&d=)kflVsE866+ zfv%kJ+3x2!&V=p8xTJZwO;`|!8~7$&@gKh4rYhbnH?3mOZiUERq=OiTn)?{D6?+vl zv@Ji<&)@z^P#}Prk1j*&#{@xTC!D{HI4vyBSj7vrcC%dnCK{`#Lc7@>ymg|BddJWA zSJ}cK_aEZHxv+*Z5@Vk1sfy(uIPKXv(hDZ)O5T~N@Dyp_&(yZ@SblCao6^^5II+-i zc(LgFL4`X_j1W`QeXywff%Lt!6(z>DjvX zN7QAAc>7wdc=swO`joDEmg%PyYXL_tOZcQu86~QT@GN?-DrTUn^gd;j{YZd(npoy9 znBD*L0=VOu+|7VQZLQUEo%hduY=PLdzQ((;-e%Dqk1b|GvQe~&9b$$)@neQp4oK6j z)^AvWxGKAm_WE*P&HKHtyeQ0C)oj{~@4eWz0l}|g8&%uo+vP>091Tw|C=~fFey+K4 zWm=r^{nG2w>m1JSRov^xg!XESQ!>47^OXl5ae*(w8%4qWub-mW<`)ew1{XCxi`I<+ z>bkF`m9tQk2F$Aze&zv06a2%aNxStCQm?<*A|J~0?9g>U`;)hm2Qi=T?7fs;^C!xM z%l#7d_y%CH5|%eU*UTF}+tLxky|{T)%Z$52t_0*|GVY*OGGOuxz!|)Y^$bFl-zMp1 z#<*l3gPiN{+(A7{n!8wM^Kk+?307d~6+Z{Ox0qQ7Rz&&P`Sqf&GQqlf586m)i@JPr zE%FM`sQ2&t8wTh8Nab+QZEK=d7)s#U^K$fT2e-HTKtC3&Q-fs>$2Z*&e6N@`vAxNr z`;JjGZ0T3KtnJpItuENO&E<%_C3e2D>bf1zH&#|%#5ez3pu!*GNT&50VPhz#{Rh=m z1bX*84El~sMJd7d;OUq8;?^4wgJ)e~+~WwVFs+c9RSq z>FQ7f-&UmXa;+;o1SZws03;} zVWr;UU_XVN2s@#8m#O?}i7? zSb?I9on2A22Vcoj2x%9oK8t;;bs{lvh~N8=$0Z&S@jTpD*A-uIio_-)U`$+=6N__e zrAnjxi%`S1OltMHb&dy)PjQJM-X)OYL9-tASZ0+N|1Qz^aP9j#Cz!m_kMraXc`QVW z;Q#H3yM*~EmOHg^qtfFC!Viko^BWn4CX3l(kIp}Su$(Zv5v%L-Hf?SfgY_oma67&F zuk3qKiW`yWoaWu47QZx?c|1mxH!JARUoP~#^y@&IM;X#urbP$JzTER(DzBToDnm^_ z6N2rO@D^?2H^X>`+@@TsQPaR%IKv8j!?sH*r`^8uM+&B$Tn|bjj-Ny(cvONu~To+@_kZxuWaiU?4$6p?le^xRao&c|Fq=n%RH~C!A&2I z9n?1{qYAtQTq$SIK4PO&4&wO`ZTB~KEm{TEI+LOo z))@UU1SCp5ZCpruIu2IT8;I%8Ru!ZvUyO&_E@R*)>OkH;Eo0%bv9NL=#XT{brm5Z| zF4>i?ylAa#{JiM6EB8&nNp)AosZTSotNTbI48G#_O)CP1$9CU360DQr^Y7pXd8_{K zv5uAoB7x5F;>zavlOLDP7HI=5U~msGpS&QUK={pGALf_taArGptrVlLjV1Y^hnc;| zKgv64UJds%MWlS57HoB1RkN*PR{uUpFWqvDx&#{#`3Jct8G?+YMg$A^MMV;O!yDsH zlRa5@qroW)F&=m{D}h=-C^*EK5K{IZF3cXMlS&<7;LSQ2nllj4D7S%IZ=B+}18K+d z;bIWMa`hblq+6FnUYRP$4A#P&MW<3eNvY5ya2I7qX$baqu-%I+r1Id9z}-sm12aoUj}i|e+3_gFyAi3f^ChfrT`4?iT}N4z!7am zV7qfmm^kkW{}zv9O1sUiDD(9v(D6}TDOH8Fqc)Q%1;&IK#zQK0BQQeDkIC_12>*0{ zl{2KR94#5I%aL=SM(f}QThL_n2QWMkb4gN`c-tDoKjC^`$_#gI?ZEW}OiU5erYnVP zZ0KV(b5?8`TB!>fTczMv>vqh9*d{g>Ufvr=azDqpv#8^GwsGOB1mY-=M zo_p+i9KsW6UE;K3`j+%5Rs)UZ8oA!9pD%pWgNnmDlLU%g)WsX+yocz@J%_BlJz3)^ zvXaboXQlGz)YvfA@i+dKfAkge{4Di31s4y=;q&*~%Cg;&JaE7@OFIAXn|fy6XZaHg zYWS(wMop;=q`=q0XGa82WXVlLJHNW->P3%j{F4c*i`?phwPN?u#I;diV7wZ2Nh&M+ z)B%1SgY8`H`Tf4~z7zMvB;RG*OecbVPMG5(V}#>eA-ZlYHN~f_vh;N3tAN-aDOLVR zV$bSLv`O$@ZvXdl+jH9Z2%9Pql0G{Bq}cbxT~OC@bE-+pK#wy5>#U2~>HWNo`%m3w zk+*dK+{uXnB2vBI<-PMA(2XvvFQ@xd8xZY0gW4?LKX>eT|B8#!ePccJaPGS4^SB)T zOTqrt-UXkTpLuln?)yu=E5@#okm|;cMbLLu=mzl+9CNoGN`7O0s<@dWD3HOS(qIRv z*D_NN3)6coP^&E`kWRQ7=`kYe0c!A{W)}hb4lb9QeZ^v!_ud}o%R^+7vJjwY)DGBe z?t&2E3{q2+>Ru*=DYSGL~QZll5~Og zuHAjmaPmK~a4M3CRv>ZFi0~8hZ{j5c6K|-Y8Sf@!4mZKL3rUbfn(P(vdz3zGIpsT1 z@e5)XrTqhkQDuF5JL-=?OZXg&6?C_lxfStK|C%?5iZrMrf(u`Ri-OENn@BpV#FR=) z$MJcD9aJ=Z1>}7&rc0LTPyXNCUNobkcYXeGM0-w6O)owW(sBK6;(-P$lMNdJe}?;U%&P! zsKX`E`E6@-)No!)#Tqx=r|!D`TNY#Ish5h-rbF0JM_jMHpkjJFs3d`$PpIRM?ZU#7 z%#`$mL#?kVO`40190@{P!8!aK+YGGn2&wUcJexO58R2~xm-&-Q0qpRPiKxZq79ji7f9y`MdK;hv9%_1_$0>Q$HZs{z~3DJF)sEcnU;DmN~MA znr#F1JA}rDv?H8a4M4$}IPKfz*sUi5OWKZMY1vhA3k zTUZ)Tt+VDnFlG@{`su@4w5Yb}tS|IL(W8eTXVa?_ve}j97s`oQhHNQ%6~OR@z4(UB zN)TncSf(NGQYF{FXYz)D+8O^lynbR@I$NPhgN>gm+H&&w&&3=O*|>w_VQ|w#!MoW~ z)@9vtJ;W>kTiegLdj7)^FHpHLF_U+B5M#$XN+3Ur=HuG2A&ATOs4NGjV8sOKP`AR^ zvBmb>Q>Vl8H~-_AgdhkH`!HETOh7P?%?X?LYPR86Qsxd`tl31`F>O=6(V%O!Go!}O zd*sy_{&hrS$w2 zRi`!2R8?CKe(EkmmOxJ0FhTFh*2eli4esb*lqQhd1UhvC3g2KBg??~pW312iDE#T| zd}L@`tHchM5SRF2mTO%SL3(K9!XhBOCEejOg8*>5H zs$)ECC#4qrAw9N!lGtuJ66m+>H~XMIbwV;(h4VXU{{dl&L6i;s1kIyEQyM7}SKSRi z{e+;-pT~$V4zl`XQ5eB``T~z}?d-ck;4qWd zbp?TQ&9G_1wcKXy=QDh|^1(!=)i3#Ov7o{)jdVILp4PKPZMtpyUt6+^>t71>GK_6= zRh~n<>(i=2>Valni_TJH$+y{Ik^RJLbN!xYfG(vW}0y~ z$Euz+-|QUg(@!AsbqthtP?NcvFqA->Z-70eU*!I32);`*{&J}#A9r`^Tw?Ld^}QpG+Dqoo?m-6enx?Gz?OvN(z`ZY`qo zhUsF)imnc;?=i>jwlA>H5AU^?vfWJwyOAM%-eSujUZc{NWj&cS5|l_NJioM_m5^-6 zi3v~#%ZssJ@;49*WP%Rii})p7QJXgtvZt;KJ4hU$h&#SqXSO_!!>UVs+4uJ4N^;qA zZ^-*M)S9%j(kMh@~v0e zsVkNr(B>n2)#B*Bpm$>u#1qp2>6sV(wcWP=h8@JTLM= zUa0MaK#SmB?LS*C3Rx2N#(`v#>#nku({SXNXzZ@^LG`=hk|LWui%oLa{FlqjICJl2Aeo(qMx>?dWvE4q{&Lj zJ`Of1Uw8qVkk{B9ciwK_|KSf^Ab~BJ%%sirHTBt^A8lgdR}y@ki*86>qFW$GLX?-a5XUlmB~ajXD}bjH zU(7&^N{8{BeDcX`b{TKGw(qurydrC8tGBOy^=o#`&wu7-C~PoIKqGPaZV-gUQq@{d1>E5fP66W$n4u=elYZ@c&Iwq9(* zXU~~!OK>q*kYC{1^YnP}UUs(HP^}G|CoMk7X3U)Fq>O#yQ24!~zj;;hLnX?rMy6u@ zcw(sGnD%}S1ks9KMp>p+VR}-ys5BHMX>6)uFMn(RujqmbIWH`Nj`-I|%?;ZPO9Jvj z=}Heu%9%T*z$!1Did}A-b#L$V)sz%&8SB`jSun4}3Qk5~6)%lTa)Kd1NqRkvWr)JC zG}{06Z~tyL|K=9E^rFk`Z@%!?ev+#+-U`Y-@<3hX`(Aiuvx#RqE;^@7nQTj!Ex~N8 z(7y4FuiLJ@yR56T%Rct;j~(DJK=~bw5cV7d$jU)0v2rWb@pOaLWA-zETRzo6!GaR2 zX5T^HT)dsoK4Ww9`ccC8B7k~z{R($41LflCLO9o~at`fz({$W_qWS*$78__8aK%FR z-cGCe!DgFtaygpuxOyEn_8%~gzPhMz4BI25K)RT9grHu1$s?l{<`M!FWCyFcx6X5uYx=!V zjo(LKy%@@1n5l5D=Y#w?vq|3jg`Ws7<}nP1Z;qd?`s2ZwP1?P_dmOGYcs#`&i$4Js zoCy37*U|ZT(nBVsAQUOb`gXbM?$Lcq|--WW7%|C9Q&7C(FH-XREy?5Ws>xA$Ll6scE z>h0UM*`D3IL1oAm%w1qJFir8wjq&*s(~6euc$3h^w(hPTn=@-JE(xa*UFAGv#g0`` zk18CM!#5Sflhjt~uib8~>lvk1mj~}} zy24ESK9+@dvF^QyXt#@(<2mHKDZcctxN;TOvAxwAZrWu-xM>XhEG=DHI}(f|365JD z0O2MRsvB+VM6*xAO9n}_UG5) zC8fb3YR}SVKK-ZmmN#E%yXg-<_~G~MUiK>~Koy_7`h%V+_m~CI*riR?N7DXyr;V=l zx9+y?JssZOGE1|q>auB8d6K!=J{=>`@4?`79{E8Pb z!Go77WMk9U^kl6yF!|G}izY3igVWGkw?{kn^E#uz>skO$uWKs~x5rcu1#d9QrNI5-Z#)L7wz z>?m9#&dOHz3Z4esy`22``{WwqA9xJ|r8fA*!Ba5e-!M$S;U~dCk0U|DWoUb06D_77 z#0!4`z5s@Sc_h`5+{N-H#rAT_eV@}xI08HySA-!?r^b&31v*+g4Yop&eOzk=xvYfr=t-w5r!ntlVSJ^5XS5`mSe|`9WE@( zG~rJWpif!0l$A}LPMY8>bSR%DoA=%GzxKty{ab|fn_UU?H~;5v?8{&JCu^v0@HCSq zPqM%KtG~p1*HYWOWs805+ut&owhYr`%&;E;q*VxSfAI>_kju;FJNH=2!?l(HZAHd1 zj+HNm?%IIqf#MO<>nXjgBamOEcS*fzQWANON|J+{#R<45%$>nrGTNO?=!S4=BhN%V zc!u%AhiGfwHkGU(^|F7&T_<^Ng> zF4HuFbs;=yF3yl%paVttv2%{T#>5mv`f86pc81YYU0({-@pXf^G`H1y8ocK;Q*k&DVgSK;1pRIYg z+gi{a$pKp0VfQ`}Q{0_!rs(je`*D`jB{pDpCz8L1=-{runXY*4Nza z%iTi-gErwEGb~sBhXEGL>A8fgMzZjBoaF88F}BO3EhOJ#g3xA1;-o)Ed~|(Dfn+LC z=rd{UPw(S`j8C?xL)mUi_*36@T9BhUn1|$D@{~8f)Fxo_ojaM6QlXXFKyO>0HQlny zPYU(^$ommEqluINI>L+E@Iwzj>>6vCp1kSJmwBFX+!1(h0~DhcfAu%tdW&sXx4}O0 z!H?Rvzx5qE>4YWri(mf2uD$j;RznB;fT|@+PPVJBzS=R{uwjG!_BXfVHn!L6{6)2= z!zmMZ$p^d0t*cC*WWLtBz1`}61ubzEtG1TEpxCP3%BEk0P{mb+deu0g4=2M{QCv00 zw05saamk7aR(bj)q;X9>@b6k#NKk2W(AGdQ1 zetY)pv9Eva>vrpJZpE$Je0%4+-)Tj~MWIaIwnE)T9FFESmQ`2%MO<~Svd8!F{!qAh z8nsLPqYbIgtGJj0jnKNO_FS+`2MWEE-v1~0dtGy{Yy1;o z{e2QY;v2t@z7jbR(8!gv9@5e(eaJRk(@_ezke~M{r4_CC)ufOcm2(IW6I~lx{B*@$ zOdWI>lWdQ@ECLqWs>(!P^Omc+h7RTDO}3H~tDN@Z5aM;XO~a%0eprlaBUv@@dLXUx zb6gKV@bDhe)2qHktCr>#yYUylW_2sWE@U%hVL_qi6WXxKOxEH<{M)u|yFIybrJa7( z8TOv{z1uFo;&S`PuYB1m#+Tb&ciw@Jjh7ADxrDmQFTdQ*Klgk#CO6yt_uubcQyi8E zn1pnE;=9AWMw1_vMetl0$00y7n=U(cF&qBF zl56z!MWP_Oc!(D~5RHH5G%K1_YJ>f>0Va*|5#ID*t+m`y!^)L3Yy4L|k=E%M^L~+e z9*g$srob@)ocfC$M^|Yh)FI@O@DJYqem@jO8Z~Wd${i}OpZ@fx_CG)VnGf_nkn$~t z2q!YT_#Cf#FJ>zZj5=PbKNSRWgb#gFJYD0xBJt?FE5zrS>~tWDGdJ((NpFuxjPW(* z?^l?pu2E2h_cH)!5a18yBK(7^y>R8x(eyN=i_h^IU`lur6Vmrk-gF^$mLap-vr|26+h-TpbTy*6;4Ohi>VzYrfHDKmT64t$nJW0d&YF z<7RT+;%pye)gF#UaHuLD19`p0bJ$Ky+Z8!x+UJ{9#-7sfW2=V}q@nh^mP!?M5J#e+mfWH{%?AE-HVPAkZT%nhsi-fMWw~?p)maF+i-m0%0K!3-nVa` zpTw-q#yaIzr@d<8P>GWOjU&46r|nN;W2W2@N^!J($2Qx#ZJQ%J4D;~Oe|`#)*a-=O z+`A(H%+`81c&7f=J$wtoWbtw3?uT8zu|5A`f+ltJe8|85H{NU#{Jbr4@7E(oEq_j_ zP5#In%cx*Ao&KVO>H2&6ZU0T%E!EW*nw=(H6$1M%* zL*HVX3K7TVS2U80BihOhp)@k82Onp_(?dW%c^KQvNS&(g$(u=$-u?YQ{GD%B`|f{z z2QLr*YR}*n^wWR#89VW$6a7TXoE#k|9%wI&R9{M`&1jE3@|gYmx4&&qKKYoPihH-u z|M}87>C)4VZHb-_O?jo4)A8k<1Xl@QnuO~baZ}tFC#H)~hlNVLAf7b+ zJ=y`r z0M)hEU+Z3gPCw&xRzq`ORiu?E4q@pjOKtY-IhaZ|U=O|tM*^q$WYj;7!H-XjsFu(^ zL?>0#kTu?2V_o~(;e|{ND&=)&y5(@vB?a=y6D1>eMGluSK;^}Oi23QNATq^w{vz$i zNhlnX2uYjqXK!3-aevosl24?QK$UX(YU#~;tnnfGDSOJ~4zpupt93u!Vnt_E`6NlB-H$Ef zZ@TS~dwQ*$ z9dy@o{WfuW7J_v?ha3fFD8cB-d%NwLZ+8M&CJx5Z@PO^wk+R;-Av@*VJp5&=d}=e} zY}e*~yYu=kn|oZAodF%PsIP>8#KBD*M}+U)lCpJA_S&;(x@&hMcvEm`w{j5(X3o#F zx$!F!DAwwXIbVvqh7g)S>WBVhx*&Gm|RRXnRQGR5@??@TOHdk^8N^sGlMI2mG;r^0)5NaBmnrs`J(Ej({ z|FygBxyv#pc^xfU#ysLxc_|17g&u_Dn{K?xo__i%JNukB z+G%*eC@m{Z+ED2Ck%vSaBfc7-y-#q}o!VCGBlXs?yOrbDvsvcux9WGzu#8e^zlou~ zm&9|;v{L9j|8#X>8uYxznYc>a|< z_IO-Qre&8SfE6&DB>5`xa9$xLGcxgdddH{AtajuQWHo0X}qYc*eBQ)kPsqVC9 z%mIo{Ll9C#{X{(Vd=Qv={Zc|hQ=<#v6UwV;qj~V1fdkrQgekhs=RmmA@C$zTJ$DB` z_uwIKTp#(+hwZAXuJZ9BJ-&S0S65ftu3bBAH$uCFeT^v!u^q+uFXif!H8E+?2Ogoe z)!#rJJyhotfk7rsrBlnS=%fmt6sRgA?Y*^*Ast^V{-R?iDIWb) zyxLEN24{zn#7pN_G3mjsv41IV-5VrOjY`SN!6)eRqVIGbi zz;!I@>0tOAd-uwvz%c<|C&WA^x?PgrSjndN8aV`IA4 z{_cxkWVw5jec%IsV6$dxnJ^6Uf|LHIECg%Irmgn)V~?RR?sIo*H{E!vEkkpm)BF}L zSipdMw!31J020!NbUZlY)3TqRupY+pFkp;kCTZn4(%Zmq17nb2#+|h{vpfIhZo3Zw zys|pO-gsGoRZPgVO>6sM_%JLD&@eE`8@*jamX8Zq@j?B_bY_4Je+s#IX#5!;q%q#~ zOuw}@rEL79OzUh#s6fD%hHdd1a%=)ZvS03gDJ2@PVegb$zWa!h@GGu$6>$8n( zq4CN-+;(AiPkNciWakXwJ9nHFm1J01d4?@ICC6GR&qH_i*#3QkxQgm^L3+g{2q2i% zC~RFTGoe8nKPA&+{6+-vv(7%tE`QVI-uKJP@YDgt6(T)G-}^_}v#O_mf3LMZ z+Q<#8yD(ifrP0=2!s&m^8@}(DYC3 zM`5sM!1`)1li{!uom7d+-pi=Zis5m^W6-2`aD;j7PqtX|BX!>QGQ?L&k~^iysxFvn z6&Fsiyjjfh1w)ez$|iaZGY8M{z#q?p_(e;~taNs{HSDWl0*5w|bY0uqEY*td^fGPI zB&Gy<1;HG{P+U~xLUCJLt95pESb0UcLr-re>htRRQ*oDh(M1<)!6rn$^`6_KpbnFl|QNd&V#JM|C6`i`G64h(y@5 z---5nOABTdOx93(mz_Srva9ol;|jdfeT?b&V$l|jyiLHLbsL56gZ0Z;)0%W@gQU^k zb&9ORNHUDHbe@>NJQyyq&Fh=T1$fcPtiH?}Z%r#}Q7}NPZVm`z<*MwwN#19@-Kjl? zL}T9e4u2eeZp*-OeVOjt)*}YpAnd|M~_L3WHWw zR&L8Vg81Vf|3fzDTxbP_1)e4hEOcZBU7dod@j)gO>e_+9a2vUYG907eg931shcgG@ z_Tek~Dae7oU;m%r1+2Emv+`-%s3Ld*fU3UEtlZso(Zt zYSCD)!6L(!oy~F4n1DR_V7LAFC(X8FPmdixHQ!3IbFHUG{+o3?`k=k}!$r^#HWmlj zSiWS`(xtz52*G;*jd#jck>_@_*&SUHx-kb~oR-ObA%b@ac6bvIq^HlxvPmSYQ7ncr3?{Yqf;uAzOZau08PthbSPRo_jF|zM$3KyR*Yu8wagr z{{V2Zn0RSo!jaMSDNax2fIYP0!7Kt~eFO?sltSy?#Q_bCsb1Xe4chrji|yQtiV)(1 zz2url`q&IPU?-iCa#kQzrks7Vp1J;%8R`sp@)_K!#D$J@B_rjMl8 zv--|yr!TkTPh4bMH*d8y&pg91+z;3$CcUlgZM4flE8?)M*-K{I35yrmx#yj0GiT1S zIrHZDWO#thoD#kxe4+2{IZ)K|ibaZf$LFJUEoLL=qD9C1X}i03?Y3)vc8%SC-vg|q zz1=?Yu@AdFZ_c*_3pFX&nSc#3672=hro$VWET&A9!myMCJ$pHzu^#JQF= z9wh@f$aGi*a{Ke1L2G|xzwP<24c7Kt3oA02mQ|3A;GJ(}Z>Ywf_arM=RA!lI+MOrG z_995iisu7@o)bP1>o+4m%SullZ;h*K5wx($&0-S1zKKaigCE-C7$<2w?P!<>WN>&7 z1#G&^#P+9fOek${c$Din{J1Vsug$gbdF{#LBa#V6g zwfkFlx+NKSjf7^P&X?5F;pCncSM3B^xi;G?Jgq-_T@%-Wqv~pEL+c2w#l- z>3{skx9sn~_$7O0?K4)uG4**lxtKEavZQ>YtwlIkaoP%d^A&HlC8sPwpe`FuD>&+m zlP6EMFMRQD?ShLgwDlZ~y=}`j_uIO!ZXcT3X1nK}dpOlEpX0LU*>T4&QnC<|-YAW< zcWHHW?v7>l~8@pue+>}6ZS58E54YQ=J;kp6fmx}6+RlfI+}*;?i;%7 ziN|`dhbAo=x-~0%S?+~#Gk8>1WwM(;)3!a^Z`b^)*&g5AW(6$Mab^nUD{|L_z+8%F zj&cTf6&f!1LgQc`!uNBl`t9+%yKT?5ertD=7}6F|0JPYfSjwJ&R{8uZO6>SI9OK9PnNI<;GNf~aQ(Z?hqg*IP< zk=S=P_Tjy_iA096QUv`RCKqJ9HC_m2y3(IKS1J>2kVVwhAHuc1r zHu)k}{m^E2JcZDF?G|fY*XTCeIg=!SlMz9`I;5o;&ks#c*15*KijxAfD(GzLJ3>jq zC$|#P*M93UYuVmlRRsv0OeRW}R$BQP6MPe8&+ZP_#^=v1LGbsEhcHIMaLdi1gU8hd zNMPocNg7^9J|W|wAwU1?!TLoRrbcC~-e|AF(@(8p zOj+#RYl!+&P<_k0$=!F{W&iY*uV7l$$kFo`;>Bm43*+DW?)Uuk*ON~^$=j|bmr5fb z(t>yOcDdbmX?ZDaKfqMmKGHB+;1$S@)s5D&s?jGfLzo3soK|hQvv7M!eW$` z;#ClZr2X}5Yqz~We9qd}HQ99fQ6awL)y|ZsmmrXuNn^-7mSGs-bi^G+enOV2A}d;4 zX(hADnKU<8ZV?-WddFGECU{vxkDGltF2{aeR}^^u2=%%u{tFjYohvd?-HXv(aKZTq zUs?9muYc9nKeOJtIuI}s!sYsGkX?a&arv3@;E9y8%^~%*HlYj zmLL(dLm@Byx*P%16j!U;m9htZ(}f94N~U&JQjS>%0kxa{kNKdz_ozl|XP@MROd8=u^%t_&+J3YzWx`|!RYAzNvD zum&IS=t2wF!=J_mZKQO!X$kZ7JV9FKFLM@A&e?gkvcAh!J>Fw$9_zOn?9(&&b_#;` zvU741xM9EBaH3*;w{@&*ws9$$Kc%dAPPygH zEb{WG{QBpC%W#=^*H_zRO}LF~#B;^IO?xoi!EU^y%8EHQycE}Qr7Nne?%EyJw!Rrt z7TKl_TH6}5f@>OG`&`5%rz^9~N@kW>*~ODNdK*4amW9B+3uQ#V4Ir$0GxCZLIHa2C z5+BMdyAN^^JX=rG~Jn1&~0`zB}{tWIj zuVP|Z%07V-``Z8gnq7C@bvAeI9L!Kkfh$BA52)`(yr%4Ls7HZQU=y(i*Els???>2{ z)lZy?!V`LH<)H1sI`+Zzf`7hcQ%A+8R9Xi6NPOZ2KEpxvD_*VOuS0tlZlcQp!gmra zo$h+2Dh{=nM4;HAYyb=&19%C_io&1r?4p0PXoIpm;NX_i>z3J?&~Q{xpoEndypYlU zyq(gzq?~%rVC81~ zrIW2`JrhyB>CmE%=bEjHzE;Y<3*oFywT_$8A7Q-(WRCs5k|}UZ0Dlp+6FZR@gzg7!?y=45`fVBJ5zF3)2M|mx6h*u?FjHKGH4fOL*i--f#wJ@|*I~0N z3az{xrh_?$WlE?;_{J{0p$>ac7+x0wQyv*g&cFH9$$yqJX?8q;t&L!o=o26ygi z=QXQ2Lw#1dd_1NaMP6sV@p%-{2U&IL+Q6h3LAm+9I_qn~oTO{74dS+N;rS=pl=scI zoau$uv#Y~8@U^Y7yA~Y}MSXj^tp_0_Zw@OAtT^RlWMc+`QMT%uvJJHLv#*6cAZ*;s z#4KevAP}j@O5e9zHcjvg_C2`vn)fP+^D~kRwX-Tz*XZI=X`}#QL4TqLWr?F2MiyEjQnSqGh!``|Pu}bm>ypUdI(5wcjk1I#Xv%x8*CA zn@+nF4p+VNDnDxe{`>EO$wKmd9<(cWfM4@X}}7PE3*`PEWE$mg{ckgv+(3{Hvi7I z{COn-UwETy$CL7TS(HD#bf814M~q?YdGJ9}!#Tor@f)gq^X~xpX0mFP>gZ)Ps@+P? z25a$JC$b==Q&G>y!fTNN#{}?0)T)?GI%CkY{uUrm|M4e3$#UQvTohiAB*{G0Dd&6l~#qrpWJo2au z-^U%d2+h|Nw-p?Xny*F+r$mT^=Ztw6=7Z97Xhu$^o7eQ(;}7(r!OpZZFU3x~LfS=G zgqR~3L_D;CatoU=>oKpGT$E=QEHAd1*m=uk;p1z2SW+FZs?1#14$qj6=>Uu~X9G6v zXq}fHUtn`_30R0rK*4pMMkb=$Fizy9Zyv;~2ouq2pa0eZ>tJ)_(t6F-bpJybv9lh)U!Z*|DFNcvsJF< zz?Hal_5JqjHUxTB;Fm6kMi(&YU^8bqCOO)99|;;(Jx5PG_PC^&xqDP-{ge%J;V>tr}n*CeQNppoxRv${b848#*0R$9wBUK;e{yfF=Lo8i;#X z;_vYzWoZoH(}P=_Tx^dNN8gmLpfJ3i48{NeKmbWZK~*+iht(uH_E5?ql%c__Fy zAaGI|FBkdCt3_`w{>W)~@=vZZsWNp8wIm34)u+V7?UgGYg3|W_yA%!Qxo4ki&u-Xa zSsiG$InnZ(pZ?r-?cQad`qNJ_=|6keoJQ?p9C-Kc>%xz<){Zo;Q|7tsc2@h=tE6uz zf8{OBUhBQJR(AuM?PsyA2BlO+Sc0E2b&^ef|6I#mh*<~GJ2y155wnxFKuc$GtW9YH z-SmsvPT=%}x<&IkPOD$wB0)pGw0rTOQZ%i^O+@@_9_(si6O(DfPtq z4GrD;8hfm~uhc5D%B>_-WPR&dZR=_G`pCw9wuH@{l^0LNe!H5!Lq`!El+TL@il_SX z1kg_9Dq2!*xuvv|#(wy7Hv2%BWFYVtVxEGkAZ&I|G8ijSNW$=Gr=4c!pMO3Zaerpt z|L-5z%rDPmLwK3DXN@i8c=tK;tTXJ1m5G%crvU;Hdfz)3F|8Rr)S`50^gs*378wc;S(YI*- zmafNM?ePX{URh^V=S{KkoN_uu-&PpHB&&}@nwsyav$m($pv(%<5Sz63KY&PD^YC%iAMPUjH)avM)02WrrCa!eEkjEt?n1wS-t7={@cOc zh#X9=rv254!9$SBp-v;=T6wE1^p}pOzzxm2wAp3nOvE!xrMEf7(FCt(cB!?X`IcYz zK};Dn(HvkBkjZM27j~rZN^&f`hAD7N0DlpcE5;QWSiDwo7{XOoz0($-c%t2W%guJ{ zEx*B@douz^w+~*q2;*6(iM1@=&p@t@wYRH=j!b9gNlaZ!vhwjNzo6|1@HuQiJoBtG zF+Z8dyTB|x?*vC@F&a<-=X@*hrZMlX6xZP^Z~4By`&V7og~s~Aa|&$EiKxE$t~jy_ z*U{+%Xd{Zt#@ToT^aV?@?XhR^bJ~f}fwuU$^(h7n4je(FJ!NW^&8W<=ImZY8+ieKV zO}GZ!k4cK`vZK}*&`BPC=XoV0NG{>M9<-5i_96rY?uC;xFdn))+4#jKNeLjb2QPAM zwIYPgoibhNh55<%bRWXv?(O~d%+5}Gjss0PH|5z??ZxhurDiYMaOx~`5avjEQT}<| z)a)LxRS)&rBqkVTc!B9`!^;b{?#OG{iDQ#X{XM&?*B<#TnnpD0dS0?3*Gj6#*~Gkj zn>!%`;k(#QSk9(XnXfP@kmg@yhy!IPkIHnE0(m9lA2#dYOonlCCsQHi@6RIJsGxq( z)ZApZ-TE6_zhRvV&)Qhj(-nj?3DFWdCD2OnRzDChy_ac^Dp>b2&B|6fgk=e?Ie8qE zBHwXDBwxXe0=V$%<5_uX zbDYvE4;^Ttl_*Un3JMCqivwSjKkY=pweS=nE3v^9F1f%E{=!8jY1xSlt_$FlHC+H# zp6WyLX|0Kd&I2eZDt25%8>Q`MAb;?Ihkb$}la=`k<~toFTo2&FtP^ea5EH&3meF%7 z@?8M;Y6_%vJoTe8cCK!=y+7V$?b~rzNS?)*nvA2Z&0uw>x?&OnPk{>*s__1LCI?hi zW+j^ymyWlNRn69eK&}m&gJ=vhv{!((oKc*O@QnwW8+UUG=RUVR9>CtV=6^O@#X0yr zX5*&psiht7Mj%QdU`YVY!TduTA)TNSUidFw*txES{@P^C18r73P(m3oC&DbNd})=H zpE=Qw_0F5aNv3EGMZ3cbN}w7HdFeNIMzNnz*ictv*#$XhJqPLctyTe^wNCzynx(I##llfiS&KHJum${dq@O^|(k*6F%ahxBBVRR#a z_c6)N%SM2NmIWL(qdf@j+n$NM>PM@c3Y||k+MXXhXFYh#nRLZ0t9~D2o7`0rgd>Ia zm6;~oO?K>Tvus>wmM$Leo89~BdO6m<1i$t?OUq54Xh9oY^Q*0HekGF-Q8$nnp1}7` z%uedB-Nq^o`y-lVmY8YHeJ$2_*9NP)my=LwSDU-G+P=OTtInyi`Q>P|tMN`VCCA!p zTiKAzX55ombr@*0228bj(e~%DUmzc^J1x&P;&)%GPLc=U10`O!`gf+11vxvBA^p;Il4PvijNJEORc6SE8Tt2=Ir2AG>)C zQs9^X{yQo)GS2j?lK7y@lH!SrPqfKXCfnI(o#V@Z_uhMtJ%>GbQ)812vMFYm@gZzy zqBhD#f|LECHcDyvu8GspG$^XeopbJ4b|v=V<>lqx0f*B%mtivR3NILqY}dT=dtdz> zB{=-gKzC(^w9n5y-D?|g={IiyuJZ5>^5|`t$aKhi087(&N!Ww?zT%Q`_6E#Sn(Bw_ z>D4{9yQRlk@t87oOP*D*fpPA_EIa+;V%I|FqWzNlLYbGeJRxC-g1~F^tnzB!Ax*V> zvFctyWj%n;h1bcqkUoj(afP=wNp^v!V9N|9OM-&{@xK|(^|oz&wz&~48ycLhHF>sg zEB?LrpskVSR$3F{DRD`Dwv{oFnm{=gEzYtzCuLhLrYn!*ZAAzBG}f^+j;Trq0yxS{ zn}{il?9d;(qucJewF{SuI_*#bXqc=NflGN-j?F?FUV)Ia0UE4g6XtBR;BUA9Mvq`5 z`iJ)R!sSjQ!ArkY8RH)~>YvK2Yd`JNLnN#}@P#NoaPlnSddsHGw)&|xR*+X<&2251 zVa0tBa^J`juL3Y67)x-Kc2OXeM<#tjS80}0#HGf4>AasArsBJ{93j^y0TP}iq+{kK zp;*FPJG*uzBuf(~ocg&>kw+mOgycb^2O(NQvrM{*iVB0an!E+Omt`5PSVjtFkpV6_M$TNGa=Z%V>`CJ4M6~hR?-*`vbUjU z7bYFFw?XjBpI(GpKJ33mSF-oM!h^C_I~vCdjci);#}`=T*^{jD&C}fGxp`gvux8FT z`$DU(^$0GMD|>v7jmK_wV1J)$#8YSw2O7i1SFI#;ZE5pU5S!5SrCQ|)W1Mw2;8Ex| z-7e_QMbjzGU*8@k@0he`B9hDMMjp7zCRZ*5Lr`0nEo_VJ?c8TwgPlIHDV$kiWtUHK z*LHbq?vv??7fgjDxPam}2Q*Os_Pn3a;KFO})#ftLs>{2|Ad>)n9!LM|T+AL#t1z@fctlUxpmC4(9ik(Au5Gd1 z|FO$ z##1$RY1`!@v-tr|HwBgyTW$Sqwyk@o<@MxPWkH1vu=3N-(W({M<+y*Xwy7UjXeIMl z^??7>{B*Om)it@szU?VA>3bQ!LA3l_{I<_1w6^Vx)fz_}dC`{}|51zLR(+ERXK!71 z7^9(4=X&-QuyW)yqD^H`XQlX@&-pPceSO*>3NNDH^paOT9)1K#Z985)uVX*2SqdBz zz+Xhw24RSfL1&A?N4%FHP=#&xId80TPYsv7=~An!t+NO2f50Am@F8C=)PPoBUyr>p zo0J%&rD4iN+nqvSP!O!?GpE^m-}hcW(M-$gg6l&?7)8B_$Vqgwr4QBWzy#qYlfLAR(SdQTkz~r|?Z?COXyeDctxqAs|dC&9(Ed zEVL7r=CZ_!b{v~*#@T>3`N$=oG`~tGSnwr;lO$8IAeE1Jy6SIqi0?u@jKt+G(2BI- zR7`{k6btT6g?HU(npdvwvU}FIfM>srN5CGBaDCz_OcrKj*d{!)40a;iH2`NJLKIB= zgsKud|B?c`=*j{s<%sz0#M$y(%FRx+0w7N;-Dp;nx61Men?fa2cc6twj-njnY|#>I z=g-Tt&6wy&IFlL6rf2Ye@?@WF!yG0bO@6TChzxjiiI38ALfcLzE0UKpSp8LK$Dg{| zx(0)h5b7m9S1?ihz=u9yS8^CtPIiuKoMn;`G{Z_SDJIq-gHvo^34qv#G$2dUt!MrD zhEN)V-m9&LMlOvxLOt-+KNPQ*{v%Dd+(1&bZVM|o@*ysPQpeOw0GC!?hiXa4SJ2?D zwA$3^CNsR32}u?LaeGI*3yXp$?RpRKL;;zUcpl`ZzNtU8L213!aCMcCou@q>189A_ zd)=dvkQW_0nG8r^lIIF-n9~HI48P5_H8pnoZNK4=uC?F`Y)#H7cSlz%-d~C_SvVbY zgINy45dat2n9BQ!>IUd6|H}@Rl>jCV!b$P;KF|}7LW|37`sYp{-H;VvznWQ^ZS7Aq zA^@Q6r5swm*9uK9cG{h2?4)JSV-HIv+Uiy|TyF2#VU^oEtYlXeD?v<%5N;YD+=qIT zi6ugW4i+g!5XzdIXC2S9TG!5YYsW;O5P?D(a2@U=p)3o{XYP15IWifL{cYR*wbrqw z*-B0ukG*Z56=x!7qaB}m$sC*UuEmyj9M3vzClo2PM3ql}FF81I{5ber*?R*^_T^fQ z?86&{wD{SoXhI;RA|wy~_V9jG2cyRf*@DZZt+d-;{p#23Kfd)JwsY4``;$-piOt95 zt=hmL%t=5`CvyQ-BFtgYSSCm=b^_Y8b3Tow~Dw}Z0 zG|Wv3t@9Z+`8HvY@|MF>#q@4L9t>yoDyuWTIges2^wCV;<4(nm%bnOQoMiwr=!+!rriY(pmw`Q{o4)LL5C_a;v3YwLiSAe^_h zwA%LVJM216`IFZG`E7ms+umm9zVTe&s56pB5O~C2=(nL`a$&r1VJ60Vkwtj%(Uc#2 zpbky%W=u$mFb`RDYOdApL7+rX*5J+RbxCkvT@bpf8&An_CE`#N24g8^?0aa&OF9PfIcmW=pU%)RD z_gDgmq5y(GeZSN55%+6@L9TzU8{s=;6IgnF^M$4M<||9AdTO?c#KI2Qc~9??VVAO%rB0O=#lx1FI7I^f@P>k#pfU5kf&7+34s7@DsC_ASWT#CrC~) zeIPJ#kcSt3!*klN+MbTXo`rBY79qA}?@OL1c!7rMGlm1=@KVp|{ZR1^_AEhOLbt&aDhrw4h!PU;W2UaPkCO<#>3@KsItqXyOosz3!O=uRlWrH%i6;1(2q=wkcA=g7 zmX;R#ukZbrZP~GvNlOkMl~^U|f?s6x*^1>WFjaUj9)~XEgu~z`*vn7)0fcXDMr&q{l!9Z7%ED*k{rz9I_o>T8-9^l?gQF zgG@C0@P5+IJt7zOubd9cGJ7{dRo#!bSmP75NJv9g|1g^&IS^<@KF65P%C|WmTI?=} zygP#5^tLMcRbbQosZak2bQwed|EB%qCqJe&l0z;iVT?38y2MV3u+kcHhfs zgTBH-8`A!Yl~1m;pI>v0-NIo`a%uRMH(zOA_`(<1?7zV4MtQ~jjsRtF8wrsVDeR&9 zu$R|FQ7c9H^R+s`K|Er3(9j!Pl5mi*O~R-qeyYNZ5-DMHBYLtKAHll*!9A9V`CB!v zDXTv)m-i?l5b|Zd)N)s?)&9>mHtjc9?e+Cm(pzX{3oC8fXOA}t-(D|(5oHkkpLVHI|yVd@1i_QAVDQL*Maa*|An(!$<;k2pV4>UnZ(dHF| zgRed@@fLnsRuQhELmuWcZ97;=P(1o!59W`9Xz;TT$VoQR=2aL)Iu>4&6gVb;A4avN z4+QD==^)g>oWbIHu!6rBG$?CHUW4z}t=sHNfA=Le8L>28SZo(vatYe+_qp3b#gDLc z)gcX%0mQgC$HAG3n6~#xahUYMDQ0$r*VyN`uoZTpS zXZ0h!wiVAPLIH17Ht%G#@l(ptGtSSqi?1%g zMn2Owg?6yfQyV|o?}zzK(2%dVAUF6__l<3dkf3@;dO8Z#>-Zh;Lm_oisPfWKM!|ie zq2_h4NkD9$f*~PO?fsyE*Qx^rr3rmQ5B!nFDaHHInsI$os?lIa=M$gfl_VZUKPx{8 zFr!0^7trw>T_8&*x`gl$Vmab-c$-d(2>-ws;cX^+4OFN&5qRX`N9{kp`AvJ|u}7_s z)sq5j$Q#-k@c&)H-jes*=RWs2_kbaL^q7b66|V9^F@RSS0Th0&Ri;g8WzFMqtCkz6 z$b(Rif+Y8YY~Ji+S)3WPyAV{rRJv~LTiejQx1L6Y8J zN;C1T)2(Dll`?c6;W8^}f1=*HFgeM<6G{;q?wHLWSm5WFjhfncn>#&^X9Sqrc4AvD zGX|0){B-PSXO)W0vaCRql$TiUL`*1FL5aq@F27oGOGN-a! z%nGtuxfx_dK^sE{2idQ{iJc0fU8MUk?Ot3)&x_@Gs_v9`a#bYxuK%;oe#VwBTWVkZ z+ShE=>Zj~;fAOr%`~T+IS!bTje67uKZ)^GFXZdMh<$fT8&c>@4bnH>C+Wi#hy zyB8Iir-+sn*l!nT!)8H-m1Am>fd*a2!e??SXD`dBkNg(Xl^eT#0wR-<6Hm>tDQNy> zz9DwmiFu34KA?PH;%~XS(9XCVM$Tq31qs}(98$z5pBP9$U&fL3r=FM337Em8A@bYU z4m{z}`UyOqvV)TuXv0`tR@mx9!}HZE7}JVT8;5t@~~2)T#ERzyAlj;>tJsK|G>C zq;Qa*p(*XFXPdkx1PwS){nSd2>+N`ia)F2Ntorc26`-Zi682%1!*mm$*tqqh0tV{) zY~%gwZEeSTc0~-@tfA>{hd#LsFB4rHqdl(3H)v|bPP4KMJUittKqyBOI*xk@Tl@ZV z3wGe0R(f2e6`oY)-XprUV$#Du`K9jJ(rLTaZ?s-CjIyJbJ%4^>A^fG#dijUu2*7<4@SB%a;KI_mBjq$w9D)@8flJl-NE; zUx(wR6~4bg`zkQ;;Gf`SmC1|;x)O(43qmUVPfT7bAAQn*^Qg^`+Sj0ZO#!3!`&+ED zwbROHjkighjGFC!umQ#Qxba#pG56Krv%bI4Cgn`9>i5pFqSIIn5?=vcG+Wc=cnQ&| z-Ix$zE~$f}dXR$0r8;bH?`~@!VA3a__xpJ-A)Ogm#t?0`g+7D{l~%~)2VCXqCT&3` z6F@@Y;!?}SHQ^wWPCwi#A&4JUFPdnH6MQAi_v21bw9x@sZf5GO26K}c?0LWpWu(oL zM)Y8hh2Jp26tC2(mb|t2;OI?M85|3*bqX94z>lDYBSX_Q4!H4tw6G!JZyb!{xed?S z&wlnZG^A`6D=D-~F1ys0EnDXHsOf0}JklhCDp?GN;Z1^R?}k6V#2dkvJf&;M3*j>{ ziO57i&~jriQ($8Wr%gV6e-A#g`)uZdY(L>qOa(sXF!Hfu`~TVd5&*l3D(%z#_TKkS zr#pLq1PCES76A!60zptz+!+-`&~b2d###JFbjEEM9UaHfadgyCR8$bz)v(DTB1>4q zl9g<|rT69a+xq{$Q@7r`-RVxclTN3Xs-)k&w{ERp)vfzg-Baf@VRPnlSS@}Ma^cTm zBWF=bKwb|Oh6UJUNLf{;RR-02VP0d=fnqLPCB$&Vq_SlhYjte-xtdP$OLc%0`>3pi zd^fa@kYIBKqYT7)Aygm^KaaN$k4*9eI;~+uU-?A6VAdQi*)tH zhHQ$VVv)vxc4JkBi{-0MMt+w?Roe>dY3jh2ix-C;li>IQO5nxC_QP8g4wDe7g9I_N zePX0y*#5uQJ@x}&tt8xg5!vU$A6DDh?=eHpEiOFTm3L}8+3>;!`4Wt&+ z<28ZT4{;NV=PVb=4#_T4Zu#{VdEp6&k7W8J5`dwax*}Qd{^jVj0onfb4Kjd@)g)3; z$eVX;#Y*?9?O1i3FM$e>3lP_>pLtqZ|GZ0Owrk(HNWvf<{g4MwY~@Ba^k@9Vcl2tJ zxYA8LDE6`Bb1S89Q?G3PxAiiJiE80;Xw-l_JH=rdp;4vjuI(V5&>R)1R?R3{QTEeu zJA%TRavUIFxJLVMys*TRvdVogc1ihp)WE{dZ-OzN9p8C=FwW*>(_&<@2u1!t`xqt_EYQXV?J5ngW>Y45H8V zB8?8nO|$)rq4qI$bg8~P*dOV1+<*vRKQ_s?#oJ-1Er~rGg;Gk3sB9O{Y1xq(dl&QR z?Cz3{y&I(!E8f)!7{5b5+ZAs%q&kf~SNH-=qocMb=m3XRX&N*FycqwmcO!$n8d#uV zzbHDY3=>pnsd}+n`k{(8AJ2->=7a(uU+UIZ%|J*o(FpA)_cuyxA>IT0gvy5nF38n< zK4V%=_@V1jOsrs8sfv1;{VwpP=o41J)iEmmv`xIynj7wSrXk=6_yn~m2Ln3)K;}Ou zv+09|g|B`68}iC4uR!z!^5S^eUjF8{s0=v6`W@37=4H~xF76P_yBng)I3{0z+ygmh zUN%3jhH~Yy7BbHTcqu*yGxGP`*e6H8$W0Q1#Cj0xN7fCf_{Aw_VwXD93+e!aai$1l zG=zcmwXJFS-VI>ZxD^E%KI437jDZ}veetu5wKUEC#wgcPpqdb?>oDUf9kLgt(KQat5&7}7ZkWIc(OI5FU2FBVj^Otvj+uo3mr zD+}ewqhV|aEA2;OV+YaVlV{l3%t+&;_pNK@yx-u!FbF!$=ljZ$7*1(BbSw6-^?&hjhY{}K7; z&wU;%!Ik*Z0^Q}_s;B8U?1cnMX@+)EH6Q}QK{iej6s_ea9)>Z0_8DLtda+HJmg0|# zWH&@uu<{px`p108z_O78N5rJOs7gX0F|~8*WW`^fB2||yfkp~wlz@uS6hx=^$-ERB zCKIp#9IY?L$~OoJRuOl7^CfA%e}~W{dCf`nQoaT%2$5f^6Gle1gLLB@k#+NC9>xZ) z;<9ti|M&_iII&uqf3w}JlBXil^=PXS?bhG#klrm__-Y7}hjLb&HW!=yd@H@I6d&lZ zI)=B7afq}z{N?V-bF+Pnm}jU))H_#QKMMNi6_2JPCof0Jcjt% z(H~dsKMY3_%DzFz35;!LZrbFzz2?z6|GD+EV+_AQP=twMQFQdT<>Ga=?!1Sq6jq75 zF+m{#=lDiUbg|p?uxxn66WAp?(9bYgq+CW2B32>jfFB%yjf{1>@C7g~#n5I{dPX&P zFv^dylIan9p7cM}E-g>&lJ(gPP2e;(OPUe1;o?}l!nL)`!rt5(*9^8vp8Jtd3BQF^(lTe%7aD~ZWtx8PvV(MwBTzkWa<@8uL-rDUcQ>S`jo9@?BI?AeX=G?Q$&C7^++tvgvSuL)y&eXy~XuZ*5fWz7^Ije*mjv-r_?VFNOtJEk5(2m@HdOwTc`q z%y4}ih-e2ySDt*BGNxSl$XD~~=U_G3ydV(LOP?#kXI^6DJvpG!*>c#U@!kI~!!r&0 z0^w2B$(f>=I_W@$jYC*$J!4qDE(Ne z4nbsO$!AtW&KrFg*=_&gGqU@4+c0PZqLQp zn!$==9n{KIUp61ISlAbXjce?D!DvX@{?sJRkL|=52bHbVW&?6keepc0xUk+B;ITNc zv+|tBF*-|T>@f%pE3Dt(PWm7zjBjBz6vTnfWW+YGj^~yr^J(FuKOSe_35;XUZF+jm z^w7T4?1OE#8ipEEXt0Ikjcvv~L~L7*Eysig z@p)e-MP9jVc#9L$RD^V(z(frHsNslm(X{5MiExF3{!EbPNONEpYy!a05upr0CY$0) zDUi&bN89i<5u1){ic~X+DJSZOc^M~pDQx!LbN^0h-H6re3>JEz9jD}oa&3Qxh1_tjIU9e2b>nf${_qCFKEdhV5o}gGoOJQ)zt24E*Bq7fo zT>({~_+k@18eVIwBQM1>1U#pr)b8|C4Ggn1J2POCPHI`*L?9_*)SOh&t*@rN5! z*=yK#1(fxsW)q~NO~vN`PUXM(j-W5vGs*I!JdDUZ`OlBPMshzh4gp8N4`GvXu*eQ( zc(8Us<(R57!wcxFGtZHWFSv+F&=UZ8_J zaA0D3su=)he8F7so+7EK56Ekq(<=APd@0s(%<2eMaItX;tJYcujXa1|ntVC$ib7bH zgk?aGxESQA>lSl`215$cQ-?Hk;K8reghG)NI^Ob<%7xcE`~R`&Tg+{mHl|{WfsJ(_ zVb+NTO0VHHUzCA^I~`6LK8E^S_`U))H9vQ82%8HtQ5OYo9#0(hC4C3%7!!kj$@oYU9`j9%W%wG0d0g5B?l}a&3TdqS|h8dK&Z8x?( zWxgM{|3PVKY1P%-uD%XguxO!Na>-hG$2+co<_d^;z_CA4M%Fri8paO|!M0#y=n)5W zAj+;%^%>hvNv^gf0;q?HIpi08G6?NIBtwEd7$F$O;!1KHM1-O(JkMaWbvnS!!bTmc zu8bm6u4X2&sjj%TO36m!|Gp|M4?^q&V^Rz!5=E>M zQjO$bFe7s=m@jj$J_^Qw@Od83Tg7R$(*A4{_K!d}J2qp6g7{Jfae+KMd$ns9NZtFE zLN?rJhp=_e9*0zJ-xnSQLR16bAih>{GD57m!mEzYEB;1&j#DJYOk2kk()(>fWwJ^ExLd#{sba!<_?cojb*q{C= zwKdgp{Hj&5XyGC`39IcDu)s+pKx}Kf63#>v3C$mgp&%LTcVR!HymX|zKtd2n(o9fh zGlm)L=$C<^CIPwiwfsUBome*thR?6BG`1C zmM*C59HdPNIDRdp%0W6Vps2oa@9QA|fV~NjbCfZOb_*{CKBya4_!T4G@Mh8^>;YBB zi3t_%>MA-{xNt!PZ69Sm&dpo|awGoCt>a;n$$mgUTm#5ZVj&y~<@%8{g3m^n6+90Dxl@1p1VV3S~7mU-d!0 znPXo8UfdDURkusw)51jDa{#iekntliBa`hOR z0`ucNL%i)h_N#a~5K==0fA8#NC@LFa;aW)`fo7nR1wlm~)25%RbxO z%oL#+1cP@rZ+bD|$#85IjQe4{0JT^w4+_GU3(Mx47feQJW<(zB$CkuYR#nRR=bs04 zaj!vFe7_ub{7P91@_qX0r>k}g5*dc2GyT&%7xr1u=TL8I5OiMn&=?4h@Z!oGKcj0u zB1d4G;hxT(a9}!g{^KA8$w7pp^GYQO%@9nB7A`XZka^g$k3z*;KJ??$afLjHl|Vi- zlx?NS_(4ppd!BBU-M`(U1hIHg8T2wAqh{RAxP*ux6EPUmp#{PIoiJ_$IdQ5(ti2e7 z8U|F<`X&YwmbL2g1yFI=FP)DyO9HFm921Kg%A^V_-{t4lL3N==zEN$hH7m|)@i1o4Rq7{^f=UJ$H8 zT-)++!oe>YB+#Z!kskblwC{lDngmPx2g{-rVH2=&Xb0~;b=_NW6BdDGPD4(E>G6;8 zDbvg__8r16sf0!Aqm?uO@tSes5N~?^`DgL>oIDCL{;OZz3>yvQs%!iNn6N+RoO9&3 zl`C~qC%>@T`q-ncIFodG<-v^7o1wFW2#zs~_t-cT*`TNtHzm?V7NQM-Z!`ML3+)?Z zPrMCZ*`Oo-8)m|dAJuh*?%_GNAK8zd_VBZ4+DF#!L z*+bx1T+I$AI*5Bm66herE1)Gxoen)-5lj{Bi^d%J;^8N96`4VK4ZpA>3S*m>2ov*@ z;gRZZava~UDHE|88jr=IpJIwz_P!pydkTO?Gb>PBKc?G zBQ@_>tft~g`n4!5DvDKjT!k$91xRk-rNcjbGvFZ*|0+&Yuhj26uRDaEK zskvmn%8Zi$bCX(h3FKkv*a^W^vX0jt99h3{oNx?f0u-ZB5tJQ77!Kv3syvQASc$wMAnaj?EQE2NM7OG@N)5bm!cPE*E6+*HsXDO;Mw(iho75;z zWksdx2j@Mu8Q(Yj<8?k}*ZQRdd6GbW@{^yETcBy>4-Y*8{o1e^xu-+c{eGQ1_`o0J zZfyL#?6Nn@6<1sVtz3(=K7O3ppS8_UA|7dxm8$rp%Cx7y?=esA5#Sfa02Yj>MWhKP z;+yZ=4kK02R)Wq#`TpJ88)efspONyKa_QOGg|Qa?cqSsSusJ`xK(+G(`tzmjK72i% zTObkqp#!RhoaUcxlBRV#q+|&UO+h#KU>JR~25pFb%)A5m(o=9m8Dz7YA?5^QHya?o zjxRqoS1p#lmR=}8fiW(KAaRv}vgc{&SWodq3#^ln?{5FyF37xhK${C>4NNY>4R$=8L7bc`+HxI)?Y z8V6fje$`Hba{5cEFIuF`7rJ#yJP=3+UwE542!Rt;Q?G4AFOsePFmIdWgdS}k_BbSQ zQ&?MQy=CN9@$UD?596`Cw#>e&8{#n}L382H%YW_9b2e`%LobEwn=o^!REM^HZL?d>&v0e+D%2sj8k<8i|!|JXLXW;uA|N;>-v zEzfrMK;>ge5mu~Av3VC8WASB`t+Ay*|OAk0Vn(`pr`Dp<+7qb^vD4q&yMqJ^ADr9jwQ?%aVf z(a5?}e5#A9~H(7D>tRFn5oYe{L8JarN8lKYHN9 zt3cry<_b8TzqHx@pp#mv?$yM-{%)~!$C-TF`V#7i7c0MT*uKd7*aJr@mkZ2u=f;EppK*&NkGv)^ zqN9j`nV{w#C>w@H8_#CMC*6DMjTcvG@p+w}uc=N?H|%xcEv}7f^0MjKH0;sl$Ya1~ z;+^;NAZk*1Axwc|bvqxQ!@cCR&llkH{=APKja9oeS;f)TS4lM@4k<70 z2g~s340d@;qBxS<hO^_toeb~%MBFpw9$!7cZboWRfHr*zYur!Jz4ZcuY3B$HF?~%4W zZ4g81!?P7uzZBHoQ8bAgD=D(V)n5{6mXqyD$4$I#Z7?2#^6=h38mq^6A+)x%Az!Kv zh2a^H+7xHZdpQp*0?ENkfyLL3XrWFD^N=6=Vv;MtP~}u!T%mP%>ZvECvlHqVLBLrS z_Ca+B8-P(6wh?{!USfYSeH7lBj|s}(=%ONt)WxK4D^&Q=1M#uWD;2dBa_UL*B@gOc zBRfGtp=X=r50iY)2Z3Hw3vyd#R;wYVlE7d5m2PQ;#niSZnwXO6&5l6+yk^ZjtUfQ) zjlU((5nf7lznnmMb)~NQhO3vW+4}-aGV`IiiW9Da<3Z#-SlIXY6{hs08tHtvMf#yG zQx{#>1u)^Eb=+WCQ082*2qIOUCLYvh z>>zju<&`0bNo@#LVs%2w$iBE5!Trog1RMdMgl4rbRUYm6ShLk}lY#M2^10;F5T9KR z$o@@+`YiA3p|uaSCc=brZUisAY4f(nT#Ve=NcU`d{(GaH=Te%2XW5G6$~G!aJC!$ zsP0V(EF;2C4+y76?@i8dY*qF3^5g~S2-y+G6N(k^k0F^%wy(pF#CtD?1huCiFaFbb zOwaF_4#_ON?Vq0Fg8#FH!nx+1i_GAfe$MNl&a|$Yra>dof)(jF^pRu5co3_>0Zc+d zm6$9H%T((IFeug$fjT{kk67b38rEM>WttuuNQdo0qMX8tI5*~zlyjw*N0y?m7sg>A zn;pXjND}$pehLLs1k6mTy1Ke#2V~D_;Hwv6Fasd>DLkxOHem(6w^PMfBG|Or8;2ny zq)(BUevtM7nA+t8fi8}7YCmCFdV0E%-Y(5Q06p1h5coLM<3?fhVK4+c2^A%>di824 zE8~obO~lF>$3CH2Plyle0OFj6_6~LQL;5)f8R(NLrRFUQrRkO}l5Bx`O^7oEp*Os0 zO@mZng_~a-()bFIo<)Q{@yk#r2 zwZQr+?s4O2WHE?7#7Qc#S3&b25OA6fI{?#AAN(?D{=oUi0p3?%g>6#C;jcY7i264n+(I+b9=OYV@YSqhS>`fycE0@T0*9qskl10 zSGXx#qiI(#|MTxZA6^mL1ooxQ`_vmuvPwt)6gEb(u~cQGMz9YA0(hq?PG!9j#^Vs0 z1}F9A+T=Fmo(ZA(@N`I7IS26FS2FEg4>>47rw`M&1@eVpdGwO&ggIwI!^iKLyFIS6 zEPhDTmLk3pgcd8*u}C3SRI!o_(GC@xK+1~OC^^`1oLDejeO@$G#?z)S{wzAeQ1cc4 zRA0!jhe}tO;Wq?VU%t$X#GKKTwBv;TxU#Osj6j5`4ln@{Pl8YJ7jE{YxC`$SJc_w= zLl-?|n7Nw1ZQC{_mbXAR{g%y}Ri-=*QeRP4F7JHTyX49%uaq*#tJC?*MKO)YJP>tk z*3EtM(+ue9np>R#LpWTK8UP+9oR|py_F3o^n5iIvZf<74y^b zAsDgaq*piHVuBU|R$PT=@rTCtl5~d|iGU;Elh~~O!E7wqW}k07loYk=gDRwae0EMi zT~neTvZcj%p!IKHspAG$E#t7vS%1Y8$I-rK zFj1ywFdLSDSK=&=EP`h4-5BM-@Z3yEHwg3Twj43Yd#fLQG$*9R)zjg*(ud*>`h^2G zxgm7(rcLsrAN^4FwC%y>P;4a4Bw&H{Sh?~qu9UTFFOj;rFr=jUc@4;OX2yvW&8p)j ziDMEIgEYRr=uI!E=5GLC3>DKTJFCuo1p%4Wi6g?2q*HyR?cF?TT>M&$Oc_u=#ACa} zsJGm6te15>x9M1a|Fx!PgKMH5W-K@{)AHa3lpBOGS*Uu`eBEqp84>p-`#didJTyRZdU=9j=;^Fkc$78P=IB-pQL)EpbckwgcXLX<9ksAjk&6R00 zuqgqLwoIC}O~)&z`guPL$EYb-P2(ocG>%LY1^|%;UJ4MSp*8|T`miEEO=^flxy*dL z5b4ha3oPnD`wJ7pH3njRX?bZg50eg$D4UAr2=oaXYn04?AWr?tCLFcM;H#_Vk3}Qt zhq+^FVSz}d^mS9D2igdrUXgm%b+Lr`pd3hA2JrQPWuyg5mXVIyQLu3yGL@_&t<3tMKSw9*Uq9x0&HdAiamu16igPOk-zZmL8Jctv%P-~p}#jv7C zb&_yayNhXdgFpSpLJ}CxxkH2i8cmIMFnQBdFmDZw8HEr#zyrVyebnKdY=l7(Y>ZS< zV1(n-#((>D8U!Kj(HFt^84ueJl+56H*emIgvKq=OIc@*n{5f!ra zAVJ>BZ1x5V>D9xM--9W)O@l|pazBY#+_f0CL0BG^8!ihm(=%Q}f8ohHL5NsTu6*6^ z*2(|=;QR9E!+*pVRfw}d?c{|QUMSaIdyVP?S7PrC-ZneUNBuBffuIJE7DTyqPZC8- z7!D&he&FvF!j=Z3A&4!mICk@%EjJ?Zp5mY-_9AK!1o8rBF&Yxjq{BQ0Io_gd45wJx zWW4Z9TFZc=4P(;u0x}%p4w>kpe#V%NnH1QhSWm4Nqq2OOg#N$-jsf=0%^Da006+jq zL_t(?rhN3)a^S^6%|VH6y{7(n*P#)Ns!v#a&AHr!B_G zsL~{KkUgXEp8!{WkS0kPHzJYD^@50EQA9N@kaUJX?6~4i0-3@xd>rIWNh$Ng;1|ZG z77|{SU_kmAY=Xh09>mX(>?G0*p-$h0*+{k z3B)25LoP7&gLIP^N3pO3F-xrv>%?*(IOHe|0VmmIc~Mf_0Ko+&B*qL!0?k!N;;H9| zpGKW%tcb*1Cld(IL7bBh-ishV<~smfDJ#l4vl1lWOq=b?au73;{1DUPgn}TwMxXB`($8qkHnxwgzeAg#J05|8wX$RA?cVda`1^x zRtf-6@z7@$nc^8uL*FR>3h|bI^!y@6$dINm$jFlmp9|FtPIbhW#efoqyKPV zh11l%cr*f(fLcyIsM^!8=n|@i*?Vl_B!;%^YTN!#5yc6 zyjOQ}TizqNB+=s_Kmtf|sDmUUpcpn?9gE;+%gAInoIpBI{dmo>2NE;tbZ|y|m3iYm zB*3@`VgBPK$TXP=2YO8(h2jP-6U>%_Lm30AFU~ZQxdNdjKJ?kO4VLt9qIa1Q5~LMT zFPa`qOy+5vNfAL!a1Dd4Mz|(AnuK&3yALLU@0W3z9{+4QiYa`u`Pds-AAE|0sG*63 zn_6HK8WcJkQ*nYfno?9pfZ08Om0_;us&_oeggjq5sU3m$}|3c=MI*!ZU zh=SQRV>@fd;!!VnGAo_$3Dhx=!)ZTOAp4qoj9zY7X$(NFzW9Vnqg{rC9OE9{L?dU( zl|7E9LCl!xjf0~^H0kCZR>pa0!%On5Z~mv;ap#>FzdEEp5tli0Yvq%F{nzrYcfVW8 zE1(?&&$}&?J$l24=44XD*EeV?31K#$fHoFt2II z!cS%9G1l>D^YT|Gi#ak?d2)pFx1kNIBc7Megfx^_HY=jXd^jd*d9*AzClJ{J+LDZN zWE_B~^~aIt{3=PYo&GI-IOk###(7Nsf>8IV)@gl7c{%%h-04h1z!C5%+@fXx#va0VY_;awzj=xL;RraCzn8i4Z7*+@n{ll?tj$wNt)_(^hOyynS}ZIkBOEe$ zkLjCq*&Zf6y~eq9Tzy3G4OWPc=w9YHGA*X9tQHs&L^$GTc(=)fzIx5HaF50CWsyq{ zp7huG4Ni0|1%Y|3VmvIc7m@kuivYEt7t61rK^XY?MS^9ho*R%OmIY+X%SmaNmoLYj zg3s}|&liNgBz@^23Fjse0YXow{f7-qjSbLW9EN4AR1-~EL;UFZcB$Y6~S&PivL{OaD(j&@5TC$B|l+ z&$)FxfAYyEi92oOTH}vxZ>eT?c&ajXCVtH|g|fy>T3JtszGE@rASL3=cJ?bcRP7 z>DpH||848dYUp{^!yjstb1y8B<3vJR2B0RJu}QYA4-*+`-+`7DZ2shz1%ihZj#Q37g9QVuj|E^9yA6j*J|6Y*^|S231_7rZ!)8Y=Za!L^YII zacCnEBf--r-c*q9Z6(S~4=dXwc4kB|_c_{XmKd~2q>bDu(||7q4?8fMVZ@9qCV35Q z5`j1UG$nlzAT;7D6|m`hA$&#Ce3^jy;fDkNaA(p*7%w8lYqXvW2tSez@0O;=@)85` zT@rJOvG7z$CBQH*DwC1s=dYueUWfz~{`?9-u}5n43I#DSfyf8X`Bj0F7S@kxSWHQr z2YH_b@(M-K5w2>}!X(S2lN;P)+9U-*{G~7$vqa2;C2|c!PTu;~w@7_MgN`SBw(Z!i&pG;w zy|z_0Xs{1rT*69i;p#GJd8QHLU=&}>KqfbLOYs>n15cRP-#oZD;>~7f-Z35md=Fx* zPGgL3`#sc3;)N6jeo3goEU3n_49l{9r_Zk&aQ%?X$a6=)r)slu9K-lU0!#T}lA)!m z!t(L2l&j1!3015}WbKtja`cHwX=}!gbi7b#EHen1` z5KkB%g7WdJB^nd$S^gxa9$9OfW(4@hG12V9wf&UchQwQ_@^M}O<9*1 zNFog)s8I^|(|Q;;p9X*j2_)sEv9Cz;?}JEA3gnjNpGh!ds4EF7^W`Q-ZnBKQYFZR! zPvDa?#d!FF;QN9y;FNieqRx~M>VoVtY8H2@`hSo{Yue~JV~iZl1s~x8>-dO0y%HsR91y+l#{9@DT`0JcgkwB{Ybtk zYtA<5M4rVJ!~=O@fi9UK9>@!uL6NP7kD*SKTi=7#&?xFyS_!cPh%2%Ga1&+(b+9tx zl-Y;DJcYW>+c+(0rySTv3`eo&1vT(sdc^!0nUw7SgvU1h&=r7c^Va6Yd zp5&hIy~%gB;^;#ZF`){~vNd%Q{Lw~SV~k1VOE=FrY#OudB>T+q zfy8@=@5%%;?{xgJ6+I6Zc#l!{dUOG-W^zx5{alOS*|@U|v)NW%N5H3Qi*vx^gUc5v zmlpURr`#k+Z+%2Tn#yZ}^19lCZAO!#ChMi0J0E_=upDMbb2hEf!r1usXww;fZT+Xh z(O))uZjT36KE|dS6(s~9*{xq>!ts~=fqoj-OMnNyK&j27ltx70NFoZTk`CvZK2A8P zD8+y<1d@CS_>4&dmwhbBwZA6QNC8tzN^ST`} zhz-sZ{%XGYH7P&00pb9dseycD+l}J~op&%7v*XvG2HZ?%R?S2m_ccmyTQ4*aMX;za z0ClcKQhG+UhGBRQY17}Q8D^GPvvp}q=cCtND{C)WD~lE{mWs-9bPLljNWAIxN4|-L zbr=%#DpVH50zgdS9sMBlAmGon$-vfbDL59BKaL;R9IWw(c`mw5W_BF4%Xys+HP*== z1qHGAlimZJ^N^n>RPc;Y!)@}4>BaBClK9URQFZlh>k-hB?rg%h!}!3lC3kzN5vlc~B!ru7chSDdpq5Z61`a7+)5CE?1r%|~B^>d(8zv9Dy)!wK7oG0jWch8lMC8rh%M zun_KYG7vxmn>VcBJEGKs&&TRZQT+F5emcnw?6l5Tl~{r%J$aQpNzsz zkvsqr9~C+sb1*0*Tl=vB*(`Z+0D_M_=}c0}-%zhA92HcAv9ZiJFui)dpHQ>jRn^r} zT~mWEj5Pd0wX685=v(#9V&gsMy!Jbb1^xJ?fPeaiLZD5IJ2(LdElwcH?Y#@51I(rmogAP>G=IFh>rGRfuzvLA>xm;@(0qu^U327ZOAwg?eK}at z-|x&=Z0_=x`Z(~>5(dW2GpWvDO+xM`-n`={>1OghddJXr57JOf5R6tR1BF2;zo=ds z@7acHSm}fqPX{)&mS0e>X5saPVw2<;YtM7Vn(=35zX7WW>>$=iVx8)UOVdqTAf{+W zc3KoITTv+mN0yj2@vnHzLd<4-rx>Avj)0E<%2e{=b7)DBz7xE{>GO#n>!;!Hql>&e z(gSX!XWHDdzlOw`(8GUC7!qCM?2%XgbN}_;{yCrAnMseS*!=zX{OA5U12#%Tp9rdTT~F!O>2H)K3$z_q`+irjcX)ZQsgaF|-1s`40`{ zt|kWoum7@N^79B*&5Bo6K^w|eNw+1jmmw_8_wJPPv+JY;a=N*xHK(i|iJF{(PKjKy z##PYCiCnUEKw9qE0TBSI9fT=$tO^$|EtBHYs+0h6ur$xOKJ32nVMOp%fAE4~+=AsLQoN*8x?kzU9-E-_@8~hwP^w_69*KDzGZ$}P8O*?Y zxn~bp12gu;?k8KNySW?thN9Z{0@%=6vbsV7p5~(LXWJQwhpW=a?Y&i3T;0+JiUk_? z;O?%05Zr>hy95aYcXxLhcX#*T5FCQLyVJPqA$hl)Z~uaGak%I)=&^dPS@u+|s#-Mx z_YaGMBgum(78i035;rZ(dE0$7m;O z`U}T`C8=-q4uA=9GZ3A1TIE*Q^@~{lwr9BOE&hYii-#rq>`Msp$iWJ`AedzWnGmt) z!O919P^HTYzC{>xNdQTLvbUtBSA)!GIi1cvn_wXr~c>%!X{5pcNODDCAV#wf3-j6eEyus+_QaOB^ECC zHhCKPiLVDlN6@(b&IGooVB~G{pPOQPW`AgD&J*51A&mjqtql_R?Dh{gT#-ja+R_xW z!$S*Ay=udYFlctg$GhdS^Q5Z$7FMq>NviiVKFT!rzYXPQmn_9`oA%X#Gk?gPc+qh( zAxJ4mT$S^)Lw=hnn{$!Ocl<^C9kz)#iV87k*1;z(QZ8k8O6dm$^lW4bI5zJHRQ}75 z$)U!&2_>6PMgS1~>uZ{h)pyTO;qffZg5zCYd0uPK)}pONQNq5r2RR;PY*an};Uf+eCsonwVCyE39mzxAN$>Td|)E9gCb$tJsr&Yd{Vy;r2mO@xtTNC|N$sDk4_-l+~NtPr#&d9AMk%+LF5i1@1GGjhk} zMXpm#IZ~QuDjFkv z;$@t9_6w9b9%|+|%NOErPboi}ojTcY|B&Et)iV4q66m12+;Gs4dX!@*D!RiMcOA?k z!dkkuPL~+lQDEim4r{;(%eBuVe)TC!ml=g3GXY0!&Dys=9j<}lXHSJJK-HL4KLVj) zm2UB%#Ah#QG@xK>BW%C=vI!%5}LIbmyvS=A05MT<@&_p2h;E9CqvmTSjO4VZ8F9UIklpqe8v+K zoo*ZepP@_;AUw>jh(u-Ym&W`t=a%jmW2d%Dl{>hIez$omHL%u9|RTzo`zNY5!K!s~TT&k1f$+U-wsate0C;x!+Z18UtL)$R%I>1EOAF zP0o+8){hd!#O0*mt|dH&E{ST~LC>5S?GCWV(!8$7vi0ovFp}sfL{JshIXpbUQ%or?l+gzCz&j=Vamu1=1355H}$80;@PmW3y?urBLkyCDOveF^~ zZIfb3hxze@TCkpnh+Sad6L%;1v0@qTN8IsfWA;bA*Y&;)3$?SRS-8tKQZ-ashLHyZ4) zp{_<$ZqlF$<5p)P8vdb^aGDLbaUIjreD3^kIXX*wyi30Jgtu;WnzhQ#RNw zag1_eWu;$;oTl2q6NjV3_GlV;N3%PiW~SpvCi&~A(U(>I^>3b^Ner549;1%eu$BrQ z8X17!L*4P1;?QU!#9zdcB&zBCssVBkV;d2NH^MhI{B_WO>_bMWHSEhD6Ndwj_PP8t zCbztk`yeWqa@9ehh@`Z6h@pJi%?4Y6(Q3UyO>hT-5X{E^iu3_!O;K}w{je`2J;|1+ z;tF4a93JKUWxETG9^sT-__UG09Oi0jEsPGr)=&|eOmwB*ygsw57hA9=t6M*8&XE=~ zJcB{%3pjxU`EAYDJxuYNv^}Yx5PEu36aA3K(e+V_9(WE@P5r?$#LN4IT!-xF>eHPI z-t}L6$QGxVDw-_fPEE7z3EzI6`RK&uQi&$#%iAjuQU(SnZfp3X3Cr7I)q(#ZPMEIs z%U|N&56J7TBll^5#33bt8)u4t{E;X%q+krpH5ZEUe+BW@UU};!R*K~irCa#WIexae zk(u_n50uoGc2o>trQ2f7`I&MR+whlybfC=oFq6dRirP$dhFPrf_W__F?uoAp4$~ZF{%{@L9r1^z zx}p}H#y`w^WJ=&piBt3R&Q*PQ} zANZ<+si5Dstx#xn-ljY7&OjB0>dRB=~FrSS2;SlkcGVmjp+VSW=Tx!I!hW)wIcj zJ`zu!N=aSGq~%}iYvp)B#pK2qz5pLjds@C>U}!_1hm5=9Dy;tM7@ouUNDZ#ar0|R+LN=h#!O=c& zdUy*>Y#L^?S=PMfu!$fpvQVo@0ykIB^#S}~Pfq-B>U|#1c`RBfT-xifsipYa2*uf$ z2^PdVL{{E9H8}o(NL&c=SVCB;l0waPkC^i<^wNS?$zpE*P`6)0+j^}tWvFpKzoKHS!Y`nJV=N{!lB=QwN1q@P%xB%y5H0`27p3Fh81Za z<4;dxs7WaJMH~BJh*J%z&M49t`cUfRBuPilUAOVg5S#k8 zHV@UR*jf-kM6LrX6wZYE%kWjLUp?)qQUlL>x#Rbk2A~r9JqEW-e9+ynR!l|-i zmTkhe9$%1wX}^F(v>%uh^?X9*#UFJ+G0Mo4!pRsE1_cdIHN0q*NKzICu}76nq%<>h zV+lt64SJvZ^^%2Agr9i5Swx%%Po9a=d*4QrP9Gjjv znXA;VzPmKhd-|6aqh(@wu#k<`9G;bAR1_4~n)5?NC1qp&>n3s4h4Vez_o$Y)NO9@91a9z=$O- zqI|I*qdlMtGuU9|r8!7E1%9+}_z~oF#m>PsTBf-H z+O1O5ac3FK72vff+vvSenRF^EJ}uWIzHQwoUWx-9JF(At57)yB`w43_p`|QQ!oi9} zwTDH)FC=J;$kQ{-Q*b(C$BE66r5=xBV%BOJYcrT{z7_myX11xDGMjPa>WJ;ZbPSNJKpPjrreE@ufse2DMdgGU6~xG*TosOOMiqoTI|L?yW3(bY&K zo76UiL|MP*L5cl(me8RGy@j7s>&E91!(Cm#RkGN`Xqhy?YT^}PRSiVb4aaSbEv zrD}s>$oBTPv7h?-;jM=-qa7rZY{$6#4S0FFX?85Ot8NaC4l|#ej!K=^^ub7})NlGt zn%dL_X!Qd^vYx|b%b8h0^wAVny3L8LUFUM};o22sx?f>(?W-nHg46eTQpHJETv3=s}EGBH1|T!Z*kRg=X2HwIup zgcXW8oKg(>fDG(BrX-icm!vEOc(!e0wBG(^`dA84%A)FLai$o^mzWM`eQIrH`^S3% zc!RSGjQ#i`W9C{bt*^Y@`>(I@2yJt8yfHUcT1`?dnLQ>`B9fn4n62RUH@T{9Tqj9O zyzoNCSCY{_t<`ZIv)uVhr*j(tTtEXsGfK;u>Nlqz)e26p_n3#CjgV=kHA^A4vfE{% z#h>ZlJHCC|7^e_ZXSBcJM(YV*vqgNIm%d*xjwxnLkP~1~=etF$N~aW!&xnKJLs|nV zn+k1JS1#Edt1ezG>i~a=B$llqFk|D#ALI&^1R`CBh3}UXNXZPGG|wnetaGr$g+B2{ z0F2z+3ht=*)O;RmRdZ23wrtY6C zfgTNdti%ZQD+6+eJG`+i$?=9N%{%wBq3jrvMhd7qy@Fphl1)^(!;&)XY}?C-KsOPt zB?yiEH5xriLx9kz=9~brT_aog0G?GA;)_V$DSx>af@qE-bI;PGMBWmaU)b|QJx9)) zw#KZcARZe3!qDffY&5EZHFAF4mj;*SUnzFY>%zf4Tc^j4jpI2r?pZ5jtzI1xR9-0^ zf;zmiMN@v!IEo%dCGk}yaZ~7}E-gG2 zV-gb{4pcw4Ib|keURYsg z9ASFfH(95I9Ur=kOE*O#rcLR1R|kn}f%#hGr;(O6nmS1K1Ahu7@);vSGtwtBl21$ws(DVB_G<9XA$lJds|J4nY336{F%65gW zM*Vqp*IIDMsa0V5BMAyJeJ01!pp6PlAs0gWdRSMFj-qHzG9HhshP0CMEP*6#h+sM1 zd-2*c0dEb`Vy=UDZ)UAp?9nZDT1WjNXtPu{=5&loGe;2>Pr^W%I_m{tlWl?@`xjKs z<%(NLyCKPSR+5YH?*aqh6&k)S^_qi~BC5FM8fgX!r$kd@4&qBBk`jc~2M@*NU&ecX zZQ29|;<2(DSpp1cQ7zi}m$L#KF2T>5a*(f1wdPMeLmC;Ch z?9EbXo=C4jM7J2zaq@g zJwKPBFFrUC>c{=Vzg)zJK=%j34h0gg59id9Mc4RX*vDxMGPOkko$d}QGw`pdhx`DA~$48X1&CQVo+l7snpBk9WS=HbK2K{yo5XB%0rYJM- z!)hHEN{kKA{nMp(H&EnrPbgw3dJn82TOad}qerJ+ZP4GRUFs7-sCFlukTRcM5h2pr z)|nW2#bOv1aJ>)a5lPhFg2Az%nCJ=HvvzgJX-^LoR#G`hqmc#^Y^fpP>Sz{o%bBW; zjuVmvi{<}R$K1}Yy1$t8?J6w)u@;(!yG%Bh*-(^)+OwElDXtF9*5rfR?>S^Ljq=dT7ExD* zzQxqbK0Nfs10_=Fxlpc|vKN9jSR!+npzeGsl!(ae6es}Fh|l$mt*D{|mXY6PR&=D> zhY3s_FQ*EOZ?kD1G5jakyU&Amu>ImG9Hjf{!XzeMWNDzJomnDxD){VwCEyGWc8&2J zy688tuQ%#Ez8}=J6Q-x{Ak@bPkpz>l`A|SI%c1xgnY=9r484n4bvT?Npo;Y&7L3v) z8Aa&KbIt%_V8LI#Z>(3T1w2JlV=K)q{|QI^sCPc`l`@34;aaK%{soEd>WCTENbh&{ z)#b4qYJ`f4;7W;$N<(jA9+41z(47X~NS)8?Gs5`3$g^@M0m1{@qdHEWEKFm z8O0Fsk8`7Iu(cM_LA%;dBqhAIhn}!oH1eHuZ=|X)68$xb5(BfgKwI)a{`{u- zEycvd?=8bY*r;o%oKZNL)KV~q{iP=0JEP&M2~v|sv^2fuoW^4+Y+n_s6_7 zdwwMdUlOPlIt9Xh95h?P_mXQD+Y9VrtF`~h!;&?{D}D%%wii2fw_@HGwkl%MK5GpI z3bl0doIn*fZ+iasde8?7tp2|~>Nh)7yu?8?nnuHO@E4%Sgs(q!iM*k&ie#ywl%Q(U z*(~Fsdxy@W-jSbb2;lfzos7*GE?QY@wPKQYI4Rc!LbTGadfo|Cyc~-Y zJatqxwT^UgfbtMgPcQmM5h(yZKdB$~P*qfQ^Q4zhFwk%)*xJB30l!Ek?gf=8X8DL; z(ui|wVFnP1w|^#LymD@{@pGsuYLly@0Ks;;g(v1mF{Qp#nv3lzG#KQFkSyc%!T|#W z69mesoy*b~QDyn}-5-GBu_84wEk}`dhn_N%FVc8W6ILfig>pGF2R{n;FYw*8@1@S8N|ltLZk1vz7tsaVl+er0%H>(7hmP)KxCvNurmwGdZ)Iz2h<)Onbs znhByZP_z@4NsBUJ{b??yw&?C!{OYDja@_LRAexwEhmh$j=>{&qleP{2h<)8T6+@@Z zuFAy=IjcH;eL3S#y7u1cLby0b&+&K%h~GA!Xt{u#>tQT#qm!fZQ)Z&?-tv zjLRBYAGfBh$0uXt!N~> zdgdTRybL3*dqIRa#uvl8@99KP9S25_(O-Q5T#|=gXQ3`x3n14$^gH@(b?BNevBluI zFp&l%pD|PXfz-KVBrukM!6x$=?MeV;j&qXg>b#Ev8&(P%*YnqcfVPWAbNsIjWvC-0 z|9iBar;~LL=!M9`pZg@Oj}?6)U(U}8O6YC%j|Y_PFa7VI$iwV-U`#G82A%mT?oZ<6nWDq*S;`DQb7zeq5%U1lce zW&4H?s?XOrX>V<4m~8)! zF$-GLPtY|Q&de!52Y2LN>FVyJE!$_-TK?b;MVC!Dnx_d|Sf*|$TLcAs@O7f%(~f_L z8U-w1!A5bhT2aICARHpI}xB+WRS2j%7GBCHHLlY zhUUD=Q_}8#&Jm9FqV^|-PG)dF1K2@Uabjm$O#AJJ7&NpTsp9?GwDpJj9$>0IH8svz zNP5cHNZ#N(ETxs!*U=koa4A<6cTrN1aCJ^l9$APRR)6y6?^Y@B5nM|5d`h8cB>3~y zJ+d6ar+t?F_%4^E+w=fQNLL9iU|i2g1`gDT8Y*vXU3QhxOEoR5WTt-@L6m2x*zyCm z?#q2MCDu%43iUzRQZ(Y?yjLg~?#it_UN=Dz6hu{9waV*pkltVgf2ZD9^EtWed@v($ z1G_#7!LW}TeIQ6uF7im7F7EU+uFVDI%U+Efn4$FChhez)N#eRNzbqSu=9;HORdo5p&sn+E|V|xx}Gn192&}KxB?Al9e8Y!xJZFwFI z?qsyA$PfmO!v_}ZSXcHGO3GGWyLo?J z@ltJGT;FQ-zD@+UMg>0q^^K1{obVn7mZ@?N+V7%>jXsEMqX*13>Hm=RUhG5mjY zT&-HCx7NLa)o|Yb1{5c6KzrCVR=k`n$8+35oLgrZm?2_@+Y}p7op_ z-+sqF6pnLslX2nYT2UrE#eCq9JvQV+L~X~I`PYj6Fzz4!5HCRdVb+hgoq23$$jy68 zm~(I{Z*PeI?P*|VVLQnj2T~^Rp$*ml@csRL3V!gj<71c$!a<;|cQXTclNT!X zT=OZ>8UxW&nzLpV3qdRNmfdcZH@&l^haj34W)LB`DQP^wnwmBtOYahitHJ6udFrO3 zHJYXke&=70g#WqAXA1yAm!GkiSkm=PcPfQy(ZCjTwWL?i4L>kk?kRc2bwn8GYdq`O zY=v^h#Q*HB@sPnZRKdj09q)l0J{aG4=PW<+x_O?%$E|?r41%_aGtH1@(;;m_;va#9lHwBb^8`7xVcZQhdxQ1amE#beuO(e1k(Ox)6z8<}D@9boG zt9_{f@&WCkJjj*Pv;JqncoAZvG@@wr4go9g>omJn(=`V8kI^!n&OOhX569jfRXmqd z(FI;!qkP9sx4C2mhe~?!WwM`Dek}}g5n>G@zjqIJ!2ltrvRF$|f(M+_n1ji-D*rw{ z{ue(A5g|O=Z6A*I-{ibrrk{wHEgkHiI1ToMXE(Ig9HTs5%jP9*B@7yMRS{OK)u<3z z5pitS)rII#+jeYu1mX zs+9&QRd$R)&h>WjhJPmZWsYU-Co4a?~BKpn|iAz?dDv3cMtEaQKc%k8GbvfuFpcfLG@-Ma+w-bNSI+b)~oef=G;P zV+^C6h{E|#KhVDj`Xf_j5Nh=f_lGUdZ6d85DQ8f;8g34!NBXDb#aTM6mvhGEljJ}< z(9po(qSPKO>bt?l5^38s^VEMU^FZD^JX`MIq9Wo8;(5>JK#DW|yF&7BhFuF1BW|^~ zlhLSo>|?rmDBTHSrsvW%@pP(Kx!b0BJp7s;p_1cQozBxA!9j$-Ict=`+d0eOZnMw! zmL}(?iu95fwU7ND^}~kK)XXZVC68LQfTyGSA%i%&-!wKXm3vTHAd2pv{pBzv!1v7i?Wxx5kLRZ zz4ETraB`v$+nbNa3N@tQfgoLjAizqw#AQ*=Xx`|65-IaU;`8gr2%eYg>G#qd@Rl#e{MFSo>-p(h#ijkh z4IyMff7s^*2a*G8xSVEEJ|Mhbv3(z&Ar;KgWvm=cxk1$4yF3wb{!IyrFLIl=ylMsR zKI<^l-WkviAT0*f@z&{j=}#0Dswy*Z)XYi*1p!;G@BNTt@-WpSgGQA?SOvc)!6zd1 zaUN^O5SWf7f)wVmkG6c5LbmbLKd}0zbpF?&{wwr01k~RY)m*OH>F?kos z?T<6&u188EyJ0GrieC|FGU0uE#0(W+BWi7@g3?0rw7|0f7&^HBV`p(;0-}QQ9Sx0w zucwF&b}LPKiL&Ori@{o4O#+0iRw*LcsC}m7_Tmwwl|BQc^4Y+|wO3z~JIc#n6+Uw0 zsf-N&PU#zR2~oS}DeYjx_c*P**;fs(%_2b=f}G0HZK z16g1Lb6|H-EY@x5HoWRroBu~!>xF%eTB_6)D6KVhNwd7OCA`j+)67L5MyAVll}yKD zsc(Fur$2PfNC|YIFO)Ch$%#|eonhWyke>nWT_)@d{>PjO=^(jFfP8O$2UIQ@`T<-v-gCwX z*5G<(y@e3D;`@tGUH!Hae|q2kbGZ%RH$D{gs}AhyMw4F0*C=YH%<8arus4uz3HU#k zy;qsxAa-l2J#OnUFK_eqq+I-N{JA0cl-xqgg8#B3c?-~XDcSi9Dr$;ojlbFx-AQ|^ zFSSm%Gbt&n_$!vwUnB)#mHGi~>4JetRquRj~! z-I)gM+|@*n5{OyEE?cdWg$9spV12&iWjxW=5AJdh*%%BnY&PFTYd-o(QCnOtUi@KK zp6h+OD`qXD&i|-~nZJN5IlZ2){d0c&{CmC7chPw3Ul=V$fEl;!W&c}OK>KwLt1g!i zD_xI~=;dG{;V3sZz2u;slpFc)YRrhJ?`^KQ9I14*|L`XY5T69I3v9r2$K=iW$Ub8f zH0bvs7PSWI9UkkSay~B~fO06qnZrzK8I;PSV+i==W}_YQmn$-~xl! ztQ{u)4vELduJ3o-ryWH)vhF=>ii)?z7)gAjWpW;x9Z&p`PobYKErF+<56cX8C$@5= z6=DU&JE$5Bk2DW6>lQc-^efoIpTiTMHIC&~uEY!o_TJx%;CrlzthN32HkiVt_h(OE zNj6lwOqK0hhW{dhZ%Sxa*4qlhe)?UygY%gsoZJ3b!1I2S(@+-SS)%*t7gbu?uU-u= zPn~D+t!KWj=5E}2*`*=s0q8IZVASKE6N(vVPq%@-EX(-muiQ3JK0^UK4vy67c3NA$kgIi6sq(!v zUF}4$N!B&T=D%H=jxHW=QaP`#Qug(|$MsJ?c9(t}8J(`AOB|+oO_K6A+g|_dnSSh2 zsWKXJrcbOg>v*x8r49O3&D1_ab@sMFC!A}$y9~58cY0`*8;?7C8ArWj8Ei{RNRjBt z_KOpS} zEC&W9sBpp^N@lXdtmW(dPXn>+=%#w?DIZ?1vQ&p+#rm)3ZnAWUtN1P3EtW9mkG-FN ztGeE`nv;9;ODT~bBlE8$$h;4w@$*sB4d0bG1hL3nivUJ<8*3UqsK(A)ihFFWu<<#j&z=)m5&o; zm&@yo@HgRE&4toyw{x^FY|g7y@3zjI0?41nd1xAcljrDcu@`TQRTnvR+u^6cK#~|u zZ|96bhUFQ97wZ~;O@{w>=ogF(Ug@*`CQ!ul_&tdSDHc7tEP}w-Qp;&XuG2f|w-&S5+Eq^Yr!6Tai&(w46F#oj zUSqq>f})ajqB&mJPIu$GO4X85nk2R{&x~}(SM!HcPOI3$RTIOP3Op~y4}mOP4~TR| zh^Olz4*uOf+E+tOQsu;97bfzA9cO0EFMBY?PmPo%6=g9}>rRR3j@_a6b_sLtoe#S& zwRog-0~t2?ZM!=Ag9>6yOFz6r-_xAddRSBcdIUs5hgivX>EqS|1`{6PD?)^Yi{w3& zLdS4MNcNBj)?#5|x~IigRuCKwc--!DSaXolA>2J*wLfxd;#;9KM6n*Xz1})Ig?s0A z!S=?<-5#uWu2l=a-q3|BGp_X8i~YfYb-;e{MYuHZ4KMu~3sy zY!&+GjLA)~#+GWYYyk<-MZo4w>;nezUJ?dx4e(rcI;8X@V>+);p;K;J#Vsn6S#x}% z%hkq=o8feB8DV1ZUY1g#aml(}`Jm8UL=w1DhN3Wa5ihC*} zeP*GcJ;K|#%w&8K^tIQrqiNp4tS1%@$E;$Q_{}?7|K=;TRa<1zRt=Z{>)sgH(;rA;j_*i!o6s>5~>f@)W!!0 z0I`oiAbRpAcW1A2OUR|bD<<0b5gq0bMoTDwxb%|6O7W7!S*L-& z`2#v9NZe_vxIZvzXpB$w$dAYAf`}XZI+TCTIYH+m{YqIVdeaYcbKQgwT8TDm&%96@ zfy%DFU5Bvb+KWPQ(kOh!zoeC#__aCL7ZpY|*=^s;yHxw&LUdPy@=NmVT1 zcc>J%&APB4t)Duj0bh%SixL?L`U-!|{7~1TlgYN?r%kb6ice^s=4brqla)NAs3>P4 z3WeNf4^T6_v{nbQq z->)CO7Ff>4!8}$aRD1{C9)eSJRxdK_pX#_sz~5kaoE=5cIjBY38$p7Glp3+%GT*L6 zUg8po>-UeGy)6-mE+X5)=ya^_wS}SQRV*wpWs<99bfbQbQh6WbxXic9s4O-zaCas) zSHQmB%!^5XNT6m@V3RuQXdOw=sOMg0$8F6B0)sF{FE*0=uw>$|&t3GZ(0ws>-*43F zc;-`gJflU)LtWC*Yp;PD%^|}o??xf3+UL*`oLx9V6N`n^_2_xAm#>Z(xSETRmE%%) zMT3Lr;bs}e+)PvVn5_T+6RNM0GMv*H((XVFwlrF^wT`K$pr-`Y^3MmuC~jw- zCX}KdADWNb3w)6WTJ=|B zlaX52b?OU6ca+t#iIS<}D2sxE>%hrM3aGTG{;XOklvgzklUa`b(qLN2qDxlz{q!1G z7j*pjiifY(a+zh(Z_PsAq)!_x*WQqUVr`Xo>#xC4_(Q6Bl>2+4ho>)X0;}-ABnAy* zO4G_|O}5aw?@x=ySkyeM($j@woXs0b^`0Y)Y_RqXUy+{5ERH0X>6j)f3EW2yoPoDz z+{L`D$lv$e!$+CsQ$*z`AkGcRE&lKUrC1!o)>ei6o11-uxynx?eb7=)>iyD#)uEHM z&(PdOBL$q4c~6#E_8ZZv$nEE1 zbXAhDte$8Wmg-A#D8jXh_jUH;lgZY~HIhr5y?lfCbgMQDJR-p$s8GL8kn1A9Fiu8z zs7-b8dCytSzs@Bf)CYss+T}DT4>Cf@>ats_}+wwJ3qM~AuNNLp#I*iyjB5p-ByA8^pKD;JT^A@)_(ni zLVHq(ZA~X%3U4K6P6loChNsL}ijT>SHpBS6+#NlMwH%1}4o+YYMPw6|I@}b|U!;<> z6`!+6>7TR1F%&I<{QOQ((YUs8c4sEA= zLPb-2yj$AKTGso>k%E7FP>kZtzZUXXvNj!k@iYEnK)S*~$=P)>muMSgD_2C#Y`fxs z$m%+m(Lzp5aT85>KKArg;d3{fItt}Ptwi8Ii~3CepqV=wG1yzCJ{Krj*9Yub z?2Uo(3UTRTD|DN0LYFN!gVin7$!fi#rV7~++$u#*(bws>)YmcrtlomZ&g`ELdHIcr z?jDoabZ29%8Q4!!JtAanlz|$c+L?H9%YEvy&i{NYIQnQsD#a}8x{X)IlY!# zRO9WcG*pG87+2`H+IV(^3~_!Y3B3C6`W|l(1K$plik<~4mUW6>+>yU;uE@o+KRfcW z%|z)Al=XaK81>`6(5yCW(_k+PD!T#cBqKJ0zB}#?lPsmz2;>WzMxIrehwi23?cy*?~Yl6klR40;55@f0$f^K0*grDsET z=TdK8gCDarS={W$#3_)o$|E9|uN|iR8I$#l{j8TW??Kq2Gk|FZXGgn+0jda3xss~U zsW}-Zq3`LBZ+lKU&cdyBTs3GW%|91#{<|YmI0w$~;{a8}*?vT=>zhu_@JO#%6ckw`sEs z(Jjj>P5VB#snL6s11lze;~e29+H^_P{{3>50v|W`zrD z4vX0xHPeF$-`~%(?ConnJx^)g7Q{LJJw`UE-~6S8qj+xCW|tBCY4!1WCK#Tb+*S+U zm=4RBIF62fEbpAz*D?XR$SILl| z81)rQ7m42KFL9#Ta#?&ucGv@dH85UyBV))rsbY7PHYeq1uBt%Cr1{YgRO#MozPHY& zT!j~Wkgi#yS!eFwvf>bkn>v(gZ02;i#}-}m{p6~o<;fS%Q6>qBL914B9MQdjJ{W5$ z7_1EihUwq-3Jx^}1>VZ@!lwk@$LJAf^0CX+ZbFI@M6T_PySTpVgw-CLYO7F*n^Tdl z*dtwfVg_-!?C!{z=(R<#Kl{4Yc@~p0k#cwxGS0%aJ75=mxYrx?IP~GB!3#ub?R>YW zEbMT$d<=(NWmTFV&(k6crW+E8^)I-2#~f3?u59o;zFe7LXVx*+dYHe-UZxv*Wm zR7;iFi$@MhalJj&&HC==xN-hsTM28LW$CGMT~Kesj^4}#(-`Xm{q6`ZlN+?|->C5) zD3bU6U~%p_+1x~ZamrW-H{5Oov70Ev?Qs05)3*EJceJi@LQpB#0dHoPRJk^k*q-Ua?O8x8Kiyay0(WPySI^q#v8_~U>6Iw2o0vJD={ z|NH|1xTom?{LEUq4*bt0C@$a-=Uw~|VE_KbpNj;*PK!7^N&dA41OV#Yx$>MxgMs~b zDK9t_pQjQTz5jmWi$x5E{7eS+pQU|#vLN0!Z9a|lpZy4Id;oMoz5&4gvo!HW(fh9d g|1JH0pG&Xs??Ya!NE#B^1*In394N>T~?(FfcHvU}q{{`=(Z*()U~_qS7<%YE z5C#B?1p^1YgN6RWgj&JC|GtNTp@v>zVBjDjFo@7A7W7Xk3-CX!faNT>|K8{PA{12> z1B0PgRTC#OGdpKXdlxoRG*oDC1+COHT{Pw8_)P3=nT<^Cjm?-nY#n}eff4ZFgWlSj zxfqdp*xJ}R^LYr8|EA!B-v4^cLQeXd#Kl^WTvJ|&RLtJVjFg+1m6?@X=s77VseqHI zIiHHSu(0v+^0Kh9v#_%>K`EG=J?&hKJecg9 zDgI*eKYYZ^oK2jp99*pI?MQ#|H8Qq$brB>d|0U?ZKY!_I=3(`>Bs=H-m<2VEmTu+d@0K2>l(F|8)Lm)C(k1z3JL_#Y1cE1JKbLc=NaT!7`jLqq6!!s{>$7#I;4u(+t22kgENqCb_y z_sirocXJCjix&h&?qFH$04m_KK%lYsd9sh>h=q7&^9tSO@k#GwI3cgtt#kKWh#z&%ZS}(;TKRsQz%T|52~vr>A!@QKVY%9Kge70mKD__>&p_ z&xOz*IPZNn=rqVRwH;^h?%?~2K#`T;b>rke$$otzH-w+35B$69Zwl4`ATDgCNEs7@ zSPKR#JPPc8FTmP~zd!udWC*}y1qcNg>b&`<3cvVZI(&iq*RvLQs6cN(YxUQEF#XMk zV)+f?zn%pkiMIf+;R9{8@cu9l7a-a44ej5yy!hacd5i=Ig`vD7{B?-_Fam?e z5R;a1UN$s{^b-c*%nXO^?Kx4-okP>k@8rm0V?{=-os`V>mK zx@pV?ii(Q=9~um*Wgj1(7b{3}N5 z6o8G5jU8iGclUzf?z0ZywMVwK6~Q0=Smy$~di84O^m|Rs|Bpr)$wOAQKRABk{~^cB z%xqQqI+00-I`@NQ3k|$^f5#ueCndq8Wny{-aj19Qo0ONvjPzFo4kU*C=@KDwk<)|o z7L9M(Tx1fQ03k9~oqxIZ=7R_r9Cj=PjgoDP2SWisd!5a=@Dk~K1t4hPxI^BhWW z0fY`Qbe{dm>LxPG@Li-R+yYy85lP zrDaSIx)6^oKgX6h+Oog{%+F}$sA$ zmO%i3+O*N1g!rlWyh&=Q=KP~Z1G&d(FO75mG7P%wlFyZ=qtD&kl5Y$7*`a&$TzFV% zNo2=ST;}~cZ1{mC@%-+to}$3kV8qeaf8r&n8N-7m0a>EY2bcah z;y0}>ts9mF!AGllCMh_P!izzKV*` zU0i?+*hnn-%podJ504VjobqEu-BrVu;OWDWpD;xa!P2$l&1q6|+KN3fbis&;fI(ts zYEU+ocb)xiJ=NFt9yyLe{7?yaHr4#7PVBR;JkwaceO#fq^b%EgfXXP|XxMays~MxF#wHQ3@b>w6i1^m^sx_L1MyU4+HMj-nquB?IMtT`e>T6h*y6 zT|VoM-5xJez39fIwVhO`S`!IWQPQA}`e@{<+~0v7&h29L?r#OQ%&~ z8U_*EjKtUag@5NXm^eO+G10vVC|G3R4%xa8#l&q z8JeoEa-E+~eIP;`w)rk+1ILX#Zcw=!a)`EIMW*nI!3e zj42`^(S4bcC?b4k1$@nT4x^?gNL+^p;?5NXDiJ)q)JauM~9$cSH1MPh(iw8 zR4~+6$6>dqz&xJoBkJ(YqR?6LF#NV*MgG zKJN@NM(&1o1P#k0CpETx5Hlb4ErLpY4y<9-5CGCL0GCIxL{wzAz^R5!t}s`OU^0(Z zWPJ?7KIfh)To?|OouhUiL{c?;oDP0}JFhPHh$zY|rnfjkUIKexE1mk10qJbnm|mLxXFALEeIk$%t^*(9*&aK?lKj z4$+l*kfO8&K3zvY0X3)MGC~iQ_VlmnmruG$qNT3%qbJdLuJZiNa$(-oS5yz~>(u&n zD*1$$#@IaF5SPa$T-A}yBl(`jW#;BoFopRp34|h)pZiF=aU61>jB^1tx#HVNNm~Zq zNaF9CNS%6%ZGdL){Gdr?jJwt69j^!vj>s`?ONR{~;W#>+4K=oiG^gn$p-~FIKPD~% zKmj=8d52%)-mpd#28@dlyJ8WK1g@HaK2?S2;7!H*A0a)#8ic&{L9#f5U} zg&8pKNVH?9POX>^!hpbqyrdtxhTVqv9NSSrC<*&zhhkpopPrBnuG?U^TSF5T9RQUl zp}F*IPdwNxemDJa1LCHGfdsN0Yq7^MC?czIj>GRaTB610^?p#87dnwwbo6$H5f2Mq z&5Ldjp%&F$&RWAw23X$C4Dt4CfRe%qqTORP%0>Qa$jiTVhwSFXsA z#w~a0-xGe)>#LJvWVr(uzV^ucxdkRuSIH#w^Ob&k;aS)F=_o@svyEi`Di$-uarzC~ z25m%m!SJ3g;+8rbB24 ztQVmyU}$LI%2Cv|Bb+&ZA+R$X=iS=%SlC@TTi5tVR#kVPe`Rb=2UUha$rL5KW-VKx z?jd=Y|JK-z_*r+A70kuUB%7{SqG1UfJU3w8?yC$B*)kEHP_OwpU1jl?~uQc>tNo;=MQ@0=+vfkUqu zp_Gb7a!<71{Te2cyXKo}39&(14s^|>W3*3|f-KEsg|EyS5O1Z3%CGW*2CuVQ8@9GG z{hB~_%noIw73{86^2`sW}h05d|2cgZWavn6cs9AM#%|aqq7}hGNP$q z3n<&Kj4I%+BA=HVMS~hxA}NIx7$g@gk$C!(ZEyLC2pyj&!8Ta1(cQxve9VFot0D;* zx??@qA;qjhS9QZaL#fcc_WfxxI~UGAemx zrg$4kSU8Xf5I-t|uvFT2Zaik&8$*09E!oDl)^3=`k|SDsH8C89yijp!JUoVIRT4V3 zAV8kxWDhdG)BJg&B)X!F`vto2J`|(o4aQw~@Iei=JEQ`WI~;O*19nHoB~y zO+z0W@!`X}rZxDq^+gRKnXbz3?$ZdG>WZt5biw@=-EPC+x*|vqcX$wKjo!oE;#HI3 zMqdJj7d*u>MWF3d7-u59K#}MwB2E&e()%_VEmNt%D41{u0OKd5!X&RLJ$Hohx=6p; zhOXBTQ5Lv-Om&Myuga$LqNm<>hf+Il##=WFKqSS94jn&?JD)$m&^1~V$Tz+*sDtE> zhmbE*mVy>s;)FmKT7J-i-+ZE7ZtTI#b8W+Tt#8ZhR!zNS72@w@TQ-q_ndpd@F^uOf zq>jKC|9rZ&i-^jYQv0OhI0eFzc{dvEyN)`HhrX*=DY{+{Dgrzq0(;XFtlJ#5}glP~$c6S-CUT zrxY5!8$GkxLvMl>x-?xm+BhfKlqr1EHZS64X*ZKNc?j(E@>1lvSI{!I&N81GHoGsx z?-z512?Q;CBGL7Uwrdu>tc?YlK-H-CcGqii>rkTmj^hCig_dN+vHDD#0~qy=?BV4h zqukb4g7(q6Kh|=W*I}P7?Vs|e(q;&H>#47@BwL8E5Vsu}k$8HG?h04CuYs}RGX*At z62wB?MLx{y;RUi*7LYUW0@)r{XyUF;UZ`(T)!;V-AZ-o zQzQtd^=4ej(2UIAHO|EYCpvee1|}Q}M-#snIP5XQ7i4+k#JiPUmHS?X%E1_0VrHzk#IL z$8@x&+heXffKe|vd^SGAkI;l|)CcBXdCb|?pn3(L_~Hby6Kr%^B6(mTt-(N4=4x{s z*2PF}V{qP0x-QLruz^pz2!NBxT=Sow+B|N&dzRwe4OVe2&K1XbsK1|XYQFAdxfDY0 zt+Nlfa@eC+$MbegcrYBRbS~${VY*!{_w6E@`;a>$b}4wDzZS(2OPSOpu)RgGOxSSU z){US&+Q8g!+!nzDJ`my57fMr&S*Kh)?6GG~#kXYM;09zM%iKABDyLl5EnB{$-I3aR z<|yTgj-_d%S9+W0=nC<;riAp+*<}(S!CLUyQ(-z_ znL~ytrNWd1(A^Jo(g$4%Yx>bXB5zASO0UPwyEotI+22<6VYyPuGcRq&=FZYExjWJ& z1SB)Dgh4l`GG3@Zarv3tWK1oR6s&v_sLp*KDS3{J0H<4@sdotw?f3;N`JggJE?Hk?7!M) z7KWr)60XYbP*9Z)$#?v4@Qmzquj7VKNz%;e}VX``dbNp!A{IT0PA;P zZIFs7v>TgS(i8Az-qA?6it#Y*Vp2*(&l`;%f-UI_EuP$3$GTU>)qO$W$*RK1m_f(Q zEV@4vR zB1QI0(?!-W|EK1stEUjgH)HX+iqAPsjbmZr{T=W}xF$y_5TzWq2pm|^3X17GH(ZGJ z5=zOU3SKoUK+NxQ^G0i%TD2hWQ!N`}(^L^5Z~3caFgod{oUw!qEP9IM4DizudN(2F zM&9AdalqQjUhQbmfuwcx`s7Qa=xt)wl_k3M8HQj`}yA{PPR85_+*z;yy zyaCyuiQ<9)6}DixJ(OZXIK-)=SL!Qmc~q*A>@JD)`C9nS+D%2&-7_6Ej*t8$dMASD z2=rogl&{2}xR%&GfPXkxJ3A4>m1jQ2T?x1$ey znw4YMB+ldIoc(am$A=FrHq1ef5gnJYi0{>+{Lkj3SBcu`7PsBHV6rz+U_)W@y%(&y zFtI97nr(imxV;sWS1Sov?zUOG#VqK!Xih)!4KFY1rS{B z$YY1dyij3iecc;j*koZry3J$}9O+difO{|Acz!=BMKX9Sd+OgAng%Ke5UO@prLq&p zbQcn~Q)=~M(@qM;S(4!Ht_J4sz~AnNrKVBl31S|R>&$eo>5t_rc@K;60!iR^-SApP z@Lk)JA_$1lffDclGdUWvB{H_<^z8+BMOyo25PieR>h9CCUSHkm)@szZ3SQAhw4^7o z1v1zJtxKjY*E-`MJ%LlVs*MpL9&RGv1&XR!*Lz) zw%CYhE=mhOg~mIbr3%qK5%~{&g@}6P)7D@jF9HrgjvLMqn3du&Is94%4j!_DP2LF%whx z9O{lmg{~w{mX+iPFH(@buMaUS<|9@sz0uEx7YI{&-K}>`P(m86O?(7>#o++* zScn+%>t|);^j<$hCH$~Kt~HoP2=+j|((?AC`ZciIG%`+3O$b)*HnZMj&sl6iSS(%{?z2^-lW4H73y?h0vVO3F&0MZ>q(UNJI4o|Rp>Vyxk*DzznsuT|5RP(G zFb@uHil&g#k=X+k16++HRn6>y)GA6j2DDNq*)p5BqS%I$*u=ADe>jV(d zk5#ppgG2M0FI$T3RXk>^d8{{3sbbf;o6tEz;})h-_}8v4eB!GLEMCnPjBN#E?8|G2 zVsup_NNTzFR3u4Ds&*#ZL^)EVMq$Eab;Y}bi<#gGNX@v#G}7 zCZ!H`QK7*J%P&Yv2}M9^-Q+pZ@scOZw)>jt$1{BRMQpU9b6Bg!TZIeDfe+%i9o(H` zRggx>HV705mQuSQ#g88=8m)&8SV^7Bm5wbrcW1ECxyjS@4N6s=X1GbJ=@ije0J$4tuEDJk&@r7Vq1%;+G~ z%v}}97N7lAKE$(BzAvvu$=wLBzcOKctsB|uYSU}X>qpvg@P>EPezn5>)s#hmRF@R6n*z25qcz z@cJlC&tp%k5ycNW^o!GtD|y9~v2)O(yRY=Elz>GvC71j^L{jymeL;8X!m|K={SMq0 zL?!?ihj-{zZ!P~I3(J2Q(h<>9H}V8JneyoN~g?FRN#YtTnkMRi6=#_fOD)@ z-F2DevVt>Tg}xF5wI3#^P1UJq$E`T#Cijw6gNOFslDTXr4#bBo<@pf0R>!i`>CnfE zZKPGR${|NNoR%(^;u^l0%1OErp~M`8)ul_QF7M4e`$*+Oxnu+>r2GEC*o!ixH-&66>@Aj%LniwLnR6mw(yYwO4(pGL-1d zAHvpXz2AUdh#HZ;58OW^1Q)zDFRA1`9F6ejr`+eEhnLPRG}w2xqo?*W>1p8K5jdp?B+&Lok~aLc;J-#xsa0kasMZRASE)>BNH6?+yV5jt{!#JjN zN)0w@_+T?dyFw%*b3n1|?XH|aF(kvDUfc1>7Bx(TJ0Orj18ZjR!c&)T)C3Dwv5x^i zX{*jSO}wS@dpmQf?t2>M27V_t^E7`|Qdj5;zz(^`lJ@7`Y~RUkr(;`+_%2k5lz`8#=ARlqnqY` zm;b0m{Hg5}VxPbB?%D0uDWTe^LzD{O6V^+c@htI_bOxODnEh+rd6m%8IFmI=t9fz$ zVzQfEjEf+;mEH-e#+q9CkfWL8ZfX85-TrQeI*d3EFG`CO`&1*I)Kvo3(dV6(6GYi& z(&Woc)l~TCmwgEN6E4e$XW_TtCQH-ENXZv2qOF%DFK0O6WfJ`;i+)4c0=;v+U! zrAHu)x^>r-#X_*+YPW-BUk!x9r74xC!$zr+Jl5S0tdX^*yy-Q<3qdUG3om*cmkXC?D(Dya0uJk;3Vab%*?}{JKn!lOUkfV=d4Y`Y-X{w`kOF8{=Hm?kf`S+Te~gr}DZj90-_mX$j!H5=C#9}nU$w|T=*_9BUS_pCEo z2Y4cgSkP70p372ytE_$Ce6mGamu)32V@5}o?*+``2TvM(f-caiz5Xl@wHcO>4B_f; zR}vb+Zp4(8@wsvLKgCJEHC^-VcZdssXv^g*R$IKidAj3_(%ucY)JR8)E`ONnTkOgEiyOzRT@L#mzEHi- zKFVXhYDeM7^d51=?;Hi+4sqX|uGl!M@OM;|3H$pZ3XqhT@G>`DuSQrB4qQKbqYL7~ zBgDDq3(#x$=C(moO}hwatSE>#JJZKV4+cJT+d@V^d-gxu5tH;-J?uM- zBBh6Qr@!E}6FzL~jGpIW-8I~3C%A-uL+8h-*~QzF1R@4ie&DmS^M58t!MKO_jDIw1 z=PWnf$t=0OxZ^=J+kO^!S@<|H^_wMJQOEjuVHp8-Ph?*71D2stIS6fz*dT3XQvGYP zv>XW_#3S}RB~?eXXC+dq!5ht5tzjjM0HoLEPe&AaAf+<5@1QXIfhHZOP&=A|oO#gm zh*L>DTIlH+)tXVgH183bdT1zm7qijPBE0O5yYMyiM;atkr1K|1X13F>*n;5&=A`2L z1?3RT7o7NYasXlGw~-Ee%ei{^dc5Ab2}p*Hxi!kmc9E2GHZ9U0(u1nT2r?pXpj-a3 zIC9bVg{-?5Ar^I{w!|+N^-L+2nTf|eT|zm~T$-RG9#LuUcoQxGmxWHg4=$V;t}whd zoA&dXvl&I$h>v3E7FeaAy+X`SsHN?P=nC5!hDhKGLvHdlyyYY*4xDh^5OAEGIzz(I^jB$;bH6wxSUb#g5WGGv0Nq`gD5(a6IT zVgAF8G4baDtaG>yQX&w2H(*QzqNdW`AmZ&~)4FR522&_M2_38jHEZt%Q;=+Gx9(5H zVgpJRq0r${V3hSCP zWi8kic(x+Q*fhn>V3pP8aF~P)Yr#8xSjPjFlHT``7~&Vkau_(IOQwG6nK0T^bPwDd z%8B|QCMC4YobzdCv;By4z9+=`5x3lL?_xG9lTJXQA(-2`#J{bF8|C zXL_%7$1ay56afJB_x{ruTOKJV)`gs=rmfc}4u>gY)(~Yhr>v$}X%F0t^oZf|oJ(-4 z9&txvdf|^tqM6v}grH)uztzImo14Cv^=@|&^7M5`PrNKT@-RS?3CxIo67W0(bW@(_ z&+$SAkr&rxBb;P!Z?ADFu++buic2o&f;MBzp=9Qnp>lpH5B+Fg;-ux`dr&*37xe_5 zjF_`4*vj|@TM@>$t4e8N&F_Q> zrB0*XC({!;Q3~u>!h7@d9EKWor}#SuEB3Z2MUDB}P?Fz{);n+Cco6_)s=d*|MH*K0 zybf{Ijp`2T_nbbit)6~TV>Y1Jp;)G@8Yv=v(=-B>-QS+RF%;b^8>0Z9f)Rbh6Di!W z_GzEB3vQj6JF8_Lg9n{^5v`G)fp04};`Rjh4-?!1ckH-2b(_`?UCQC6Q)O1n%*a7nTmfmo=cM$r$`=KcTo7F(9z^|_XRGVa;_nrn zb!%Bd%!{{_1g$8eI1!P#LU zU~r?{CoP=Av;QRR?-P}7F;*y)@=WT7a#Ah;GqT-TroUVX?D}PfuHy*m1?XOtYLQx} zhiagZwSeBPk!m+jYLM+ASzZUTUSgPv+-^O;u=~2Rasm6{hl4K$54#;ZMmWO0>vx+~ zPRyum;9A9K;SBS0d}KC`al=(Jk{vEKB2>}Vd{x-y8eKw01gjh(pX$#;VN8q~rt-cG zKpwY;fkOV0hMC-lleVb%aaN*FO9OnJM}2EiFJZh-!VMyD$kx0X79}JlbB=M50f!X@ z#k~&iDJ;@m-(-ulfOrm3od`>)SIW^ur57xT4vJFw;wy z^I>#+g8#6VB5PJv;K*i5)AC@VJ35ihO93E#QD&3-=?2rUn7ZQVazl}5TUW38GWM-p zT&pt6Vo{9uCPyYX4Hj>xvL*ZD&~#b)IwSDw82q$zhni9d-0CB)TP=Lr&+j(ybO*~X zNx&e2{n#k1awDl0E1{)OMI>6+37+&d!Z$_l+MUi1oxfg#kr9(=F249U-h6K93k0z! z@H?NFs5wO~P;#_EvYbaST*2`3??3C?Lx$K03^f}ZL_-BsruHlPnUSxDRR$JcZ zcJl6@f5MD9F+7_^=;d+3|5WcZ9u&2O+34n#Yd!h3SDGRfkw9#B7?mqVyUE+LrBgfp zKBC6NC)b71%1HCPt|)V$zAZT+Ha@bFR+lhQo+@;S0Gvr&&<)xMDKm$qW^75yQ9eib4c8kl2JFrpM#~^HlE!gjx=uCkqK@< z2u)p}F*DMi1Lpm6n^Nz$q23m0t^`s3G}^S92+s{8>8u;Gf)iENbP*$RzJ2l5uWn8= z8{igMMrlpZ(C1zEC%tVLbsmCOqIypsueLyuXmeMNsSUHIvy(*#UuRG0C8y#lCl!%a z_VlqUGNT)ZS1NmBf{r&fPOUw6xi;~ee-Rv|b4ZJjg#V>!-8*7G%ZTv~qJu!G8a!fo zSS$LN{IptY3Sd#ndQK7BETz0t4C%J{%c{Ep5_h*8Po|Wnz<5f!*-zKPCmkT zLfm7mH!&;W@9#tw;h*J9lD4rLimDx}${u#J7O*Rsiur&HS`qF3CY zIf)^E=T<pvXlM$Fo%6-?*}sMqT=K> z-9C}HA|xO*#KPS3jmi8)2l%hr-Ho`Cn)p}Sxa{6Z&d2b0O)!cWU~$& z$>&2%KJ0G9b;4^f43Wx`iH~IiZ3j$zLh;K)MBtAhwV1!1?iR}Dr?Qyu5ko)Z6=h`I zqn%gx5?I(4S6S0ym!12YD-<+MY5{NfO4+TZUOVV=C-!NOwcq&@NlXh+n+fA9R#EwmkQmj#-=UI#7G8t! zPDngwhgV(1nQVFfA(r3xj!-?JOEt1RX-oQ{FkAdPC13H^$Qll$YYz&WKNzyY0$XMw zEg$?rG`Ip7jOMQx-%fp3(p3adz!zs8gu@`y0&?Jj>|#e$vxm5ZbftnjQ;TbaXqDCh z{N_?6r+4wz;n&_SFFwDshZktx3rZOJht&aT7%gsR09E&TLqXzzb*#yf)x||?X889A z#S!n+tUj9(LcgOw>=ZO4k~2)8Jxs5eQg{dsA+4SEXh<*0iU74GHvJR1{K6kbF0)u2 z4{JM?PusCAL^$j%Zk=X&wizB8ytFw@P!9c&7`k9QGd*{{Y-aRS80PWG>&Y>$e*lsV z12MCT<`IcKM9M;2%7Cm?b0jxCQo-h=!@OH*9V+M&Ep#{e`qxS;SRB)cpt2-U?Wfe4 zZgu;v9O?W(cH%`5)|M z4j331x*sqH(5`F6Z1Da>(62K9=H(8dSlV0%xYFBOuhx>EYx-)cqIm8fnBe|IXM0Oz zefc6Q-L_NM#(_BmycW;0@%$GvD!{_R!fPxAkaGQ=WXo9vz2@gX(f!l_Si6=r?v><1 zS0pIfe05O!7e?|Q=xfg~5h*FDdR-{kdFSjz015%m*w&u?7fmFLx>6bk;4r2v=bwF{ zRICa>o(FqsVN7aZax}OlERgq48{n{DXSv)@)Jc^ z@ipI>S`he*^B?2*7jFHM1m3Rc#=LGb#riy;r15f=?OoZ>kOJ@H^;+JyZ}QUBo{S6( zaR|uRpWwX*l&XXO3@u8)%U=f)UVotvCGo8lc$_vlUHej+cs3-c@uo!A3V#0W|MG=C zP-!L?iulJTAlP_BB*xhNfD^4@o>=m)pgLqxl&$U*Yy?A*tZw_$pOqL5n&v5&5xw%t zwm^T_fXfeGTe3lmF6QLS4}^rYDg4{oQL>f=&Zn~%nK2y00;*x4 zi{`ZY)JuhDIYP{H)VLpr zJb+^KjBnHEUos*vU=Lkx_ML~#I;d|BgCdVB6rgc-=(~yzK^dM z*1HJoSOFOUQU6*AZ~+wJS!HEP(8zKaH*YN*B_7Px^r!JUNBCQ0nfzvq|9;x? zaAgr`1TVlK;P&%h6g@-%+1cb;4Ibs^B3O@i!uO)Y-p8W6PkAf}KNW=q(?4JybG~%@ z^Q?{`Faj-r6bQA{KK{yY8Lq;5idlZY_)7^SnZu&b-tUZncl85Bhg%yVQ5W`qGBjzF z2u$=gi?8qwAAYxc#>?UR-zfeb3V3a8osCF)uekWc#6GhmJ!JuBX<}d? z;AU2%nf~cA1SIX~SnjP>@e$S{A#|%GA#66t;sThE~gVZ@TSRA0?dLB(OIZOXsKYiE*fb>&?IRPLuo|Os7}@O-({Ohlhzr zzFYkCy0vFf{8ec-cb2c-)6>4|#PhrXzM=gqn+Bl3_qf~`L}yhPR=`fvMXMbxEB1mO za6_UXRx*`$pHqo$E((V4#C&W+%m}Y*7JsO6Kb-T<_+Cj`)!SB{+Z~AW4OkyVL#!vR zLj3$>qrX1wn^as{D9X;&W8-b_CRNls(S2n$cl;S8!N|xM)0X`@cYNqGe zF}-2)d+CI6D!hQfQbt1m@1=nRK-q0t7EfA0*+&niMxyE6B$?Nkz(`;Zy`7LNhRFp* zwBKcP{L~eMnK^`PHrvGy*%HN!zmy%WR@kxMi&KfL1vNn&&5;022Ps>od*29hb3dzX z>Q{&tCr-#-C|@WUz`}O>Yz{l2?;j>?BG&Xp8Q8`=a_NnYj*-DB=@?x$YL=k zO*S*^OnI2c0-joLP<}@zMBE=G0sa$~<(M+NJ=H=3np!qWT?MrmT}=r2&YM+a`{H+` zhawiGF1D6&QR+lv$bk#U3DFe@6DoeK0J>t)8W^zW-A8k)2a7nh;f%njT&desug{RN zZa|C}L+e&~v%Sd2EP|$oBx^NoeI+{fqwk^8YdVS2u;*RhO-Yv`CgC0q(>1;&Cir>C z5YOJ0z047i(jaCsL!hEBXZgk$mqM{;cH7JZbZzc0|M)_ACoQU0pMuHx_u>SN92XZ? z#cAEvx@+mX^m~BH(VpK0!}k#OZ=XdU&%1@+mP4n}-dv^A?`eb~;V&LPpM>R*QQV

TgzvUgZ#^_sfpYasGWS%jT>qw8*4{cA>n#(^qf)V+nl|l$ zB3J({NomYspnmdI1r_N?xNGYsOzsSyzDO%Li$8OkD)msG!n(RNp*n**S$Pr&VOHTP zeM67_4N}4Tiv0o7sUpy*OLYVL3{$27GijJ8os>i}C@`KwzQ<5DQ?A1a2Dk_;Qp$sD;}iK)a@{p*;wKaPs-_0xKIU;% z+zspG-bC3}xOL-u)}Lt=(HF;>siG(C7ICSlgCyv;#--_U7hPG>Da=AA0y?gG5BY0k z?sl=QJWgyd*7)dZoh#49GJn*3*8oU2ARCT~Pv|LOg)E&eSd=?d$iKyrOGqx&p?v@9 zeX&JGVnbyI?P0u0LI3_4cF*phwdrIDp40kCch8DFuYwF3a}w&3E}6WaCc^~Ha7>Do zS&ERnD{oSwKR-G@{;wmgF$KV?nwtKif^_O%N5tMEV{cOnEIqZNug_`d)BOTf&pWiE znuo9|E-m;fGd|x{sodDva-0%Ss5(oqP{LE99@;i}lh)9cMi%1^R7w|1muW?Ye)XV{ z@>mV#_FV&8*)Iekgj1<-f0uKsIZSjl&|y&Rdz)9-Eb@^-Cb!=0BWIsn5#6`=AZAU< zNR|}zKp_d4e2r~mseHNbudsHjLsN6h@|E7Ar&3F}=8dd$8G~&ien!8K9fM0xdR-W@ zOhMNX+OVd$S1#@R;*>Tqxl|u_*G3FqkqcXXKdEpqR)Tl6{f5h@8*p``@~-u`L}_` z`<26q_C9+$PB}$c>r2RUOENEMW)5eGx*t48jTn+QF_YO0q}sUbz1s57jFzHUC%Z#y zrn9uQq(i-Q z_K^UDgpoDoOdW~|HTxp^#C$)S*aMv!FbU=E`ziADbhxovv#M<9%xO^q#wR89CpBG5c>X&6R6IoX+Sc})_pWLt^L9xX%DT z2C;wHqV;0O`)!TQ%>=$)FgToyW6`Nhjj<~PRJc^R^+tBZjWH@sn;aEqXaXTo@5|hD zDSYMs{4ap4FO9O=-23rETiT>M8DR)XRG-aWGLM@Cq5{Y9Z#)?H2oyXS=~nM@N^?rNJCu^8KCZ#1?U){wK`!XZ-UE zkfnE4V#$oP=h8(wLaNnY=SdY0waK#9*@c(&vdCRZSn?Jtn^%j``01ZhVn5e(vrz-weWzTlb{k2p=5;Qh`NR z613j%{5{ip3(hVRQRCR$B-4gy(LkD@s(EIu$b{irg|{&o2h*@;VGGh0^s`~(B0fhN z87%fKeZSwIT4I0>mFan*#Ji|94lA!0vbuq_IK*cINC7WB6^POJ0mCUZ6Egj=Za z6w4&^QK=h9iKa)CB=TQ}Y}VJ38KIl-L(&{w!q6REPCnhjoEw1KxL8alXVAjY)300E z;iG@Jo%csEJ*X^c(#l3NC49t}3v+fPpbU(EHSBkIugML=o0Ls&xx%42s7|LRx*s}znt1y;`QM#{L(L<+!p?C`X(^n; z$5$g9{O?@DzLX@*b&-y;)y(vTQ}CRf!Q>T;={I!H#r1qv>#5iLiSd}B%!UK6-d;Lp zC8ID=!cI7>Et%(q4QbM~0-xmaN+o2K?~mIyNR`*@R(<&<>Te%-wz5$2-Fx>$e;Fxz zj=w6fu?czPg6gavCVPooxciWvHa>zjB_m`Vwsl}G#xvkcMeD-53#jcSKQY}{MM*u- z;gmXUkgMN4Kmz(qs~@>f#|_;cfRY3)M&YZzCVYT0x?p0Lmdp;G0=({Y|9#ANgLx`kR)(H#$x?~^t{IcWYU923U_H3bSU+FsX1ij^^+{JzRy z3F{uCWLDnhsdrGmOmvzwt|ilkJ3Kf`&Y$vV^f#4sHObI%=VbIa>u&sK;h%e2#}`M4 z2m`UY2+1(5I%xmv#BjjC8zDZ%4L(?GWXZR*w(BwlAd|9+y+;~<$Ok*|{@_V4oU*&r z+@wBL?_*@v<#cP8>0rWP)bkmFd(}&DaJ|YygE|u@L^poACE`Rk`kc6GMdO~;e4*~d z9*L2IsK1HV*LsF5yyCLoKvuD4S{-tU?86_cfH&()KlS43aH66ocHKeM=a_e55cN(n@Z;_ch&n&{QCPAo3k-ER0Q`GlkdqQc}6R44n zu#T)NFawORx(pm$5gs4#KUqp^BbX^YgQxoe3A9{}XO(4jc9!6~@go5p8g7!O5OVd0 z@#bZoA1*Y1g2vEG)0k{mknCTc@yc|y>9Q;86fuh7i_pvZJ4t)Q7+A!C{Hq;ma$%-D zJtYPiADzM+dbgE#=f}w>?RZvPk0dlX=;afIuDwkG86ukcuQvaCu;q}?%+8V;^pZ{d z07*X_E@vAgkd5SGf;d0}$HS<65QzAT5SSq{XYCrw;%pZ`5ysZtT_a|6HijDcTT<@yiiV_GjPuD~qQxm$0IJX_NUv06Pr=m|a7ZX}O+KIO@f0+k~zo$xH z8Lx+8msnjzi0*dnlySbLajr$`<#Ov5PO_Yqy7L zxV74XP}qat)ei9=dS5a(O!d>}B)qP7-);vOt$I`(Hzp2mzKs z+WB%)5&AvL7MFATIJ}9rd62Ip7wu~Fn(@(-CV9zPViGx%8mbeQ%6Iz9{N~^92US3_d|c3y5+4~ltYxqZ$>z7sIR7hJnlK}d#;Ci zn{4Lf7;bss+i^RV=Up3e*$E(-Nb-~lDEnE>AOX9zzVoN{&#w!Cm-nhn2gZ@J%XO}^)Dhd}YeLMkQ zmZ1)1LhYO1WvlK`?J4vH{YL&Q4L4~QUkBQTw(QM=KCCIp>ToKaYywA(P#1=VM*t7? zh3CiL22@s^4x$xC!rA~4{Ov(aL>{wedMlrMGMZ2t(IE?EMG6U@q);ya$@cklLSPl4_WKb4NhP^{+8h4MW+Gje44Itc z-kXmCo}kPt4Jb7pgLWnLr2i-t|A?-1Effy z=5|DOniFb?ziw}<2Ua*y3x){v8@J~%n`iYU8HBpIOI$+6k0eb$v3^qPHSWK;rB%gy zAJNmhX{u-$zu;d7Drzwt(r1#XlwgWv@RK}#6v()?Py5Lw6CfJvX<|MsP&^H4UfsW* z>g*MVyN?(}v)>$Wa!3xGiFjUY6CFcJa6SB0P7@G?iIsKj75aW(r-Z7(1jbOCl` zVIoXL$4#AIYaYBQGH=wR>?Ikh%2=)P8Mk|@5BB31r{sSB%1k}{5AV5Twxsd9Mf#+6 z?8%f1x%0F!5jNBhWqMSGY%QYV$$)RU)>>AqPoOvsNkfCo=q2I<+a&)w3v(F{& z@^ejQCO8kIG!BBFmd7shvolPBZ&7q`l!xwlCXL$&w=MDTo=m!6@%2Xn?a-)l9 zo7BI74#d93>E*!_S|OpjwPROHf*q<6>vb?*zvF&0_n0U!rViB9B#YBS>de5>Q6fmy zBbdDqtU(5D`~>T5P%c93D_i-YB;eksD{q&sy|I(P!3?&;58EU z-nCaRix^&8WE_%D)eIP|jY{-__p2z2ATAEBNquY;2zoIpnz=_l=6k!_(6?%=u310{ zdE7!38)d;zMC-iWmE(FTxmk49g?vwlG>%t3eN9}WnBv+14)j~1d8q_X(m z;5TqIs5ieBOaLSR3HT3wbsA@A8JW;(Fr6<~yLsA;9jC1ECRDW=2VtpFQriII$1g%^ zi$Z{jsg@3BA<2}stAvoYA~-4-;V~;;)YG!Y9=%sKHaVKF0E~2)?DHl?iRId6g(;pI zT`WQ^#MR!TDB-Z>I_CnS6MI&EZEDidG?pUCl*#fWx1(_EQ*f#B7RyBdOrPl%Gj;MP zq5oMD65XpQ9IW3UX1u!OaIR0K4$;~QEsSf*NUom}iJWEtx4V8_=MJHo6U9$|QsAO# z98A6Z#OIK|(X%!mH!hp=3Jx^o-tLXSLEuUBu=Z93%Sp-5~gVQv8` zNL88^s)&~<_-SIo7^t_LvWIQ-J(`{?)wC{C9DPQ8oJ_C*Hk>pQllCNom#oFsOW+@(%h0%12Y|7~>T^fZdP12&GqI!^+ z_zzuEsSu2{0uoaQ!5%_g*qBG441AZ?%uk2>mfNc}#`2wj2-&Atn%}He`AJw0gfl`v z5)tS!^DpI$yY`q8c11k59vIW!>Ec4elS`2~24?P!RI>Pgt}dzsz{CH-0h;7?BlB!~ zD;EcraLk|xx@?@~jn{7d;P|ULCN!ZF&xswR-v4>g(=>kn$T1%CAfK+p@%2Ches3*R zV}#qjRNb!jrcOUSCNx*2TGiUEP%**=?F91LxF9O6fB%BQs<^QXEQLstL$)o2j>r}jMXNor8)aKP# zd{7J~RrmLq4>o5-7nRbDqD|*NBxw>bG}5@Lu&%khV4vxHzDlffdY&f39{m zC=4)9vy_${LGfC_PtO<)B*e;>xCi$>2LIsm(ujS#!tN-ta!*xFN*<%3Rv!zsPa{K! z`4%r{*V8-7c>e-*(QuG{7vL896Wwp$MUpOl@540^e}y%T@LHLZ#FHuRftE<5sI%addG zQ@Qt44#p&Ok@QB!(j-SS(Y2b3)ij=igN0MHLn@z5G6}X;v* zSpL<1wP_)xBob$Atv5iX3PW^FnOkmng1sLt9yFLPfp3sz*P?>7D9bEnnLK~Fe%sZy zM6r8TNok+C%UbqxbEFzJ)@3&m!& zxMd*}atF)DkoBxOT!aBWtXM|K0aw#$dQnI|@TygNA;EM-4mx#w#6bHD^o_=H4Zrtb zj^+9?4ZK>|+anAav+Zr8rjkKS9`a52;P>?Ju-~5d5BvNZUq=-n=DFoE9H;tDNp7;w zpOvqlyO_TgSA5TK^hl7_xh%)weM4q9zz&mWby1G`bB9A5vzJM%bHZ{b`!1L29ap3G zzezR|H(F_VdBUatrMDIW$O#0R=CaOP{3aGFi9#sCQ@r>z<`};$ zF{vlP1rib4m_=SPPK^>Wo*gbsjvKc8G0c^i<)Y3vZ`W5G_>GH&9SJ&DaWeF=|56(w zbe669zrm-Wzi`0IW~#2!ud|IVoo2grcyf$dmQb-Y1T1iWTSD+UquIFEe~B3w3oQNjfYwr{S@E3yl*IrVE$RsdRLE z>6*Id>`J{^@|J;!(S!SUr*9l{T5iVNSZZXfi@(Uhe|g|O+>$t!_*2u@@ZCzrzMVg2 za}md>=#LS^{6J?-@H+f` zI5iy;2Eu0s*po_q{VoOQ&TiCqy%T05Iu-1(AQ}jC1v4*93ORM3f^8k`iNB<{o9Wpm zMhX(tl|8S_D_rJOfn7x;Bw->n9rNaTdRimw%m%0XfNqN)Ki1%* zXdEovHwhw|MP27;5YcU50#$y6lUEpunj4AN`kbfk8ku7&P~O!sbmR;wpQdjmOhoI8 z)zessml^+=txAK*Vl`NLqrG zY$EBl79)r}sx^ylc15}LjzZO`_F5752}HOreFJhINGM)*{#00CC1iaj36Tj+g-kGo zf+tmD_N(0^!N5c(?`Q|qk=HRpbOQ`gxdmx(v>CCp&5+X9cQ=0-*KLW6Hm zesgJ^Y4BI5ajCLO?B?fJ4yihctdKVJEy7$KHL=j1;QE?m#3UCf{`0pdD=p6Z7Z?}< zivogbkdR&P`}Y4k35funtziQ1%D)Q>V`U{dwC-z;;UM>o?#K0G&$z$ug>$=Taao@1 zLkr*3J~iiW$gJ;azU1>FrO4f|f4uPSn#vNC;S&R#%VNOwE{88p=N%y8l5($tSk`e9 zL*AYNm2rftFfKMP>?9kqEwJdPV&N$xG=Q?W!P;WwD6zDOU|#{y<+;WGXqLzzQR}%a zW?8Ps;^@w=7h&-qYTI{0-(h!vnNTg!V=oet2aB%>8x@t_vDyv^4x!Zi#^Y_o>?suC zP(sFp?~D-+xCX-QO~NVFy%%}@%Cf`NLr=9A!Fbji>YCJrK?0g=(?}W zTp%r7oGdj;KVdl;G`83XPm`lf8#;evr~Dj0a%+PEPa|-M3vZZP)D8b?0+Npsi35|D zT*6ZntDoKu_tQ*bl)Se-U*~eirb*tr-)_eDPYqr(KtSHNOHbY0fW1ul4hNUGWK9oG zmY;xM?(2;+8L+RDZQ)5HN}5|9pSF7C`c7-3W=?lB`_e(n+r+=*oTY0?r%$N8Os8|0 zy26G;w7_@xVfI>+@Xu$qds)$|O%wo$Lptz#@fKctfF8SWwhm1!Mw)nxE&nA)Re#}a z9D%RNvlhk!gwp-1ZVkcVYDvenbTNbvs{L4S!?m*>ogADou(jZWS>*G4C*1wtm}Nuh z5TiAB`2%x`^iv`SNTvWOm3p+`q+4$?A62b4;Dwn$yFuhM%FSuCH14(~!=*9w;^ zYlymwr0!-S+@p{(CCQdo?-${zx>H+DST7U-AH*N?>ViM+PHp;fbHHV-X75`h5za!e zgx)J*@vV~onj9_<(Uxf{GN?(&Cf5fGO$krXFjM1N@b;r5f9LzlyQsRA{Gw^H`NL!J zdw zMH%XnYzw%cVhpYUMtP_gCnE`>b5SH|w#_#9htgLpwRRXh_mWRX8ndI2@ z3`(W}2{M4HRNfCIQud|#m$-p37mVDn^rI!=gU2BwYZua4MzO<9CWJB;?$$UTl#5Z5 z&F?rfA2A5vO29LgJYwvkD>LV);dV@Gk~3cgpB^9g_ocdD_pwF~3*4O=g&=dg74gV0 z8`YONB=e{B1dr22F#=NG3#icx?G}j@BD(~Bg^Lt0$#&ir*AVne#yDHEq5PzFR23;G zCMa!HEs8-!08PJ8D{6y2|7m++3kIwH1j&t^;RYu;YbON z(l}AS1C9QW3OmK6g-5i&xJ<$*?y@fV*{vnJ#=2a0g#20_&OoDFCQnq&c9*gl^s$c3 zd&cKUd;bb3!N<GO zMKgOPS-&VD3R z=`{D-`82q=xJjYuTdK)5{asW@K8}uNv00FwXU;L)PqDzg~QH@wJC-BS5mrIRAf$()@+XM zsv_yP^^wP7kdMhO@%(l5w*>x2PN{K_-N=U54H(2d)4jgLg|L@>Sr7{(_MIlVFkM>W zZ?SN0ajCgXTA+3tt?p=%8eF3-^Zr0A<92ONnyyju2o{)epU}L{Z6>7eaejE`p#V)P zalk!ezKlK+wrn!~gW#G`Vd(<-A3z?=a*Zx&f%`Ir<0h%zuVHU51B}#J95G3e*$U+V zr}wzij5T&q-X2L=%AAZGgi>4zC}b1yU>;(0y$pQv<(P*^uiPCO59RVIU+S9~c}(02 zg6IuNq1__*(YvPtggLk4eiaCoFNLRZd2PiR z8F*UtF24vs1;=TntYmz4n4n168U0%1;^8OuG|&FeCoTbLKk7;NYJdPOC`BmUB}#IA z;ZdmNw%#qLO}tK%eflUl1D$Pm~0*aN~nv#9xqLku0=DbNW9%Y zCK7iJo?tv7a{OC5>vpxA%Om1%w`I!R#aWQtESO}ccRnXFzhh~oi)hA1TRjwHkB(Qf zyOaSowncCh_U~~ptxNsL*Q8#-ZW&tpO$rr-EE*&LuVjRUP#4l=@@Av9wbZ-{f9v7I zWs6bK|Fj|@G_Jik(SP<_rQ2ZtwqvSv4qH4(=?37D&`6rF0g&_DR8Y9rRhJKFWs%KO zK8*7wRYW)`q9W&-M?OlLv>T{eNKtHRUWmh&fzJ*85~Iu%_T6I73hC<{J77DW8+)W& ztzsWU>8E5PiGM~x{)JB!I0p0!K{IQ0fb?Ry#x4}i<#Q?1yT-1HLxby6uRT4RO`}k@k zO&>6|b1=zt81SY28k)p1KpLr9_=42)nsphUO9ZCIfL)I^0BpeR_!!QBkWTI=NzZpe z#l8@DIh+&jpWWr{Ti)|xnTj^YwkPoy(cXpcqk6!bX+q#HmaFxzCQxp|QfQUsh9qAy z3&qql|M-6IV#{)m`)k03QdZLKEq%eR@N|Wvp8tN%zHG2 zOIpMX2STg!Maj*N+KdVCnIL}@4ygPcSE;Rsp^2Kidk&|}b z``A`F*<{1H7?sF5(HwNq9F*B(ayEg&;-{e>M@Ju#q`mknW=wKeg7!4r#>DwS>6njv zVTirq?kAn2tY7VOv7t#wcpXzLar}_ovPr4o_qyt|$nQQLA5Kha|2ko@Us{kd3i4(f zy48!Qk^B_12mBbbU#G%fw2uRsZbhnJ2LbyL06o&jjaPu@%!>7fC~c+NaSoFXeYo3K##IO%fE%Z`?E3&EcE^4~a@)ZQeE zF~s}?gAAS^nZ>UQ*j+oQnmVO-O6klOD*Qh^XTA;~8llnWCQV|Oi4(a3(kSN`&*G|^ zs(?*Jc&SIv8Hg_v<0SaSOp$tz~H6OHLuc#rdUA2R)BRd^Ie}IBH7b zwban5^{MIgj_BUre#yuvbuPWZ=K<$_yMZ16pVKy8!DnaJ>BMS0A5EC|)?2D{+?#EG z`n9#JT%>|D1`)+DpVokt;lo9=$z(T`I5qpxKDZsGLImt5Cr4mFdhhY!RH7C3WN>4L ze9=_?0nRUmO+nQzlKlm1XhMvf5?WM&H%Wte@Tr76-}Q-WpLXz4I$ezU4-6xg6>*mU^GmRBi% z7;Od0xyLfv|7V|K0wM6fCUtrnQ79d;%=Vr4$0RBk?`BLPr+xNk6n_Y4B9;B22NZkZ zhP^DTwL3=f&PFVmgT43T4LkK^c=0h1@+_4lMK zw5*k!33XNDpE<1zw6#4@>+hVocBCE$vbfwVsg`uoMirDVl&>P#P}idAYKC~j#tINW zhx?ktnD@Zc=1Y|JL?f!OuAQk`p=5PvK9EccNS=x9c&&gIHJrracFPo{;qs31+9;QG>c(c;l~z*Z7c8c;T)=t=QxY#;E8TDPJfhc=CyBBrY|@c_jKT-xqzI zj$auEjK>*n`cU4&JMT9)X`5V|wAI5RPh4&@rsAy_@ULt)=II5WXB9vN@x^{At0qyw z|76bTv)=JY16QBQ3;)}$nI~o&c-0!CKzARB>`))@wh^)TWr>+=Y zEYPE5u3Ilz9-CRrv+lzWMxTw!xiis6)|1^8vkCW(!*plHP!M_9J8FrDPkDxi?2p6W zSla*dZMqru68`YLiXZ6pDZl(uU}5&S%iENYn$p*gJ~kH#RUnoe%CalwY<8f7EwkJ= zk2jp$x83XWf6gIoNLeVeMp*fsr!&;$uDTvmg$6sZ+_xRxs(K@aJ8~L$YT^E#S_q&} zS;@X~2(%5dqL&Kn?B2keWz7pPb(`Z(nSs$iq)S zm>8PqtRvkG0Lw6d)2-T>I24tbEnw0#z)8Odtf-g|~2 zh20Pk$jItH;m4-~VL25~jzzSXxVZMdX;yHzh?q|z5nI!bayq)o{~{!EI?re#ncPg! z>&OHZ0w3Xxt09@B5bg>p#A%k0`)^T`t?x4^8(+R+mKV_LcWUC{;qCp_(5HA5dj)r5UIjybQ50)3PB^mbc;VK1TrVlC zX+eS5bV4NXE+RmnJ?!=^`)k0Y0WZUSW2|Pj4u7p4VO-7D~z949%`!Vk#bukc8t+UP=)l7#2yP)*dyjgm{{nItE@*S86tm_b!W;Uj$ zVTkwhVa@!k6<^V48hLvprcs;D4O3Mik5S#ofElZpj%Uoz{$By9y+q4nFGT^ZO=w4L zC@K;&?=4@X(|?wJjz#eiqNS^w@!iCpm1cqkb2C4x(bk40c}6abK-XuCAPX#fLXXKN z|FG2f!#I5(%(rNR6zm+*=YJPTlE(#1kzDf4Cx2*H{UP2@vuo+&lCknEu`{#TcBR{~ zKSSrmnoJ$isLioq%!oL7>oTJXJ+)a>ynP)$e1oZE_^F4iT(&Q>K41&~633(*PRwhH z?(`Op%l1b33wqRI!-enn=1{gnc^C07W!h@w?FiE7g?h-f5t!0Ysc+vXdLXg!VML-N zfav18f#16jfELM&I|O_k^A>wa@pIlaTx{dg_l*^XIEMxxpie4trE<8YIwCEKO5$8248=iK( z&wDfJZN%QYse2NPP%zx(sJEhhqPh-$E8_M|A|M?vI*2CyC3PxU`7EPt8=tPwObmmT z)}lG$YB@!69AmQnLxRrK?isDTVG3o_Y}Bdaro#fyJh^G>fA;YxZt$~DeYZZS8&D5~ z5C&mBmbKhqnu zEZtCol=}9Ig7Z&VWeATr_fKky6577~)$e#omBxvbY6f|xxIVJHe98I+1IlFO{5al& zS41c96kTO8NY^yS=kvrbB2GfmJ@EkuB?*QJ>LK~sFr#$SG)4H0WsUml-L+PAqWd+>6A#v!{B zGZGajBgEjsS)%QeminuaLjn&-v|d!$K5 zcE1?l4b0^w7Q#s|!m*ok;M2gctz_|>|?1KQI$?S2p4F@bpJjJI|^CtdJ ziCz+aVnWmb*PVK`PW2ZJDC0v0RjOqz3zSPMxvtw}S}uc*%C4&XC!`{*;Eon=m;(74 z@N^sG@?0^=&X=h);g=y+1!ohHf`0=8qQAZ?rqPxT6FBYvVtH+X8K5_YGw=Km^u7es z)NtI7ChjUO0e#Hbb(;5sygBCsqOJ1N@L&nSGv!c5sRHHJDKht7>py?ooaX{Ektjn{ zNk~yi?yT{dhQ6*j0_rKoPtK3O9>2a%t$BJSP-5$XgfyMY4PN&H&=MZ!%eR9yR*NVZ zX|F*mzY=-3k4<*qjT-}1`4Nd}jx93J%EI2NmH&ogFddi%cs1llJo*x7_e$sef=>JK z)M_MFUtq7XR;yv)o;cYJi14htse`kz?w~6v1cQl8^QDq&I!LRpO~Y*T7<*gADXDo; z^E(ui7)H2dcwOBAV}fIl>K`SO@{2)-!YURpd>HN(4-EP_A7MEvp-iD@p#+jtN6603 zSb}n`1B$$+**#Wm<^Z2PQbj6~La&6p%z${d6(eimOiSyc^wj2YwpfzhdvJR3)jr;f z!CuZu($aN`R&)NcDoT!~t?XhP0ts%$xg@ghI!?NoV2c6IIBbKm#9!h}Js}SpJIuvb z0$DwEY*I}J{CKIS$pXT5&e2aN&Dvg@vVd5{J?)z&E*odk@kQs2Ou*o=7Q31GpBb}AMMm~>%dm<=tL7qGro zLx|zCZV&pNl@%8Q?v&+lZ9+{&*eiW?0%nL9wy`OKfxS<)O_G zL)$;VQr?U2=P7vFfiKj48vB*ZcknH(J}%1yt0BkKnJ;5T>$|m&RJ*erz-vhvUlDyz zGTLNbbZz1ru`g3XLZ*iC`hHu)2!(KdxGP47{kZ-{!ub;tkT`25^f8I|TQ09;@SbFP0TU)1z$$+6yN(8+Sy-4Ok&#+NAy-*L%txBhHvy)6B* ziH6acK;8}e1U--pf<$Lsx6lXBd+Nj>%BB12u8@2so0e6Jw3NQwlQwvK#-{*9&FJcj zC@&iyM(wMa#CdxpU2?aad zyJz5C+f6JykiY|d?-=Poxh373EaZ<4a`(?1E#`-`gMK#dJL~F{yYP}v_@!v@l1Q;No-j>keZCK;en8{K7;td0v8Q8f zBJ`B3TVTlrq&m|&Y`3J~3lhEN#;to-yG9?`MXycX+2iEkIrFvxp)z!;% zC#eEJISmh30YAq1`)OlT!CzV`IhDpP6X?iu?Q$iTH!5h|gFRvJ&Az8WGDuT$WaXKu zAzw!%Gwj)^R_%Rn6l1^b(7CQ|+PpH|5}3`J9){;Yr0^8LvxEg~x6EcLTUnr0>sc>d z?R~w1zZ{}wdRZZQXp*{e61j@@nHV7^B83+{ zZy|7t+jiOgT6=n%nSj!9d2(X?OXT+b+}>A4Y#aP)H&3E4PvnF`R2$C-2xgd?n+R4# zV@vvsn*u=>PD@zr#U%(Vs37d27c2_|(x6{w{Gs9GPcN>0+k#eL=DqiBXdJw#^=Nuy3n-yQY6D&?bXY z860iIpc-JyH{Zp4!6jw-HWgHlt7iX^VKXUr;qY}zzGCrc-q=wNfRG5w8jj0 z;8XB4oVE}EznLu%m*V23_qKkNBy^ji2ZS#}(LIvFH**y0Go_YWr=Em!O1T_fF6 zaeYhfOb<34IUJJl;OPtzx|L&+PFKc+myEUWJYo$ZHpB7>wy+*>m4l-ClTPkjcvn38 z^ap}7$$tJQj9*J{jdl-1Zd%Q3fn4<=!IC?l#LQv!YdzFQ3N7{^Y;^5lRJQ zDeOyZ?i=K**4~%_vA^yjizD-a-c^Lty6d7FKPj|VK7g}B>ylro zODXcCB1HYnNEPR1FtXb}g%uF5SPe^yi^VNowLa)A8Dae9HD@NEQfj*IBItFw3?krR zq`0ymVj0v}a8~D1k#M7E1C*}hkO0(9%dHC0med3l7^OO>55k^;)i>9q^?lq594=!U z)O8*EbR9(q#bQaK8@PkYAIFXf{Ok%fZCk0rX6*{Y&5*GJD;Q*80`G2r)=F3-7P_%kf4$uwHOm(zd1P**v)|K;;z; z5Tk5)dL-m8m-pkN?4@fnp>kAu|3bR$GVrx1zf8%l<5}H9KnsxBY!@#a9AWl!WmMJ) zsd)ueP)vn&HMcgBQ-YpTO8tUd8$B$&#g`E`v{-IBt-?#O9QS4EBH=1c7dJ}IS|o8o zM9pr&Gz0%Q{rx+Fw%NPVluGJRg_enP(cMctaGogj!2R^vZf{6SpiD=TQoyn=96ZFE z3}-6Ky%^o$<>MC>bgIF>nR)#$rKFd#A+@el&YzIsADoLK~9R(|KLASj$0{S@kD^U}#VzW;y?*d&I?npXK zEd_vU8}#0f3sU+y%;ZMb9E7nCZ*3O2i7nVY4Ew?3`yl*Qs=G#4cxbXdF1rr}mJ=3} z4y`Tv1DkiA(dAD!Nl;vQZvcfW*n0H-?9clw)UW3FZ^W{U+8PVvIm69Tm3S3sjsVS| z=%m^PGmZ%W$j$9BXVg${6i;Gy>KZf}c-DZ-aWsyAU5b9b8tyS9eS>XN~)MCC?6MVR#nr5i=w)wq4ZmDs;sXL4G3?;>fX zhtkmRyYJzlOO&~`m%jR!E&)`!xty4b+e1xUrmF^wzY%^(O-UMCnl&k^FIG-r(2H27 zmpC2|4_$VFOl?fIvHp>8nBy%E!a7_VzqHTjVfY5Z)Rs-;k$H&Z=gJ5m#a!{`>20>+ z+MQ@?yQEH#(ckTjS6$krg{!6ee>WFMY-XNu^u(<>j_U$kBrHVBjiLE=VjBpEI9a6T zlO97sMwfif<}CPSoO}Kbzk|Vc9qsKu{1I@Kpb!=J@Yx`Z{)FBu7mj|eS^05g4ktI2 zNYV|@gfnWTFlY1lugZW9YFHVZjnngaKUGDHs!r2ywb}OOdD>t9U?QjLn$6r{!0}t4 z{8^CH<|HDKB_l4qc)djQ+H|a15H5+d9J!ok9&3N@EYAh?Ef{Dmq*AQubnTKepU_8v z@9JONvjBiG$7?FfI0;0GMb3^c;Jl6)nuZmcC>l|YjCy6p5M5?~g7eqIZG{=D!U}Ps zPW$U9&lbahSvCU#+}V_r|2bss8*3)nbq0;*RX`!~aoDj2^UcQ2It=BC-C9FK zppYOF!OXg|%D@XZTs6?&#n`tsd&p3aHxU>$X@O@@6WD@r`z9`rOYwY|`ipZ%1Ka1t zm}HZf>ZJx)5Uk?gZsrk5E));c80c2FQK&7LlwD@oz<3cDyrom) z7V&R)s9PK{jF%o^5Yt>aX*fd<#6KN>R$>0Uf^}^V5}(=Quh#hep!93op)_zSCQt&Z zi7z3GpN9?CeJ1cZJ#855<1Qz02zU;Ny@4V-una&rtzelY-g7$<;*l*$<)iKE=3@eW z87JD9C=5enmA4}4X-<@?zAm(fm}Az&<7;^a#&YBgqo7O5cp96RO#DSt%IWJgtkrRrQVGp3hZZ+44C!$_OrxI5Gb!?QNez0abz>EtEN7cn(|Tk*j* zU=^tk7q2DNBruSuHNwgYTcbm48 zF}iuaIjAiO@nVUe5m3}z=o3ezqImd-o=|FBO~ZlkYFnY5@S2h^pX~vyRzbabEZp=o z>ZGJyHqf+$pN>2;@%z^~+;}r(c|;Eu{jzHEKSD5@J;qxiu_Cb)}P`! zpOw=)m*$H-8BYkMVukErYJ?*X3a&*PhRre=fPe#-R#5;UJiAA%Rld!D?S8mP4{&C$ z%tQCsH#24tF@>5jq0#@?CtWrCqe3^H+MM$AZD4Nd#xti^K68y}1X7&Huhu6KeSa_9 zv)8I5m&`;wHlE#PaRkHPn*c$WS8d97P8mZUH3un{y0gXhR6Rc(j&o}iE*xo-A_{)~ zqAFS9Trdu_Op z3RNto9=ER3G<`o~+|R&?ryr|4Hu$+DuKbo9v+UW76)ZDup;0V!PKA3J+dw1+kKe3; zdM5AaHL(NoejOBnE7Bp z_bosMwYI+Gy`dQ)mg4Yg!~=Rmg~$Q9vVbD<6rJUB)NQ$0fV2THnE+Q}c7k;!TXGj4 zW58Hx1w|fBko1)ri5iMRGE6h!TGDvW>NoEA6Oq20ABO+d=G_YuyC;iZuVY~5+Rt!a zi{iaM-@|c#@FrSVn_DGLPTPW3C}Qtd*Q_&uun4WP*(`e1oiFfR!)(p5OI|E7{?yU9 z1^8E}hHggVO1#3qIg=Ol{nYe5KA^Vkj9b>68j_G0Lmh-71K23-$sY$QfnW_Ksf)aI zo^C1}4_)>b-bH$Wf~64n2&C#?liMxoz>uh9qGEgJ4Z$V=okl=o;?tVZotQm3^VX4IUC2+}o+X zNtDBJp{~aH*$KnNKExjX=$xE;mD~y!hTer5ePM1zWji*w3@M}2;S<&^_xYZ2%ILXY zr{4BiGFksrmG)ybD~?k?W-Nuhc62cB&$a&OZa7KwGhA_5{C?BB32rk%W^p$UX{5nX zoLt3Dxl&OV5BoX)J-){bYon|l!{)lwRWQA-=8&*j(*tSm~UM|P)<)M}%1>-LrZR%)8^)0C^5Znd7r1N)Y zSI160L1F>YxXIP4ovDc5NbZ81RZ+%k0JU#o1E7vC+a)i|A=CD4Tt#oXhaA&uCENA3 zlGdGCK|=1^@Fz99xAk5GU3IV9oe6s`iaBia52ACB1iv6)dd52BWLi)LGc7c#nRMgX1Wm~Uza|+@8N1p3Qx1L;hAn612=L!yHl(trY3F^+S z_NBUwZ{)xAP07VPVEi|t(4OiOCtjhdY0Lygg3X^v>G%G$t0;3=)+NZ68fBgY>HzGxf0 zezTHDcr$@f#bRVFG?j_1j7l_iEjh^56Vfl;H#1=GW$4Jy*U%kyi5Or6{;_btxcp?N z>-OW942VAU9o~nY8ZcNTNJ=KiUYAr8@%TAky}Y4QE_7GAT1GgDE(y(Z{&h4F$W_ZSl74A$&?sfX3~r1><{~{H0{hDvz~4 zgi(r=fbBA^(x3E)o!PtKK##oRUsHi;!nvF?6|qtSk+l}$ueB(9vVgde<0ZH3SWNIf zieqbVqS%%^y;#9LIRIk#7;6Dq2?mTe%JC0f|9Qs*t1e-TIS#08?nH#bqc_s}-MZ+P zqTa;g#D^DkD}z;705heyykyO@roMk8lEp>MfAtIb|MB#W|8WM|_x2<+vC)`~8{2AZ zHMY^Hv5ls&t;UVf*tX4y8yge;a-Q=&&zt!JX6F9veXqUNx^$r@6cpGlX64LBljxpe zI&C)v(pgQzW-CAXHNr`}q?rbMV?3)6MvZ^+`;sm8pf`-?MJ)*q;lA(S>}Lo8!+7~ zj2BGjaa|p=Wb`L|g+Dx%|1@@2;fW*?cIr8rr|3C3!;_&Q{lk`?rR)hn&@KLi1}7CJ z-Y$3ir){r8LdElNU$Ja6`9X21GaEVY0ai+dl z5}|Ii8^V>k6A#uFl^X&g$X)k+BASRtHv^9C^)PN!M-H3aFQ47aE#ui_u{2U23!dnuR09QTc4Tmc}*y7 zhGc$0J&lj~yb69#IgjUA0kAf1fLllrU%o4!G^l2fe9Bjx>r(#9=~L!Pr?>?(cHz`??!nAD1KBKBSk zV1Hw_{3V|Ws6I4UfLs(U!~7s?zPsb2z12C>I-ynfNAe{N|kKz0nU!rB5n?ShwB|$dmsn=&;f&%J9^%P#KYt>!d<|X zigrVtg2uO|UueO`@bC$%YxcLa!j85Ycw+QB+Js2oup@b*2y=KTcSy%&OlFw0J3lH6 zJllb5u{1T-ky48M+-j|u8>*UXp$H(or!1_bn8iYYcaO}Mr)@qD5p?|2+{z5cm>UBt zkG|>C#@>yMKdoE{&xxw4;Q0EgVd&|zl(S6C1mY`Mcz=zYEEf(gHyM&+J>?xWc9iQD zIBN1pGJa^&w3I)M%@}pd4b)8O(?sFU^4^#3W2+(Up$+qnx$Y{`Uj|_*lcM?4q-zb% z04wTgEbVVzi4`7LWchPw)hK#}KL^b6By!i|;mw4`ktRGFlTdH0-&qgnlE|7l2Y6@s zFGX*x6cZcSS5X3n8@q|$hQvqG2(@L1(hhoxYtHQEZZZIO?aot)y)R!O1OD${4TAjV zN#8F>M+#zvDl#seC)4T0V9~-}wwDN86(|!_HJdaIyze+YuOn!REF7YdNi{x#+B>-W ziA`I@dD)+~d*#A9%~$JU2ji!Nfw4a4+D#dM2q4wD@A7O{*BwuP%CE+wm2U_$nX-kS z-yDC^5--@!dBgEJ)lmfwgz&SdgyeoFeL9iB7~%bh0hi3N5{ng}y@snB<<7SLlOaIS zyHFyuQ>!Dz5HHBR;iVQ7bPxFJXTbVfbwW^$wMP1EO0o0yciLxlDM}6`HAajHRA_3H z{;F%VTn*abNm7PnB|8-~+VQQrGR4(J4rw#4B{&{P!)eZgic*F5!Tdgf4j$8-D?m#m{vqU(Hz zP*-a0!Yg#{sw(um6dN73s6M=$Nm-{g@>y)6ZxHreRYHw}eZVA?Ob@Lh{2nq-uXONp zt5yuc69Un=ZH-Sqp!l$lV^1MXJqm9>`9wIC;uF>@pO$)5U*iPDYeM8e>U^_XgNux> z0a; zj?~IDc4NO`NYXYA`22T+a4Q-rfXZz4HbBqIxe*$I*KoSFr8M07No55w;O%&pT3daA zLCmO$ul}==EG&dF3rQR0K?Wk7i!7R{T%g$IeulND@Qx3`=Uq(^y8N-VX>vT0RZ$d6 zc$J1mMhn1rVP6;;*@jyb-Ip80IE6Ie&&|J`X`gyQK8y#W$Li&K9~RwsI8q;UtY*Nt z0^7o5=ckMd`*F-VR+)X1G2X)*iX}bY=q&$j&(^$w*#@b#2RlR4msToP@=(iHbn7i~87@|&u*y9{Mz>j?l{lN@0uxCY*PH_Y* zaYAP$?0^K*I(Pz`jQDXN84(oOcL>@cw2G5*-=oyTJ#4^`=KCU3a{~(O%nSR)(h@dYM+~?YslPEP%<3c*fjj-p`+Qfr!V$ z)ZkQnN>6c5N&AXeh$%5-=maz_=hj11kS|1sWeGAnS0`UCCydKNh<39`8Mc`}iZ^To zSv&+LJbpz{P@&KM14bSRujZVk6UC8Zw(0O^eFej(=(`Y`^je zN0#)Z?hRXq!?j<02}(rIFjcNgDbcUt_a~*M(|bG$g-qSnvlXFWB`?UCLi-b`!YXj? zOA{pP?$+V#gW}u-03KZXksNE=RQogU)`190sT`(*ZPv`=el@<2?JJ&azLL^^kMI7b z?UB904>P*Qv1-bUjH)30VIby3S-u5sPU{@$D^iwPTe5T=MBSqC)l6Jo7>WI1Qha77 z%}sv^5@>;>5bn2sSiEKj^<)Z~2zkP*#j=-l}5^kfSXIbrsY;~vWsPyX=W*-BK zY|pTyq@=uwiIc_a2_GB3Ag|IxEDfNq&Ba_B45=8Mj;4DgBen1|~?{EImnb&6pv$@X7!*MF{1&6qq1 zn0aL*vHrId5Y$D!0VA}vtu(VC6x;yPkUiHpCs=ywB`8=RNQ`0eNeSjl|<2kxdLn`HQPlQ7RM0!_9$s-qDd%9a=d=&eE<&mraeScfiYr=)(L(= z)U=ONKg8fDV>o=jdXYfO6mOzjJ`UcT&92Te;_4?-?Ecxn z*tNU@3R{e;k&lo^LhE~-t_0Ft^V*L`Lu?I5Q$JhzTYq5%-7ob?d6?6R@Crs@GhL4k zE+F7*lYB!T5B1vsMYAbv@bV;9B`hD`e3B}0*T?*edllg(~g}yQUX(GR*Du0 zlaCLo=6D~5*2+d`@>i%?o7CPap?0b^0-p*6r%JUiI25bBBNyvLBb6) ziYW#|Cl8>ZT7QMErF1Tjt(xhekTdiMDGmWru)HiUKw=`oWp-GNCEeHK2qM=blQmG_ zEB@iMJouyTj*sVs*0A|UjmY3)M8o4w{y0Lvobj{Z#i%FC8n59O_wxj=(=-{ZFU+TY zU-KF082TG`0aZ4mp!<0tf?l$YZ;oH`GUjfdiZO{cuqx3;vss;6U-tZ8nIU(Nv&^N`8>mmp`6PXMdN5Lxwwx<|ym8do8-)m@k#@*N#+C*!ES_f0ChvO@Yhw8(!D4xu%%U~gXE zPQJi912P0ZTt$Lxl~6L^4x=Q+^YBi{s_>Ws$;q$Gt|Ku_}nuu4g!eG=dcZm~! z--MaaL!dX4nC8XC_Pr60IB`!#`2t|OG=9W4%=S1hM|0wVE{)TUI2g)Zxpt0H8YdKH zJF8{@m)PXyJ+D9hd*x6`1`s0)pk2_o@bkc%uIRgOz|5@*+(rF=*YK5()ky^FpBxFZ zuQ#P5nD^bvhRy#C`XljWX^sGRT&&b}Z96lf#z8`z47V09lh5W-7Z&?CuJyc7Qc_68 zBw$cTbytLJ3XBKLr@{R+(Pr0GRB?LHom0jNMe_|@Bo04IRS zoBd0*&;k>o#*o>|H!rHIu{_mW`Lv@{X#*|6@^%p6C9Ey)E6F&&2J=_|4j=itTG#=N z@p36v4V7`@EX=~@u`4lV;QIu5n)vifF$xxz37PP?DNkJayU{RTL^V_v&pgn|0k)LO z)_tsjy|(W&HDPNvmM6vZH_JYu?EHK#I$ah~Uv~NA-#^m%xpbXaA!I1Q9FEH5UvPdrPVb-|h40M*^P`|R z^N^klhO@9pqs4Yy&@G-@&om3oSI6>)R}i7E(qIkl8K+wo9y*IgJaX8}Ndq#cK=1Hp z$N&ydV$HyB8Q_r`Qwn!cxwsbT;>4cZ06z^^Y;=t)yoHuFre+o` zKVtuHwD4h;R#qmjT{MerEif3vjVJhD29pjiLG@b)UEfvvZ*Yq8;?1lDP6Rou`r2^4SxSeyF99%$%e{A|Bp=i=Z$!L3!C`+H11_iwsj1IYu1Ls(@Dx=AhQ#94PQh z#To+P!GyMBQ#e!uJ`y*3ss~iB@zU`{3M-2~FGAz4t7E(rZde~!C(c|2V*w#xEvDTp zM#ti*(?Xl*FiEMf_P!DXdhBwLj${tO@nFJ{Lk*mO{3*(=^L17=2(un<8xvqdA^5lA zc)|^JK!IdvlbX6|oq)qIcwSxVk(_4c%;GMs6Yr%^fJ(8~ba! z*_zhUG65C+-)rm1PnX0n08bp+S_D9jrjCv+iZ*dH?&1k68`ClJ_u^RAPinida5T$8 zQ*zN3eQ5p4UZN=?PpoI1F#W4iB1&>_cDf{i)?RsGo_T)NMSBcuUooTc@&dL=w7pC2OSL0?IYwTg4Z_m@fJY$vb2JTD@glPgx z;NdyS0Ova5a%+`FxJb^9qI8REBxZ??)uK2=m&EBmG)9CQ3s7C}EDxVWCK;w+#qz)7Xy)Co@<>;J3GZ&WW{*GHh1nAboUW za9h70Teut}jx)~9rtQHnb_Hs^_cBF%luN|(7KJf)Qd}`Nw&{O)uTeS1)s&lA*-zkP z`kW3rSL`r4EOL!h|NDUgfc;D1u(nKD+e+K*rTUrTL*vp{qjXM7gma~0)~5KJ1~8Lz zafRy?G>MiY6u>;Qm3TGX2_$trBh{g~%*HI1o&Vo-!bt9(%;rzt^j*G7SiPL`-$qjX z_Kor#r2M9K8h({r?K1!aeokzLfsyZXhIS0P;Ku2sLle9u^t_p9VkzY^L58NKA5I;a zqaOQ|YM8H6W!zU|4J)t6z++^91oCL`Z}f??q?@FQiJF!}{Y%6=J|i>jEW)>$AAOy| z$?Cen?ZqH3QNt6TVV;1`w+c5Gg^mN$D~Q|8qhBPEmrabcwKz}EbuAe=&p}IT?oL{@;B0eDQZbW1F@aO_-iw9` zF4F}eVUNy9XpEdZKMH2TevPMfsDhxR)6Mf!<>zQ$=P?D8iq--{k&L+ zazI0Ggfj9q{ z4Kc)=3f6^mbQGEwZ8a#8uFd%UG+YLa>wE8wDbQ0!wRH5;_ZXqjhQe$F0D)4s5Z-%2 z^8u`&!6o+4XG_@#0ux^$8IU$VtEudc0U0>ldldCDteSU4I|1;DhXY@OT4Cs|+vfE_ zb?2}qL(vBUjBwykR;{03pr(USxIBv8gu=2@lbGihBB!WjTrjVTI(I}VfDMVIgeRjL zlRULlX@Ure7e{j?#(_+>amr_(#x#%l;(%0VI8|<{Dj&?Z{?*bNjB&fph2HO;Z z>fo}CU0q5o=(U&F(dx&^NTHanT^ba$JO1`9i(s>Bgd46c3m2EC0hw9pFymM zMJU4zqZ|F_ez&r0L%Qucy^n7@DMX74xQ3B&e(dfpJ9JEaWh*1QhI$!Y*T#}wjLnuO z@{iybyBi_RCGmW;-(lY~y`@DyM`f92XB(@`zh#sOPuFij0C3|CJ#prWVC#o84VDGd zN*g_1Zu8mCL?sXonj6dObr}PbSekmiU5Qrcp143g_6%>J1_;cq^(fUw&U{EYuF40} zEcU$~E%#VUqwCIx$9aT7AAskHq1uI+9c^^GSl_XO?(S}Vk_b7LPGdb-mHN0$SdJFPpc zL5*0eSi1H$ykjrN&RxW zN+#Jo_8=^rF}e1N!{Iv=NjXyZpX9DvIs0^IbbE^sckm1)_S#(Zh9FbVZ)^e3y( zi1p<#wFf}{dmS>j10OmZ1UKARzsz_)jyn(dX$CSNkb4~u;ri(7JgQdWe z&~M4mTL|%j_;NCVnT;AVA+ZE*A1n7)sokUQGH;Rqat~#A#`A~&KGAoclxKPeq;bds zjNq#qNLPgQjIdJ75J-dn=LK*h0BArjhm(4Wr!eaN5ostO&Ld1N>6+DOo|Vt511*D& z5MD6I=Jd$~-{T$tfbT^0WYA|U?XQnl)`ev)qS}|e2ccIR*u}E5^J*Iw8O3uhr-z`n z!ejvBXqNSI7tc(Q3}P^x$g+T8vEN>BUaI}G31F7PH7_04I$l$eZ-k;)v1f)2mL$mvHzi-3%LJZJSip&&CMGBTuCMCtgDpkrof0+L{eSW~whg zI#^H)Zd{bWXg3?UAX((#J_u4Dd2S00qb*%LLa1N@yeC z2=!76VCfX^Bh}O*pC%ubnQDbJ8;kU|1fF$S`X+`kI$Y?-uMBA`c#OIlmUS>|P zzk-E2Tgb3wrD5vUjK(^lb&hB`W+|Y;#_|`oT0fCUfr&q(W&ABd`dvrFuXNDE@KMRD zeYtG%;^A!-jF@5Y&2bf9+va3^B5-H=@h@gb-((xY5be0tFXJaR(4B5DG2NCAhCRA* zE1QIZK^*`LXe1LKjXx75>xHRI5|JW&3Gb^2jgrb6DJ~x-2D?M8Q&tUv!mG%XLP>9^ zn}sTdQARF*0~)Snw0~}}Et!T-*;;Lu^t8$1LTe*wydKHSGve»GKj&ZCcb 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

D@v^+wtM%jo|g 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 z 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 -M5?iuf3pgA$xQFJxdW~VqwFO^FLJWYff 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%0DX6c=ToVj)C6+m=ncP)qMgWvRYrnx4ng@qqzMp$+ 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 diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 858010da..1c98ae19 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "antlr4-python3-runtime" @@ -73,17 +73,17 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.28.76" +version = "1.28.82" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.7" files = [ - {file = "boto3-1.28.76-py3-none-any.whl", hash = "sha256:85e2fa361ad3210d30800bad311688261f2673a9b301e0edab56463d89609761"}, - {file = "boto3-1.28.76.tar.gz", hash = "sha256:d18688bc5d688decf3cc404430a3ac3ec317be653cdcfbc51104c01f38a66434"}, + {file = "boto3-1.28.82-py3-none-any.whl", hash = "sha256:bb8ecd6f86289ceaed566eefc0ce8ac66a85e5954aef115ed4ac602cbe61f101"}, + {file = "boto3-1.28.82.tar.gz", hash = "sha256:ae1352d0193aaf90c47d6e57ab054b0b1fabf0fbbe9f51eefec431bf2c3e18f4"}, ] [package.dependencies] -botocore = ">=1.31.76,<1.32.0" +botocore = ">=1.31.82,<1.32.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.7.0,<0.8.0" @@ -92,13 +92,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.76" +version = "1.31.82" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.7" files = [ - {file = "botocore-1.31.76-py3-none-any.whl", hash = "sha256:74e0a4515d61b2860b24dc208ca89a68d79dc00147125d531746d3ba808822ad"}, - {file = "botocore-1.31.76.tar.gz", hash = "sha256:479abb5a1ee03eb00faa1ea176bc595b2f46f7494777807681a9df45ed99ea18"}, + {file = "botocore-1.31.82-py3-none-any.whl", hash = "sha256:5f213229348433d0b7b6aac2d9ae2fdd15c4ff9f38c30ea072f5b300bf6c1dcb"}, + {file = "botocore-1.31.82.tar.gz", hash = "sha256:9d7d8de1789b1ed37b86a2b0a4fcff9fac91deef0548461d411e1626f68ca70c"}, ] [package.dependencies] @@ -1589,110 +1589,110 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes [[package]] name = "rpds-py" -version = "0.10.6" +version = "0.12.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, - {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, - {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, - {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, - {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, - {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, - {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, - {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, - {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, - {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, - {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, - {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, - {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, + {file = "rpds_py-0.12.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46"}, + {file = "rpds_py-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e"}, + {file = "rpds_py-0.12.0-cp310-none-win32.whl", hash = "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29"}, + {file = "rpds_py-0.12.0-cp310-none-win_amd64.whl", hash = "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963"}, + {file = "rpds_py-0.12.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624"}, + {file = "rpds_py-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c"}, + {file = "rpds_py-0.12.0-cp311-none-win32.whl", hash = "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10"}, + {file = "rpds_py-0.12.0-cp311-none-win_amd64.whl", hash = "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa"}, + {file = "rpds_py-0.12.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534"}, + {file = "rpds_py-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b"}, + {file = "rpds_py-0.12.0-cp312-none-win32.whl", hash = "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978"}, + {file = "rpds_py-0.12.0-cp312-none-win_amd64.whl", hash = "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f"}, + {file = "rpds_py-0.12.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2"}, + {file = "rpds_py-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c"}, + {file = "rpds_py-0.12.0-cp38-none-win32.whl", hash = "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595"}, + {file = "rpds_py-0.12.0-cp38-none-win_amd64.whl", hash = "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543"}, + {file = "rpds_py-0.12.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a"}, + {file = "rpds_py-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0"}, + {file = "rpds_py-0.12.0-cp39-none-win32.whl", hash = "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31"}, + {file = "rpds_py-0.12.0-cp39-none-win_amd64.whl", hash = "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4"}, + {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, ] [[package]] diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index f5d5a576..52b91605 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.2.0" +version = "0.2.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client.py index f76c14ac..8821225a 100644 --- a/lib/datahub-client/tests/test_client.py +++ b/lib/datahub-client/tests/test_client.py @@ -73,6 +73,7 @@ def catalogue(self): return CatalogueMetadata( name="data_platform", description="All data products hosted on the data platform", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", ) @pytest.fixture @@ -140,7 +141,16 @@ def test_create_database(self, client, requests_mock, catalogue): "displayName": None, "description": "All data products hosted on the data platform", "tags": [], - "owner": None, + "owner": { + "id": "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + "type": "user", + "name": None, + "fullyQualifiedName": None, + "description": None, + "displayName": None, + "deleted": None, + "href": None, + }, "service": "data-platform", "default": False, "retentionPeriod": None, From d10bf95d12a4f66db1ede4d94d0ad886588b91e1 Mon Sep 17 00:00:00 2001 From: Mat Date: Mon, 13 Nov 2023 15:28:44 +0000 Subject: [PATCH 08/64] Update to latest OpenMetadata version (#2327) * Update to latest library version * Add an integration test we can run manually --- lib/datahub-client/CHANGELOG.md | 14 ++++ lib/datahub-client/README.md | 5 +- lib/datahub-client/poetry.lock | 81 ++++++++++--------- lib/datahub-client/pyproject.toml | 9 +-- lib/datahub-client/tests/test_client.py | 12 ++- .../tests/test_integration_with_server.py | 72 +++++++++++++++++ 6 files changed, 144 insertions(+), 49 deletions(-) create mode 100644 lib/datahub-client/CHANGELOG.md create mode 100644 lib/datahub-client/tests/test_integration_with_server.py diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md new file mode 100644 index 00000000..9325662c --- /dev/null +++ b/lib/datahub-client/CHANGELOG.md @@ -0,0 +1,14 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.3.1] 2023-11-13 + +- Updated to OpenMetadata 1.2 diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 77406b1c..2a52db22 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -63,7 +63,10 @@ data_product_schema = DataProductMetadata( table = TableMetadata( name = "my_table", description = "bla bla", - column_types = {"foo": "string", "bar": "int"}, + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], retention_period_in_days = 365 ) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 1c98ae19..d65f4f50 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "antlr4-python3-runtime" @@ -73,17 +73,17 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.28.82" +version = "1.28.84" description = "The AWS SDK for Python" optional = false python-versions = ">= 3.7" files = [ - {file = "boto3-1.28.82-py3-none-any.whl", hash = "sha256:bb8ecd6f86289ceaed566eefc0ce8ac66a85e5954aef115ed4ac602cbe61f101"}, - {file = "boto3-1.28.82.tar.gz", hash = "sha256:ae1352d0193aaf90c47d6e57ab054b0b1fabf0fbbe9f51eefec431bf2c3e18f4"}, + {file = "boto3-1.28.84-py3-none-any.whl", hash = "sha256:98b01bbea27740720a06f7c7bc0132ae4ce902e640aab090cfb99ad3278449c3"}, + {file = "boto3-1.28.84.tar.gz", hash = "sha256:adfb915958d7b54d876891ea1599dd83189e35a2442eb41ca52b04ea716180b6"}, ] [package.dependencies] -botocore = ">=1.31.82,<1.32.0" +botocore = ">=1.31.84,<1.32.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.7.0,<0.8.0" @@ -92,13 +92,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.82" +version = "1.31.84" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">= 3.7" files = [ - {file = "botocore-1.31.82-py3-none-any.whl", hash = "sha256:5f213229348433d0b7b6aac2d9ae2fdd15c4ff9f38c30ea072f5b300bf6c1dcb"}, - {file = "botocore-1.31.82.tar.gz", hash = "sha256:9d7d8de1789b1ed37b86a2b0a4fcff9fac91deef0548461d411e1626f68ca70c"}, + {file = "botocore-1.31.84-py3-none-any.whl", hash = "sha256:d65bc05793d1a8a8c191a739f742876b4b403c5c713dc76beef262d18f7984a2"}, + {file = "botocore-1.31.84.tar.gz", hash = "sha256:8913bedb96ad0427660dee083aeaa675466eb662bbf1a47781956b5882aadcc5"}, ] [package.dependencies] @@ -107,7 +107,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.16.26)"] +crt = ["awscrt (==0.19.10)"] [[package]] name = "cached-property" @@ -361,16 +361,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "commonregex" -version = "1.5.3" -description = "Find all dates, times, emails, phone numbers, links, emails, ip addresses, prices, bitcoin address, and street addresses in a string." -optional = false -python-versions = "*" -files = [ - {file = "commonregex-1.5.4.tar.gz", hash = "sha256:271530678ad8af53c0e8c233a762597969e846d0029f12215ac178791f3de0a5"}, -] - [[package]] name = "croniter" version = "1.3.15" @@ -432,13 +422,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "diff-cover" -version = "8.0.0" +version = "8.0.1" description = "Run coverage and linting reports on diffs" optional = false python-versions = ">=3.8.10,<4.0.0" files = [ - {file = "diff_cover-8.0.0-py3-none-any.whl", hash = "sha256:330b1a655c53c211ffee0dd23c8fee10a2e3539d26dc2362dd3f7606641b8c32"}, - {file = "diff_cover-8.0.0.tar.gz", hash = "sha256:0995b78a1d5101f5d8922006220c070b18e32a5dbb4a96a073a1941705fe71e7"}, + {file = "diff_cover-8.0.1-py3-none-any.whl", hash = "sha256:1eb651e7d4e25d9d7b4b246c4abe3e9e5645f3856f92c104fca04701fc0eb093"}, + {file = "diff_cover-8.0.1.tar.gz", hash = "sha256:cc39d199eb72fe41bcdcfee164eb5b591533b4c625580e3f9a9acc6869064d7c"}, ] [package.dependencies] @@ -913,6 +903,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -990,13 +990,13 @@ test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "openmetadata-ingestion" -version = "1.1.7.0" +version = "1.2.0.1" description = "Ingestion Framework for OpenMetadata" optional = false python-versions = ">=3.8" files = [ - {file = "openmetadata-ingestion-1.1.7.0.tar.gz", hash = "sha256:89df1b23b8614883f3c0fa0823cbb8a8489aca940824c68ccc75a3e2be43b2a3"}, - {file = "openmetadata_ingestion-1.1.7.0-py3-none-any.whl", hash = "sha256:7b091c2ebfa21c5c05827e5293dd623a4b33ab9788f5d34431d773ff4da40ecb"}, + {file = "openmetadata-ingestion-1.2.0.1.tar.gz", hash = "sha256:9ff2349532fc5b243804d4fd2597efafb72c764b4c8dff3554f7d95198b02345"}, + {file = "openmetadata_ingestion-1.2.0.1-py3-none-any.whl", hash = "sha256:da20eac2be336a6c9c48a70c45d7b6b6240a6ec8f3142f7ccaf2aa89887da810"}, ] [package.dependencies] @@ -1006,7 +1006,6 @@ boto3 = ">=1.20,<2.0" cached-property = "1.5.2" chardet = "4.0.0" collate-sqllineage = ">=1.0.4" -commonregex = "*" croniter = ">=1.3.0,<1.4.0" cryptography = "*" email-validator = ">=1.0.3" @@ -1037,17 +1036,18 @@ wheel = ">=0.38.4,<0.39.0" [package.extras] airflow = ["apache-airflow (==2.6.3)"] -all = ["GeoAlchemy2 (>=0.12,<1.0)", "Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "adlfs (>=2022.2.0)", "alembic (>=1.10.2,<1.11.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "azure-identity", "azure-identity (>=1.12,<2.0)", "azure-storage-blob", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "cachetools", "chardet (==4.0.0)", "clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)", "collate-sqllineage (>=1.0.4)", "commonregex", "confluent-kafka (==2.1.1)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "cx-Oracle (>=8.3.0,<9)", "dagster-graphql (>=1.1,<2.0)", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "delta-spark (<=2.3.0)", "elasticsearch (==7.13.1)", "email-validator (>=1.0.3)", "fastavro (>=1.2.0)", "gcsfs (==2022.11.0)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "google-cloud", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "google-cloud-storage (==1.43.0)", "grpcio-tools (>=1.47.2)", "hdbcli", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "impyla (>=0.18.0,<0.19.0)", "impyla[kerberos] (>=0.18.0,<0.19.0)", "jsonpatch (==1.32)", "jsonschema", "ldap3 (==2.9.1)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "memory-profiler", "mlflow-skinny (>=1.30,<2.0)", "msal (>=1.2,<2.0)", "mypy-extensions (>=0.4.3)", "neo4j (>=5.3.0,<5.4.0)", "okta (>=2.3,<3.0)", "oracledb (>=1.2,<2.0)", "packaging (==21.3)", "pandas (==1.3.5)", "pinotdb (>=0.3,<1.0)", "presidio-analyzer (==2.2.32)", "presto-types-parser (>=0.0.2)", "protobuf", "psycopg2-binary", "pyarrow (>=10.0,<11.0)", "pyathena (==2.25.2)", "pydantic (>=1.10,<2.0)", "pydomo (>=0.3,<1.0)", "pydruid (>=0.6.5)", "pyhive (>=0.6,<1.0)", "pymongo (>=4.3,<5.0)", "pymysql (>=1.0.2)", "pyodbc (>=4.0.35,<5)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "python-on-whales (==0.55.0)", "python-snappy (>=0.6.1,<0.7.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "s3fs (==0.4.2)", "sasl (>=0.3,<1.0)", "scikit-learn (>=1.0,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "simple-salesforce (==1.11.4)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy (>=1.4.0,<2)", "sqlalchemy-bigquery (>=1.2.2)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-hana", "sqlalchemy-pgspider", "sqlalchemy-pytds (>=0.3,<1.0)", "sqlalchemy-redshift (==0.8.12)", "sqlalchemy-vertica[vertica-python] (>=0.0.5)", "tableau-api-lib (>=0.1,<1.0)", "tabulate (==0.9.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)", "trino[sqlalchemy]", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "websocket-client (>=1.6.1,<1.7.0)", "wheel (>=0.38.4,<0.39.0)"] +all = ["GeoAlchemy2 (>=0.12,<1.0)", "Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "adlfs (>=2022.2.0)", "alembic (>=1.10.2,<1.11.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "azure-identity", "azure-identity (>=1.12,<2.0)", "azure-storage-blob", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "cachetools", "chardet (==4.0.0)", "clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)", "collate-sqllineage (>=1.0.4)", "confluent-kafka (==2.1.1)", "couchbase (>=4.1,<5.0)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "cx-Oracle (>=8.3.0,<9)", "dagster-graphql (>=1.1,<2.0)", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "delta-spark (<=2.3.0)", "elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)", "email-validator (>=1.0.3)", "fastavro (>=1.2.0)", "gcsfs (==2022.11.0)", "gitpython (>=3.1.34,<3.2.0)", "giturlparse", "google (>=3.0.0)", "google-auth (>=1.33.0)", "google-cloud", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "google-cloud-storage (==1.43.0)", "grpcio-tools (>=1.47.2)", "hdbcli", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "impyla (>=0.18.0,<0.19.0)", "impyla[kerberos] (>=0.18.0,<0.19.0)", "jsonpatch (==1.32)", "jsonschema", "ldap3 (==2.9.1)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "memory-profiler", "mlflow-skinny (>=2.3.0)", "msal (>=1.2,<2.0)", "mypy-extensions (>=0.4.3)", "neo4j (>=5.3.0,<5.4.0)", "okta (>=2.3,<3.0)", "oracledb (>=1.2,<2.0)", "packaging (==21.3)", "pandas (==1.3.5)", "pinotdb (>=0.3,<1.0)", "presidio-analyzer (==2.2.32)", "presto-types-parser (>=0.0.2)", "protobuf", "psycopg2-binary", "pyarrow (>=10.0,<11.0)", "pyathena (==3.0.8)", "pydantic (>=1.10,<2.0)", "pydomo (>=0.3,<1.0)", "pydruid (>=0.6.5)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pymssql (>=2.2.0,<2.3.0)", "pymysql (>=1.0.2)", "pyodbc (>=4.0.35,<5)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "python-on-whales (==0.55.0)", "python-snappy (>=0.6.1,<0.7.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "s3fs (==0.4.2)", "sasl (>=0.3,<1.0)", "scikit-learn (>=1.0,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "simple-salesforce (==1.11.4)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy (>=1.4.0,<2)", "sqlalchemy-bigquery (>=1.2.2)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-hana", "sqlalchemy-pgspider", "sqlalchemy-pytds (>=0.3,<1.0)", "sqlalchemy-redshift (==0.8.12)", "sqlalchemy-vertica[vertica-python] (>=0.0.5)", "tableau-api-lib (>=0.1,<1.0)", "tabulate (==0.9.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)", "trino[sqlalchemy]", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "websocket-client (>=1.6.1,<1.7.0)", "wheel (>=0.38.4,<0.39.0)"] amundsen = ["neo4j (>=5.3.0,<5.4.0)"] -athena = ["pyathena (==2.25.2)"] +athena = ["pyathena (==3.0.8)"] azure-sso = ["msal (>=1.2,<2.0)"] azuresql = ["pyodbc (>=4.0.35,<5)"] backup = ["azure-identity", "azure-storage-blob", "boto3 (>=1.20,<2.0)"] -base = ["Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "chardet (==4.0.0)", "collate-sqllineage (>=1.0.4)", "commonregex", "croniter (>=1.3.0,<1.4.0)", "cryptography", "email-validator (>=1.0.3)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "grpcio-tools (>=1.47.2)", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "jsonpatch (==1.32)", "jsonschema", "memory-profiler", "mypy-extensions (>=0.4.3)", "pydantic (>=1.10,<2.0)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "sqlalchemy (>=1.4.0,<2)", "tabulate (==0.9.0)", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "wheel (>=0.38.4,<0.39.0)"] +base = ["Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "chardet (==4.0.0)", "collate-sqllineage (>=1.0.4)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "email-validator (>=1.0.3)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "grpcio-tools (>=1.47.2)", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "jsonpatch (==1.32)", "jsonschema", "memory-profiler", "mypy-extensions (>=0.4.3)", "pydantic (>=1.10,<2.0)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "sqlalchemy (>=1.4.0,<2)", "tabulate (==0.9.0)", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "wheel (>=0.38.4,<0.39.0)"] bigquery = ["cachetools", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "pyarrow (>=10.0,<11.0)", "sqlalchemy-bigquery (>=1.2.2)"] clickhouse = ["clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)"] +couchbase = ["couchbase (>=4.1,<5.0)"] dagster = ["GeoAlchemy2 (>=0.12,<1.0)", "dagster-graphql (>=1.1,<2.0)", "psycopg2-binary", "pymysql (>=1.0.2)"] -data-insight = ["elasticsearch (==7.13.1)"] +data-insight = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] databricks = ["databricks-sdk (>=0.1,<1.0)", "sqlalchemy-databricks (>=0.1,<1.0)"] datalake-azure = ["adlfs (>=2022.2.0)", "azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] datalake-gcs = ["boto3 (>=1.20,<2.0)", "gcsfs (==2022.11.0)", "google-cloud-storage (==1.43.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] @@ -1055,21 +1055,22 @@ datalake-s3 = ["boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0 db2 = ["ibm-db-sa (>=0.3,<1.0)"] dbt = ["azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "dbt-artifacts-parser", "google-cloud", "google-cloud-storage (==1.43.0)"] deltalake = ["delta-spark (<=2.3.0)"] -dev = ["black (==22.3.0)", "datamodel-code-generator (==0.15.0)", "docker", "isort", "pre-commit", "pycln", "pylint", "twine"] +dev = ["black (==22.3.0)", "datamodel-code-generator (==0.22.0)", "docker", "isort", "pre-commit", "pycln", "pylint (>=3.0.0,<3.1.0)", "twine"] docker = ["python-on-whales (==0.55.0)"] domo = ["pydomo (>=0.3,<1.0)"] druid = ["pydruid (>=0.6.5)"] dynamodb = ["boto3 (>=1.20,<2.0)"] -elasticsearch = ["elasticsearch (==7.13.1)"] +e2e-test = ["pytest-base-url", "pytest-playwright"] +elasticsearch = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] glue = ["boto3 (>=1.20,<2.0)"] -great-expectations = ["great-expectations (>=0.16.0,<0.17.0)"] -hive = ["impyla (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "pyhive (>=0.6,<1.0)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] +great-expectations = ["great-expectations (>=0.17.0,<0.18.0)"] +hive = ["impyla (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] impala = ["impyla[kerberos] (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] kafka = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] kinesis = ["boto3 (>=1.20,<2.0)"] ldap-users = ["ldap3 (==2.9.1)"] -looker = ["lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)"] -mlflow = ["alembic (>=1.10.2,<1.11.0)", "mlflow-skinny (>=1.30,<2.0)"] +looker = ["gitpython (>=3.1.34,<3.2.0)", "giturlparse", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)"] +mlflow = ["alembic (>=1.10.2,<1.11.0)", "mlflow-skinny (>=2.3.0)"] mongo = ["pandas (==1.3.5)", "pymongo (>=4.3,<5.0)"] mssql = ["sqlalchemy-pytds (>=0.3,<1.0)"] mssql-odbc = ["pyodbc (>=4.0.35,<5)"] @@ -1081,8 +1082,8 @@ pii-processor = ["pandas (==1.3.5)", "presidio-analyzer (==2.2.32)", "spacy (==3 pinotdb = ["pinotdb (>=0.3,<1.0)"] postgres = ["GeoAlchemy2 (>=0.12,<1.0)", "packaging (==21.3)", "psycopg2-binary", "pymysql (>=1.0.2)"] powerbi = ["msal (>=1.2,<2.0)"] -presto = ["presto-types-parser (>=0.0.2)", "pyhive (>=0.6,<1.0)"] -pymssql = ["pymssql (==2.2.5)"] +presto = ["presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)"] +pymssql = ["pymssql (>=2.2.0,<2.3.0)"] qliksense = ["websocket-client (>=1.6.1,<1.7.0)"] quicksight = ["boto3 (>=1.20,<2.0)"] redash = ["packaging (==21.3)"] @@ -1095,7 +1096,7 @@ singlestore = ["pymysql (>=1.0.2)"] sklearn = ["scikit-learn (>=1.0,<2.0)"] snowflake = ["snowflake-sqlalchemy (>=1.4,<2.0)"] tableau = ["tableau-api-lib (>=0.1,<1.0)"] -test = ["apache-airflow (==2.6.3)", "coverage", "dbt-artifacts-parser", "great-expectations (>=0.16.0,<0.17.0)", "moto (==4.0.8)", "pytest (==7.0.0)", "pytest-cov", "pytest-order"] +test = ["apache-airflow (==2.6.3)", "coverage", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "elasticsearch8 (>=8.9.0,<8.10.0)", "giturlparse", "google (>=3.0.0)", "great-expectations (>=0.17.0,<0.18.0)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "moto (==4.0.8)", "pyarrow (>=10.0,<11.0)", "pydomo (>=0.3,<1.0)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pytest (==7.0.0)", "pytest-cov", "pytest-order", "scikit-learn (>=1.0,<2.0)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-redshift (==0.8.12)", "tableau-api-lib (>=0.1,<1.0)", "trino[sqlalchemy]"] trino = ["trino[sqlalchemy]"] vertica = ["sqlalchemy-vertica[vertica-python] (>=0.0.5)"] @@ -2013,4 +2014,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10.0" -content-hash = "d01687366c4750047425c9b7d9248b91b85fd277c9949d7149519b2a0c9c2892" +content-hash = "192a10b6c21e0925349c2e4583351c3dfd7fee33ea40806dcfb45bfa9e085143" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 52b91605..4604829b 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.2.1" +version = "0.3.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" @@ -9,12 +9,7 @@ packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] python = "^3.10.0" -# pinned to this version as was installing a later vesion and giving errors, seems a breaking -# change introduced inadvertently -openmetadata-ingestion = "1.1.7.0" - -# 1.5.4 doesn't install for some reason. Either way it will be removed from the next release of the ingestion package. -commonregex = "1.5.3" +openmetadata-ingestion = "~1.2.0.1" [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client.py index 8821225a..f0fd2a6b 100644 --- a/lib/datahub-client/tests/test_client.py +++ b/lib/datahub-client/tests/test_client.py @@ -106,7 +106,7 @@ def table(self): def client(self, requests_mock): requests_mock.get( "http://example.com/api/v1/system/version", - json={"version": "1.1.7.0", "revision": "1", "timestamp": 0}, + json={"version": "1.2.0.1", "revision": "1", "timestamp": 0}, ) return CatalogueClient(jwt_token="abc", api_uri="http://example.com/api") @@ -139,6 +139,8 @@ def test_create_database(self, client, requests_mock, catalogue): assert requests_mock.last_request.json() == { "name": "data_platform", "displayName": None, + "domain": None, + "lifeCycle": None, "description": "All data products hosted on the data platform", "tags": [], "owner": { @@ -171,6 +173,8 @@ def test_create_schema(self, client, requests_mock, data_product): assert requests_mock.last_request.json() == { "name": "my_data_product", "displayName": None, + "domain": None, + "lifeCycle": None, "description": "bla bla", "owner": { "deleted": None, @@ -186,10 +190,13 @@ def test_create_schema(self, client, requests_mock, data_product): "tags": [ { "description": None, + "displayName": None, + "name": None, "href": None, "labelType": "Automated", "source": "Classification", "state": "Confirmed", + "style": None, "tagFQN": "test", } ], @@ -211,7 +218,10 @@ def test_create_table(self, client, requests_mock, table): assert requests_mock.last_request.json() == { "name": "my_table", "displayName": None, + "domain": None, + "lifeCycle": None, "description": "bla bla", + "dataProducts": None, "tableType": None, "columns": [ { diff --git a/lib/datahub-client/tests/test_integration_with_server.py b/lib/datahub-client/tests/test_integration_with_server.py new file mode 100644 index 00000000..06361d4f --- /dev/null +++ b/lib/datahub-client/tests/test_integration_with_server.py @@ -0,0 +1,72 @@ +""" +Integration test that runs against a development OpenMetadata server. + +Run with: +export API_URL='https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api' +export JWT_TOKEN=****** +poetry run pytest tests/test_integration_with_server.py +""" + +import os + +import pytest +from data_platform_catalogue import CatalogueClient, DataProductMetadata, TableMetadata + +jwt_token = os.environ.get("JWT_TOKEN") +api_url = os.environ.get("API_URL") +runs_on_development_server = pytest.mark.skipif("not jwt_token or not api_url") + + +@runs_on_development_server +def test_create_or_update_test_hierarchy(): + client = CatalogueClient(jwt_token=jwt_token, api_uri=api_url) + + assert client.is_healthy() + + data_product = DataProductMetadata( + name="my_data_product", + description="bla bla", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="legal-aid", + dpia_required=False, + ) + + data_product_schema = DataProductMetadata( + name="Tables", + description="All the tables contained within my_data_product", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="legal-aid", + dpia_required=False, + ) + + table = TableMetadata( + name="my_table", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=365, + ) + + service_fqn = client.create_or_update_database_service(name="data_platform") + assert service_fqn == "data_platform" + + database_fqn = client.create_or_update_database( + metadata=data_product, service_fqn=service_fqn + ) + assert database_fqn == "data_platform.my_data_product" + + schema_fqn = client.create_or_update_schema( + metadata=data_product_schema, database_fqn=database_fqn + ) + assert schema_fqn == "data_platform.my_data_product.Tables" + + table_fqn = client.create_or_update_table(metadata=table, schema_fqn=schema_fqn) + assert table_fqn == "data_platform.my_data_product.Tables.my_table" From 1741697c0182801e12eb51e07facab61da63db8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:20:35 +0000 Subject: [PATCH 09/64] Bump cryptography from 41.0.5 to 41.0.6 in /python-libraries/data-platform-catalogue (#2552) Bump cryptography in /python-libraries/data-platform-catalogue Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.5 to 41.0.6. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.5...41.0.6) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Woffenden --- lib/datahub-client/poetry.lock | 50 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index d65f4f50..d9c23f6c 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "antlr4-python3-runtime" @@ -377,34 +377,34 @@ python-dateutil = "*" [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.6" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, + {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, + {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, + {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, + {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, + {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, + {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, + {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, + {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, + {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, + {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, ] [package.dependencies] From 86a2120beeefdff10faceabf633cfc355703ca36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:36:14 +0000 Subject: [PATCH 10/64] Bump jinja2 from 3.1.2 to 3.1.3 in /python-libraries/data-platform-catalogue (#2867) Bump jinja2 in /python-libraries/data-platform-catalogue Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: jinja2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mat --- lib/datahub-client/poetry.lock | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index d9c23f6c..a3e4ec3f 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -790,13 +790,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -1772,30 +1772,51 @@ description = "Database Abstraction Library" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ + {file = "SQLAlchemy-1.4.50-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:54138aa80d2dedd364f4e8220eef284c364d3270aaef621570aa2bd99902e2e8"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-win32.whl", hash = "sha256:6c78e3fb4a58e900ec433b6b5f4efe1a0bf81bbb366ae7761c6e0051dd310ee3"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-win_amd64.whl", hash = "sha256:d55f7a33e8631e15af1b9e67c9387c894fedf6deb1a19f94be8731263c51d515"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:324b1fdd50e960a93a231abb11d7e0f227989a371e3b9bd4f1259920f15d0304"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-win32.whl", hash = "sha256:8bdab03ff34fc91bfab005e96f672ae207d87e0ac7ee716d74e87e7046079d8b"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-win_amd64.whl", hash = "sha256:52e01d60b06f03b0a5fc303c8aada405729cbc91a56a64cead8cb7c0b9b13c1a"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fb9adc4c6752d62c6078c107d23327aa3023ef737938d0135ece8ffb67d07030"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-win32.whl", hash = "sha256:1b9c4359d3198f341480e57494471201e736de459452caaacf6faa1aca852bd8"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-win_amd64.whl", hash = "sha256:35e4520f7c33c77f2636a1e860e4f8cafaac84b0b44abe5de4c6c8890b6aaa6d"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f5b1fb2943d13aba17795a770d22a2ec2214fc65cff46c487790192dda3a3ee7"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-win32.whl", hash = "sha256:7b4396452273aedda447e5aebe68077aa7516abf3b3f48408793e771d696f397"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-win_amd64.whl", hash = "sha256:752f9df3dddbacb5f42d8405b2d5885675a93501eb5f86b88f2e47a839cf6337"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:35c7ed095a4b17dbc8813a2bfb38b5998318439da8e6db10a804df855e3a9e3a"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-win32.whl", hash = "sha256:8a219688297ee5e887a93ce4679c87a60da4a5ce62b7cb4ee03d47e9e767f558"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-win_amd64.whl", hash = "sha256:a648770db002452703b729bdcf7d194e904aa4092b9a4d6ab185b48d13252f63"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4be4da121d297ce81e1ba745a0a0521c6cf8704634d7b520e350dce5964c71ac"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-win32.whl", hash = "sha256:a7c9b9dca64036008962dd6b0d9fdab2dfdbf96c82f74dbd5d86006d8d24a30f"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-win_amd64.whl", hash = "sha256:df200762efbd672f7621b253721644642ff04a6ff957236e0e2fe56d9ca34d2c"}, {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"}, ] From bfacd6bebe856262a0bdd5f2da149f9c73251e59 Mon Sep 17 00:00:00 2001 From: Tom Webber <80110358+tom-webber@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:41:57 +0000 Subject: [PATCH 11/64] Implement DataHub Catalogue client for dp-catalogue library (#2902) Generalise CatalogueClient and add datahub implementation - Convert CatalogueClient class into ABC base, so OMD and DataHub client classes can be inherited - implement DataHubCatalogueClient using DataHub gms - rename all `create_or_update_x` methods to `upsert_x` - add `create_domain` and `create_or_update_data_product` methods to DataHubCatalogueClient class - update `DataHubCatalogueClient.create_or_update_table` method to create domain and data product if they don't exist but are passed as `data_product_metadata` - associate tables with data products when created in DataHub --- lib/datahub-client/CHANGELOG.md | 18 + lib/datahub-client/README.md | 72 +- .../data_platform_catalogue/__init__.py | 6 +- .../client/__init__.py | 5 + .../data_platform_catalogue/client/base.py | 55 + .../data_platform_catalogue/client/datahub.py | 312 +++++ .../{client.py => client/openmetadata.py} | 133 +- .../data_platform_catalogue/entities.py | 13 + lib/datahub-client/diagram.png | Bin 235822 -> 0 bytes lib/datahub-client/poetry.lock | 1104 ++++++++++++++++- lib/datahub-client/pyproject.toml | 5 +- lib/datahub-client/tests/conftest.py | 50 + .../tests/snapshots/datahub_create_table.json | 70 ++ .../datahub_create_table_with_metadata.json | 129 ++ .../tests/test_client_datahub.py | 132 ++ ..._client.py => test_client_openmetadata.py} | 57 +- .../tests/test_helpers/graph_helpers.py | 137 ++ .../tests/test_helpers/mce_helpers.py | 417 +++++++ .../tests/test_helpers/type_helpers.py | 17 + .../test_integration_with_datahub_server.py | 61 + ...t_integration_with_openmetadata_server.py} | 22 +- 21 files changed, 2669 insertions(+), 146 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/__init__.py create mode 100644 lib/datahub-client/data_platform_catalogue/client/base.py create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub.py rename lib/datahub-client/data_platform_catalogue/{client.py => client/openmetadata.py} (74%) delete mode 100644 lib/datahub-client/diagram.png create mode 100644 lib/datahub-client/tests/conftest.py create mode 100644 lib/datahub-client/tests/snapshots/datahub_create_table.json create mode 100644 lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json create mode 100644 lib/datahub-client/tests/test_client_datahub.py rename lib/datahub-client/tests/{test_client.py => test_client_openmetadata.py} (84%) create mode 100644 lib/datahub-client/tests/test_helpers/graph_helpers.py create mode 100644 lib/datahub-client/tests/test_helpers/mce_helpers.py create mode 100644 lib/datahub-client/tests/test_helpers/type_helpers.py create mode 100644 lib/datahub-client/tests/test_integration_with_datahub_server.py rename lib/datahub-client/tests/{test_integration_with_server.py => test_integration_with_openmetadata_server.py} (70%) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 9325662c..750d886e 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -9,6 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Breaking changes + +- Changed `database_fqn`, `schema_fqn`, etc to a more generic + `location: DataLocation` argument on all methods. This captures information + about where a node in the metadata graph should be located, and what kind + of database it comes from. + +- Extracted `BaseCatalogueClient` base class from `CatalogueClient`. Use this + as a type annotation to avoid coupling to the OpenMetadata implementation. + +- Renamed the existing `CatalogueClient` implementation to + `OpenMetadataCatalogueClient`. + +### Added + +- Added `DataHubCatalogueClient` to support DataHub's GMS as the catalogue + implementation. + ## [0.3.1] 2023-11-13 - Updated to OpenMetadata 1.2 diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 2a52db22..3569fdcb 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -2,8 +2,11 @@ This library is part of the Ministry of Justice data platform. -It provides functionality to publish object metadata to the OpenMetadata data catalogue -so that data products are discoverable. +It publishes object metadata to a data catalogue, so that the +metadata can be made discoverable by consumers. + +Broadly speaking, a catalogue stores a _metadata graph_, consisting of +_data assets_. Data assets could be **tables**, **schemas** or **databases**. ## How to install @@ -13,30 +16,27 @@ To install the package using `pip`, run: pip install ministryofjustice-data-platform-catalogue ``` -## Topology - -- Each moj data product is mapped to a database in the OpenMetadata catalogue -- We populate the schema level in openmetdata with a generic entry of `Tables` -- Each table is mapped to a table in openmetadata +## Terminology -![Topology diagram](./diagram.png) +- **Data assets** - Any databases, tables, or schemas within the metadata graph +- **Data products** - Groupings of data assets that are published for + reuse across MOJ. In the data platform, the concepts of database and data + product are similar, but they may be represented as different entities in the + catalogue. +- **Domains** - allow metadata to be grouped into different service areas that have + their own governance, like HMCTS, HMPPS, OPG, etc. ## Example usage ```python from data_platform_catalogue import ( - CatalogueClient, CatalogueMetadata, + DataHubCatalogueClient, + BaseCatalogueClient, DataLocation, CatalogueMetadata, DataProductMetadata, TableMetadata, CatalogueError ) -client = CatalogueClient( - jwt_token="***", - api_uri="https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api" -) - -assert client.is_healthy() - +client: BaseCatalogueClient = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( name = "my_data_product", @@ -45,18 +45,7 @@ data_product = DataProductMetadata( owner = "7804c127-d677-4900-82f9-83517e51bb94", email = "justice@justice.gov.uk", retention_period_in_days = 365, - domain = "legal-aid", - dpia_required = False -) - -data_product_schema = DataProductMetadata( - name = "Tables", - description = "All the tables contained within my_data_product", - version = "v1.0.0", - owner = "7804c127-d677-4900-82f9-83517e51bb94", - email = "justice@justice.gov.uk", - retention_period_in_days = 365, - domain = "legal-aid", + domain = "HMCTS", dpia_required = False ) @@ -67,14 +56,31 @@ table = TableMetadata( {"name": "foo", "type": "string", "description": "a"}, {"name": "bar", "type": "int", "description": "b"}, ], - retention_period_in_days = 365 + retention_period_in_days = 365, + major_version = 1 ) try: - service_fqn = client.create_or_update_database_service(name="data_platform") - database_fqn = client.create_or_update_database(metadata=data_product, service_fqn=service_fqn) - schema_fqn = client.create_or_update_schema(metadata=data_product_schema, database_fqn=database_fqn) - table_fqn = client.create_or_update_table(metadata=table, schema_fqn=schema_fqn) + table_fqn = client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("test_data_product_v1"), + ) except CatalogueError: print("oh no") ``` + +## Catalogue Implementations + +### DataHub + +- Each data product within the MOJ data platform is created as a data product entity +- Each table is created as a dataset in DataHub +- Tables that reside in the same athena database (data_product_v1) should + be placed within the same DataHub container. + +## OpenMetadata + +- Each MOJ data product is mapped to a database in the OpenMetadata catalogue +- We populate the schema level in openmetdata with a generic entry of `Tables` +- Each table is mapped to a table in openmetadata diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py index 1fcfe78f..1489b0f3 100644 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -1,5 +1,5 @@ -from .client import CatalogueClient # noqa: F401 +from .client import DataHubCatalogueClient # noqa: F401 +from .client import OpenMetadataCatalogueClient # noqa: F401 from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 -from .entities import CatalogueMetadata # noqa: F401 from .entities import DataProductMetadata # noqa: F401 -from .entities import TableMetadata # noqa: F401 +from .entities import CatalogueMetadata, DataLocation, TableMetadata # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/__init__.py b/lib/datahub-client/data_platform_catalogue/client/__init__.py new file mode 100644 index 00000000..e5becdb2 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/__init__.py @@ -0,0 +1,5 @@ +from .base import BaseCatalogueClient # noqa: F401 +from .base import CatalogueError # noqa: F401 +from .base import ReferencedEntityMissing # noqa: F401 +from .datahub import DataHubCatalogueClient # noqa: F401 +from .openmetadata import OpenMetadataCatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py new file mode 100644 index 00000000..31be915c --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -0,0 +1,55 @@ +import logging +from abc import ABC, abstractmethod + +from ..entities import ( + CatalogueMetadata, + DataLocation, + DataProductMetadata, + TableMetadata, +) + +logger = logging.getLogger(__name__) + + +class CatalogueError(Exception): + """ + Base class for all errors. + """ + + +class ReferencedEntityMissing(CatalogueError): + """ + A referenced entity (such as a user or tag) does not yet exist when + attempting to create a new metadata resource in the catalogue. + """ + + +class BaseCatalogueClient(ABC): + @abstractmethod + def upsert_database_service( + self, platform: str = "glue", display_name: str = "Data platform" + ) -> str: + pass + + @abstractmethod + def upsert_database( + self, + metadata: CatalogueMetadata | DataProductMetadata, + location: DataLocation, + ) -> str: + pass + + @abstractmethod + def upsert_schema( + self, metadata: DataProductMetadata, location: DataLocation + ) -> str: + pass + + @abstractmethod + def upsert_table( + self, + metadata: TableMetadata, + location: DataLocation, + data_product_metadata: DataProductMetadata | None = None, + ) -> str: + pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub.py b/lib/datahub-client/data_platform_catalogue/client/datahub.py new file mode 100644 index 00000000..d7afa9ef --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub.py @@ -0,0 +1,312 @@ +import datahub.emitter.mce_builder as mce_builder +import datahub.metadata.schema_classes as schema_classes +from data_platform_catalogue.client.base import ( + BaseCatalogueClient, + CatalogueError, + ReferencedEntityMissing, + logger, +) +from datahub.emitter.mce_builder import make_data_platform_urn +from datahub.emitter.mcp import MetadataChangeProposalWrapper +from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph +from datahub.metadata.schema_classes import ( + ChangeTypeClass, + DataProductAssociationClass, + DataProductPropertiesClass, + DatasetPropertiesClass, + DomainPropertiesClass, + DomainsClass, + OtherSchemaClass, + SchemaFieldClass, + SchemaFieldDataTypeClass, + SchemaMetadataClass, +) + +from ..entities import ( + CatalogueMetadata, + DataLocation, + DataProductMetadata, + TableMetadata, +) + +DATAHUB_DATA_TYPE_MAPPING = { + "boolean": schema_classes.BooleanTypeClass(), + "tinyint": schema_classes.NumberTypeClass(), + "smallint": schema_classes.NumberTypeClass(), + "int": schema_classes.NumberTypeClass(), + "integer": schema_classes.NumberTypeClass(), + "bigint": schema_classes.NumberTypeClass(), + "double": schema_classes.NumberTypeClass(), + "float": schema_classes.NumberTypeClass(), + "decimal": schema_classes.NumberTypeClass(), + "char": schema_classes.StringTypeClass(), + "varchar": schema_classes.StringTypeClass(), + "string": schema_classes.StringTypeClass(), + "date": schema_classes.DateTypeClass(), + "timestamp": schema_classes.TimeTypeClass(), +} + + +class DataHubCatalogueClient(BaseCatalogueClient): + """Client for pushing metadata to the DataHub catalogue. + + Tables in the DataHub catalogue are arranged into the following hierarchy: + DataPlatform -> Dataset + + This client uses the General Metadata Service (GMS, https://datahubproject.io/docs/what/gms/) + of DataHub to create and update metadata within DataHub. This is implemented in the + python SDK as the 'python emitter' - https://datahubproject.io/docs/metadata-ingestion/as-a-library. + + If there is a problem communicating with the catalogue, methods will raise an + instance of CatalogueError. + """ + + def __init__(self, jwt_token, api_url: str, graph=None): + """Create a connection to the DataHub GMS endpoint for class methods to use. + + Args: + jwt_token: client token for interacting with the provided DataHub instance. + api_url (str, optional): GMS endpoint for the DataHub instance for the client object. + """ + if api_url.endswith("/"): + api_url = api_url[:-1] + if api_url.endswith("/api/gms") or api_url.endswith(":8080"): + self.gms_endpoint = api_url + elif api_url.endswith("/api"): + self.gms_endpoint = api_url + "/gms" + else: + raise CatalogueError("api_url is incorrectly formatted") + + self.server_config = DatahubClientConfig( + server=self.gms_endpoint, token=jwt_token + ) + self.graph = graph or DataHubGraph(self.server_config) + + def upsert_database_service(self, platform: str = "glue", *args, **kwargs) -> str: + """ + Define a DataHub 'Data Platform'. This is a type of connection, e.g. 'hive' or 'glue'. + + Returns the fully qualified name of the metadata object in the catalogue. + """ + + raise NotImplementedError + + def upsert_database( + self, metadata: CatalogueMetadata | DataProductMetadata, location: DataLocation + ) -> str: + """ + Define a database. Not implemented for DataHub, which uses Data Platforms + Datasets only. + """ + raise NotImplementedError + + def upsert_schema( + self, metadata: DataProductMetadata, location: DataLocation + ) -> str: + """ + Define a database. Not implemented for DataHub, which uses Data Platforms + Datasets only. + """ + raise NotImplementedError + + def create_domain( + self, domain: str, description: str = "", parentDomain: str | None = None + ) -> str: + """Create a Domain, a logical collection of Data assets (Data Products). + + Args: + metadata (DataProductMetadata): _description_ + """ + domain_properties = DomainPropertiesClass( + name=domain, description=description, parentDomain=parentDomain + ) + + domain_urn = mce_builder.make_domain_urn(domain=domain) + + metadata_event = MetadataChangeProposalWrapper( + entityType="domain", + changeType=ChangeTypeClass.UPSERT, + entityUrn=domain_urn, + aspect=domain_properties, + ) + self.graph.emit(metadata_event) + + return domain_urn + + def upsert_data_product(self, metadata: DataProductMetadata): + """ + Define a data product. Must belong to a domain + """ + metadata_dict = dict(metadata.__dict__) + metadata_dict.pop("version") + metadata_dict.pop("owner") + metadata_dict.pop("tags") + + name = metadata_dict.pop("name") + description = metadata_dict.pop("description") + + data_product_urn = "urn:li:dataProduct:" + "".join(name.split()) + + if metadata.domain: + domain = metadata_dict.pop("domain") + domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) + + if domain_urn is None: + logger.info(f"creating new domain {domain} for {name}") + domain_urn = self.create_domain(domain=domain) + + data_product_domain = DomainsClass(domains=[domain_urn]) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataproduct", + changeType=ChangeTypeClass.UPSERT, + entityUrn=data_product_urn, + aspect=data_product_domain, + ) + self.graph.emit(metadata_event) + logger.info(f"Data Product {name} associated with domain {domain}") + + data_product_properties = DataProductPropertiesClass( + customProperties={key: str(val) for key, val in metadata_dict.items()}, + description=description, + name=name, + ) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataproduct", + changeType=ChangeTypeClass.UPSERT, + entityUrn=data_product_urn, + aspect=data_product_properties, + ) + self.graph.emit(metadata_event) + logger.info(f"Properties updated for Data Product {name} ") + + return data_product_urn + else: + raise ReferencedEntityMissing("Data Product must belong to a Domain") + + def upsert_table( + self, + metadata: TableMetadata, + location: DataLocation, + data_product_metadata: DataProductMetadata | None = None, + ) -> str: + """ + Define a table (a 'dataset' in DataHub parlance), a 'collection of data' + (https://datahubproject.io/docs/metadata-modeling/metadata-model#the-core-entities). + + There can be many tables per Data Product. + + Columns are expected to be a list of dicts in the format + {"name": "column1", "type": "string", "description": "just an example"} + + This method creates a schemaMetadata aspect object from the metadata object + (https://datahubproject.io/docs/generated/metamodel/entities/dataset/#schemametadata) + together with generating a unique reference name (URN) for the dataset used by DataHub. + These are then emitted to the rest.li api as a dataset creation/update event proposal. + + If tags are present in the metadata object, a second request is made to update the dataset + with these tags as a separate aspect. + + Args: + metadata (TableMetadata): metadata object. + platform (str, optional): DataHub data platform type. Defaults to "glue". + version (int, optional): Defaults to 1. + + Returns: + dataset_urn: the dataset URN + """ + if location.fully_qualified_name: + name = f"{location.fully_qualified_name}.{metadata.name}" + else: + name = metadata.name + + dataset_urn = mce_builder.make_dataset_urn( + platform=location.platform_id, name=name, env="PROD" + ) + + dataset_properties = DatasetPropertiesClass( + description=metadata.description, + # customProperties={"dpia_required": "yes"}, + ) + + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=dataset_properties, + ) + self.graph.emit(metadata_event) + + dataset_schema_properties = SchemaMetadataClass( + schemaName=metadata.name, + platform=make_data_platform_urn(platform=location.platform_id), + version=metadata.major_version, + hash="", + platformSchema=OtherSchemaClass(rawSchema=""), + fields=[ + SchemaFieldClass( + fieldPath=f"{column['name']}", + type=SchemaFieldDataTypeClass( + type=DATAHUB_DATA_TYPE_MAPPING[column["type"]] + ), + nativeDataType=column["type"], + description=column["description"], + ) + for column in metadata.column_details + ], + ) + + metadata_event = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=dataset_schema_properties, + ) + self.graph.emit(metadata_event) + + if metadata.tags: + tags_to_add = mce_builder.make_global_tag_aspect_with_tag_list( + tags=metadata.tags + ) + event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=tags_to_add, + ) + self.graph.emit(event) + + if data_product_metadata is not None: + data_product_urn = "urn:li:dataProduct:" + "".join( + data_product_metadata.name.split() + ) + data_product_exists = self.graph.exists(entity_urn=data_product_urn) + + if not data_product_exists: + data_product_urn = self.upsert_data_product( + metadata=data_product_metadata + ) + + data_product_existing_properties = self.graph.get_aspect( + entity_urn=data_product_urn, aspect_type=DataProductPropertiesClass + ) + + data_product_association = DataProductAssociationClass( + destinationUrn=dataset_urn, sourceUrn=data_product_urn + ) + + if ( + data_product_existing_properties is not None + and data_product_existing_properties.assets is not None + ): + assets = data_product_existing_properties.assets.append( + data_product_association + ) + else: + assets = [data_product_association] + data_product_properties = DataProductPropertiesClass(assets=assets) + + metadata_event = MetadataChangeProposalWrapper( + entityType="dataproduct", + changeType=ChangeTypeClass.UPSERT, + entityUrn=data_product_urn, + aspect=data_product_properties, + ) + self.graph.emit(metadata_event) + + return dataset_urn diff --git a/lib/datahub-client/data_platform_catalogue/client.py b/lib/datahub-client/data_platform_catalogue/client/openmetadata.py similarity index 74% rename from lib/datahub-client/data_platform_catalogue/client.py rename to lib/datahub-client/data_platform_catalogue/client/openmetadata.py index 634c2ad7..91ad4d46 100644 --- a/lib/datahub-client/data_platform_catalogue/client.py +++ b/lib/datahub-client/data_platform_catalogue/client/openmetadata.py @@ -1,7 +1,12 @@ import json -import logging from http import HTTPStatus +from data_platform_catalogue.client.base import ( + BaseCatalogueClient, + CatalogueError, + ReferencedEntityMissing, + logger, +) from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest from metadata.generated.schema.api.data.createDatabaseSchema import ( CreateDatabaseSchemaRequest, @@ -29,12 +34,14 @@ ) from metadata.ingestion.ometa.ometa_api import APIError, OpenMetadata -from .entities import CatalogueMetadata, DataProductMetadata, TableMetadata - -logger = logging.getLogger(__name__) - +from ..entities import ( + CatalogueMetadata, + DataLocation, + DataProductMetadata, + TableMetadata, +) -DATA_TYPE_MAPPING = { +OMD_DATA_TYPE_MAPPING = { "boolean": OpenMetadataDataType.BOOLEAN, "tinyint": OpenMetadataDataType.TINYINT, "smallint": OpenMetadataDataType.SMALLINT, @@ -52,22 +59,9 @@ } -class CatalogueError(Exception): - """ - Base class for all errors. - """ - - -class ReferencedEntityMissing(CatalogueError): - """ - A referenced entity (such as a user or tag) does not yet exist when - attempting to create a new metadata resource in the catalogue. - """ - - -class CatalogueClient: +class OpenMetadataCatalogueClient(BaseCatalogueClient): """ - Client for pushing metadata to the catalogue. + Client for pushing metadata to the OpenMetadata catalogue. Tables in the catalogue are arranged into the following hierarchy: DatabaseService -> Database -> Schema -> Table @@ -79,13 +73,13 @@ class CatalogueClient: def __init__( self, jwt_token, - api_uri: str = "https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api", + api_url: str, ): self.server_config = OpenMetadataConnection( - hostPort=api_uri, + hostPort=api_url, securityConfig=OpenMetadataJWTClientConfig(jwtToken=jwt_token), authProvider="openmetadata", - ) + ) # pyright: ignore[reportGeneralTypeIssues] self.metadata = OpenMetadata(self.server_config) def is_healthy(self) -> bool: @@ -94,7 +88,7 @@ def is_healthy(self) -> bool: """ return self.metadata.health_check() - def create_or_update_database_service( + def upsert_database_service( self, name: str = "data-platform", display_name: str = "Data platform" ) -> str: """ @@ -119,12 +113,14 @@ def create_or_update_database_service( response = self.metadata.client.put( "/services/databaseServices", data=json.dumps(service) ) + if response is not None: + return response["fullyQualifiedName"] + else: + raise ReferencedEntityMissing - return response["fullyQualifiedName"] - - def create_or_update_database( - self, metadata: CatalogueMetadata | DataProductMetadata, service_fqn: str - ): + def upsert_database( + self, metadata: CatalogueMetadata | DataProductMetadata, location: DataLocation + ) -> str: """ Define a database. There should be one database per data product. @@ -133,12 +129,16 @@ def create_or_update_database( name=metadata.name, description=metadata.description, tags=self._generate_tags(metadata.tags), - service=service_fqn, - owner=EntityReference(id=metadata.owner, type="user"), + service=location.fully_qualified_name, + owner=EntityReference( + id=metadata.owner, type="user" + ), # pyright: ignore[reportGeneralTypeIssues] ) - return self._create_or_update_entity(create_db) + return self._upsert_entity(create_db) - def create_or_update_schema(self, metadata: DataProductMetadata, database_fqn: str): + def upsert_schema( + self, metadata: DataProductMetadata, location: DataLocation + ) -> str: """ Define a database schema. There should be one schema per data product and for now flexibility is retained @@ -149,14 +149,22 @@ def create_or_update_schema(self, metadata: DataProductMetadata, database_fqn: s create_schema = CreateDatabaseSchemaRequest( name=metadata.name, description=metadata.description, - owner=EntityReference(id=metadata.owner, type="user"), + owner=EntityReference( + id=metadata.owner, type="user" + ), # pyright: ignore[reportGeneralTypeIssues] tags=self._generate_tags(metadata.tags), retentionPeriod=self._generate_duration(metadata.retention_period_in_days), - database=database_fqn, + database=location.fully_qualified_name, ) - return self._create_or_update_entity(create_schema) + return self._upsert_entity(create_schema) - def create_or_update_table(self, metadata: TableMetadata, schema_fqn: str): + def upsert_table( + self, + metadata: TableMetadata, + location: DataLocation, + *args, + **kwargs, + ) -> str: """ Define a table. There can be many tables per data product. @@ -166,9 +174,10 @@ def create_or_update_table(self, metadata: TableMetadata, schema_fqn: str): columns = [ Column( name=column["name"], - dataType=DATA_TYPE_MAPPING[column["type"]], + dataType=OMD_DATA_TYPE_MAPPING[column["type"]], description=column["description"], - ) + ) # pyright: ignore[reportGeneralTypeIssues] + # pyright is ignoring field(None,x) for column in metadata.column_details ] create_table = CreateTableRequest( @@ -176,12 +185,12 @@ def create_or_update_table(self, metadata: TableMetadata, schema_fqn: str): description=metadata.description, retentionPeriod=self._generate_duration(metadata.retention_period_in_days), tags=self._generate_tags(metadata.tags), - databaseSchema=schema_fqn, + databaseSchema=location.fully_qualified_name, columns=columns, - ) - return self._create_or_update_entity(create_table) + ) # pyright: ignore[reportGeneralTypeIssues] + return self._upsert_entity(create_table) - def _create_or_update_entity(self, data) -> str: + def _upsert_entity(self, data) -> str: logger.info(f"Creating {data.json()}") try: @@ -201,42 +210,22 @@ def get_user_id(self, user_email: str): returns the user id from openmetadata when given a user's email """ username = user_email.split("@")[0] - user_id = self.metadata.get_by_name(entity=User, fqn=username).id - - return user_id - - def delete_database_service(self, fqn: str): - """ - Delete a database service. - """ - raise NotImplementedError - - def delete_database(self, fqn: str): - """ - Delete a database. - """ - raise NotImplementedError - - def delete_schema(self, fqn: str): - """ - Delete a schema - """ - raise NotImplementedError - - def delete_table(self, fqn: str): - """ - Delete a table. - """ - raise NotImplementedError + user = self.metadata.get_by_name(entity=User, fqn=username) + if user is not None: + return user.id + else: + raise ReferencedEntityMissing def _generate_tags(self, tags: list[str]): + # TODO? update using the sdk logic: + # https://docs.open-metadata.org/v1.2.x/sdk/python/ingestion/tags#6-creating-the-classification return [ TagLabel( tagFQN=tag, labelType=LabelType.Automated, source=TagSource.Classification, state=State.Confirmed, - ) + ) # pyright: ignore[reportGeneralTypeIssues] for tag in tags ] diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index 74b953c0..a758f3d2 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -10,6 +10,18 @@ class CatalogueMetadata: tags: list[str] = field(default_factory=list) +@dataclass +class DataLocation: + """ + A representation of where the data can be found + (in our case, glue/athena) + """ + + fully_qualified_name: str + platform_type: str = "glue" + platform_id: str = "glue" + + @dataclass class DataProductMetadata: name: str @@ -55,6 +67,7 @@ class TableMetadata: column_details: list retention_period_in_days: int | None tags: list[str] = field(default_factory=list) + major_version: int = 1 @staticmethod def from_data_product_schema_dict( diff --git a/lib/datahub-client/diagram.png b/lib/datahub-client/diagram.png deleted file mode 100644 index 827fdceb8889473f89075b14591b46a6dfcadbc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235822 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

tQISOe^FkV^ z!NU@~5HD9U5s=ocyZmIQK1SnBx?nAno}yvn{sl_SDYEP|h+K zlPp3$HH}u#=U$lcuwB4l&6+3;LNQxKayFYWmyzZ86>iG($jaP#s=L5Cfx#<#QqWd( zFfdn$iNYwK`=htsny-E5(5477;JH-vNGhf>CISkPZ$e*j8;0@a+%JyQcnE-Ro#&#~ zk8|Hvd^VhC*H)<(TzS;|C!y7Z88p)5P6BM8c|vO1@BhnBLBQDb<|b>-5aqb-B%%ke z%jqT*Ts@Vv82+r0Y9tLa ziT4Gk+~BgejAj;PfvJHiraEK`b#CW3X!1}uSKW|SJ@$9`NIT)?!bL6|(Y^+AMZ5^c zuawDOXIL)x@*@>aX+EQ<*3JGI^|sc)`;#@78B2fpoLkr1QH{9Abkk!gU%P!w@{JTiG#FlAY|~>dcjvF~{L6Y$RxTZq)p}NBc;cf|!vjvZ#1|+FtC< zxasvZ-J&$R8w;-R`VOC**u^89S?~+WqWHR8JDuW=B!>^&pXwLin!cskP*1Lo#FXc? zSc)3u&&GH2f@ze+uZx!?KV*rXQ}6&!`GjTA;LRt?)KC4CigxK#8d69>0Pr|+S-LK~ zDP2ci$FJ`WRi5HdSl8&MsRW68M0%fW7|UKgm_r*9%MQomDaY|ONwg6d1Vsrkp9O8_ z|6y|Jd6RBZoYe$KLgYuo%8| z8styR2Q$zUDP_B!DDWVi#un(9#hXi+h+0hNNj>!V+`nYcE81}* zfJr&}ik0mq6<68HL{bfkq{x$bvHiz(EhxR+JGGV?-ueodwM`eAI}qoYD@xIM(Z3=a zXQJXdOtj#xFDPH-EtA8nxyVOlaZ+2kEC5_?XY0NnZb^Ue0@{v)3?c>Rfa1F=QEU4xZ;afC)90unxj z&DsMhUd(TvH{*nZ94`VH0er=&@)TJX_G9SYOBSpSa6AD^(2*wg_`dgZ817?K7%pRS zmdPVCbSYyuHB7?RoU%N{{U3hvCBl&>eX&dfgCqmj>*@w|)>>f-)r=`zF;zgEi!xO? zL8M0!|Fbxm3oy`sV>J&~p?I8}B6rg>(AKL{X3^B4TXQ(Y*QJg*U5l}4W1RwHeSsy6 zn8s;W2SiWS=5cm%*Wx<6c6a9`)o}&^l5B7)GJQWIw&rEz46H%Z%c+(p?m0;YAFIXv z9@pT1iMPdlJ95az`85Zn&zEVX#}dECh6PDOel8ZtC*33)GkI?!Tl+;|_K7wDI#pq+ z#(o100Gz&a$2XAV?^Jjx^X7#Nr7E_Jb}p)xzYEQbYiYW5^^ zIIrAlT$5`eblc?^)I>;RtcMSDIIV&nLT?pc6j@?nVSgE#^a4i$c*Z;Y&k_YOYA z)Lr8QB5UGMz7vpsSe5{4*Yxt}kQoSXgpT{21a3oh(_%u5QHuKjBa*;V$uwRBK8ajD z6R68|^^NQN1@rQtNo$+&h_TU^wweK%@b=T`k(zrIYO&{EkrjFiR0$A@Y@ESd&9JgV zm<27=e8Du8?&bB1{kEcWsJJ0T_eE@>&RvtLR!|J-iu1{LhHl&olA62@FyCLz0n_sn zJkt_+=590XK{*llt_6(&jJckrF>ie`iMJZHBd;(QQa>;IYHr;ntrLV^Z-+nmW@$*J zh?`GWw8U@fPbUJbus*%|r4Uy=k1wK{a(GL&sd#rVkI_NjH}WL11TM?}aK7DNCAN*1 zp>lMM*mByA8C&ia^~?_{bm}17v*&I&o!P(uo>WrfI^yG3&$O4i%qk2GSG3#SQ=c}$ z=Mb|r6gIik3_@ck%)-n?gxYMt%bM$y_*|me6=%HX^ok*ZYhv_?7iiph%zS2Npav~! z65lM9M2PP=@11w9z!eJ^C6^Fkwo(DG4)XJ;WsNKJigbH(f>QWI>M5DF{uGA3(p;{U z9I&mWfMp%(@>(|3wx9QF!bw~v{PhYCel`F;)%g%-?C+)_O<2$)9;L71Cr|Pd7qN?M zXQLm#Jc(d}D8wbtnyoQTf7-u-oo5>xi%P2Xa66BU)BI*2MD@^6Z~!A{@vG{V z&d!rem??CZWw~!NoaZQ;FpdQqL1;=O-k?P{JKCp%m}WV8^Y{D69FrCRvX5ZaYgFjGEn)-D z<%aNDBAUqr_)?H4qB`|xvGF#oG?pnhR$jzAm1f?GAGRXl0(1#q%m;;K|4sWlnSkt$ zyc4w91cLXQRY=mo>bGTm?!sxmeDt>?(7#=K6?>ZeEp6<BhEO$41Dd&FrJ!7n z!p(2pbh~;RTIRIabVvwgc7vaIMQZ5XEb7gJR@xh7 z{GLG;)<2QM1)y3{f1?HFF?V{zX0HhQQhc|@Lw~chd6pb#?4Sj0 z`tS+&+^`}>l1C-kD$NK*i!l$gp2?zWxnimOQ#+e>dWOYeR?1k^ys*ot&vW22kwf?K zSe-7mU`Jx2PS|zvYmx0_ga1vojTDP9M_(4$wqUM(H?%U$VQqm^MsPPl3DyVGBAOmiMi}ijTA|{8vA9OjDY7m9ygRD`xzYNP%f3%v;BhAH1pnc-E7a5Ey-(TW z>t-tQoF?T2zpz|UrMqSniC8J+}#4T@N z{iM%23u%2Z!>h<=7DYN3zqIEY_GI{}M?*cYkkG+B^8Bx6;%&IYATCoXJtib^jvShf zg0?JI7YPZjnqnS(kAem3#**|H_+o!DKjD(h%%lp_Y0A~>@}o)Y^tFlTyG}FqL5}}I z=tRJCE|($ojS2EqGZ&M~P1aF{>lFUoKW8cO97w+7$&qL4`0L=X%La!pl`HUC3m6ly zBdIWmgj81^OkPLZQ^SBDakp7>E;T69h~d&cB})h|Q(WA$ja}YyY4~Vhlgpky*g*E* zC9fepqRNN&Km^V#BoT0}@mEkhl6G1z5Y$h6--U#V5 z)#;rF;3TjRJ?|Ua7XLeabYd?5YEgwP1BZ3Rd3q?ZtMq}cZRmMFWB0UnA>^>hM%PiH zX3-~1!II%1GbXBWy$cj4?SY8YH#gx$T)sbxqibt3vbEHxxvx-LP~S~TPi5thpg{sO zp|&J}jDLWJK0?+V$45a{!i)ZXQ^KLJCoto(3`k2CnV%gJy5cXWZ>P5|WrDWDBFnNM z@{1P>WRr|MSh(UKR05hTx|=~Udx1XU)2088gh-K4XTHvS<99^7#;QJKaN#w-ogR1o zv-}&0BH$WF-3j*nx2NF%oU18VtimR`Foq>P(!kUDGj52Pj#W{~mIxmgTJ6;;!`r>d z%OiAu3QlQh7DISfC_siw$GR9g5BlX$T66n@>6mBgcDT5~m($WPoLQ_*@_>aQIe$JA z_3V#UKokP`C+Rr9InL2Z7T@b-v9^6tZS|e}32Y-ZR++vkcXLGwMf|aS>%2s7AzrAT z-M$BUunUPT@&M2{3Zu)=cpMa_@cO>J$F%bcfe~D01YE2fD_{}Wdz$iI+Qj(RZ3rLn z+;Ei$v*d^kv3k~UwAj^H@n4?yufSMl>Cih@tKQ~76z0|gasDF-`fYc_=OQUs ze~q%Eazib+@;LG@z}UCw%i4&krS3RCPNE6BV&u0!`JP;i_`YHY2*j2J#8sKPly$zZ zeE)~MWC8@YWk_8U=NA`8N9b~x-8Vf;n2kF1->)9J#{7Nn|FudLm*pzlHVF@c==k#C zGQXz!QY}c9qIV+pdokWNlmXR{69SyeVqgp|hAu8<9Nyv*$-KSN&ZFcc-*;0qh9^nh zT6x}dG3|@>;KJ|ngXH%M{r|=d)D){lNvDnn`HHsLV%=oCFEXOVm2bJqr`Z}H5N)aG zP^5{SWjYB!Umdr=T9nUKrE8GW1rBTV?NiWP)jRY`^kOl8$?B*{6(*N%R{xwD-QpW2oxiZpqh77SxL8^ITCv1{UvL<}+%EjNKc&CT?x&Sv z@Y}(95MWJ95}2Kw45QNglb?L)o9PYy(Yo*#&X8Y2&go#GCH;56@`$OYGo_;v~&j{^%nC z^y(3=^z<=~axdcyp?fo$LP+Md`gXN zY`_Hb5Kn%Q6( zNT!Rr7W8b@fW<2y}Z-Wsm_rr>f)e&fBz&eIxa zm6Cr`ofD#wMyxN}iEzJPYfp)4)n)oyJdwK%^=L?%!Doedua-505Yc-Jdy8$$fXvPI z$D3(thb#I8j`i1SgVmQCj%cKAmmmVaChE;+*UvJtIo{%RLQ746F)Fm#XN`heso9O% z4v|C;_usZmTIF|CW2z_3S=owOLZxr`jdqlPRwiQG-VMV)u(0~z?Gjy2CKv9a$q z4Bxh}KL}_U@4ugiM|NZQ3p+9#&lbstB&%%RO}dZT`@QJ-A{}OWR|(%Qn_k~Hbxc4w zyvk%L=9c^RKYdgin9ne0Up~`NtD1aG$t+HK&MRyuErwBk zFSqG?DYMc|WY=e8uA6RO&MPoAa#Ls-*Tb}1mol)ov#E}ep4lbW*RB{wb7vF&$>HHH z&hM2gxz7)r?UkKRc1TX_qgb-<-4_SsYJND z5TeJ@A}`n`Faqh@MAU%0+9w_$&^m5UgVn|=!tB*QXDC!$)3xsUY9*_HQDE3fpWyPj71LhzS3dQ(<>uLybM z_E83}_-r<$=K3MlJTpAD22(^OCf>zOgEzZL9{WU+TQLn{JE2-LdcK_cc=&d zkJI6%PLw##&hf=X-ls#6hf!FJO}Z(Ohj!tI*PCS)9F%9TmwMJ|eRV~}Xl9+(uQ8&} zRsx`Xbx7p0QK6bM)|KS>{U5w3LC~K^d{KZO-2`$Kn<(R5s3o97iu}sX-=*oa#k7rE ztWjQ&w;nJJlVx}0`tZ;vDkfSA9094h`*diKXru7N0~peD7zR|xc6vu88z!=ssP$2x7QqRDz`YC^t3>R61TB5&d|pVLfiO(r%5n+>0a;G?4&vKZ{eg!w z#xpm8d4?)lK_bQ9gT6#j%n)P@VIb(IX8;+|L+E=T`QFj5uZLZ688wO*gNj6EfU_?G zXHj!zi>Es5CP*YZK&)j)`#Ap%@2=OaiKbx|tL9|p_lAmQtC05H-#6w5;{+=5V0G(C zrl)s6?*{o-1OcC`;0*uyb7<9bn3c`Dw&+=hsqf(RKLdCw3+G{pEn)!%@9CUrKF`Eu zg3U^!hu`HvJqd-)gl;bjUP$308pU*=6+_W*LYcPTu2GnXXXKnV&aMdm=okFGX zeo0nB&MduF-Uh&$x_2QsN2mzad^@fkpQ$F&WW9?FHgFKZO>S&1diJ&(y-e%@D$S{< zo!!RoA#t6`=ogZ{rw8}{fDoK`z;EhS^RZ;}#6?7pPt-5VL9=8oVj5k!P?2zQ2IV*5 z_aZZTWh#G8+^a6Gw@8kNrcCJ_5sJA9wRW(<_JmP|CB)IMp~<0rjE0n9X+#<|M{*`< zrn2NzWzFh%;enYs-?n3&P3&H2IP%fY#ql=X{ZI=+B-ph?-RG1ZFli&<5`_`-?#sy-GZKtUe&6e zcQu{1d_RB>?GGJJx3w~19+xxsGGXo|uo}i7S$<8?XLX!PGFliSJAJmc_jYOBC2k4H z3B+kQ=qJ`NGw!8!>7sC)fsF_W5vN>4n^8%%ef0`*Gl;M|4l!%^e_^Ul{>Td=Q*j z@c97?I}spI?{VhXp=S)UB8g2w~sRzWab0BYF!o7X@4XaaD0+;g3yqoOPyC}E$>=h+F3O!Kc~lWl2!)ztGm;v2 z62?IDI3N=I4&H_RX1pE8j==7r2A7myuKP-&&(uJtn9@3DF?yd<6@QS_Rz$|gP^7)C zipaUSwoIq@(X5o7V->ezFD!ek`n}`^7igbMr68m^&2Im~^B>Wn@ox%aIALW&?PYMS zJw>==O86e>DgVBY9=w&PinJjB+(F6v zH)24Zki@Bf$jaO+5-gJw``SR3>^AQAU@8@>;!i|SY|*?@ zHcQIyUVHj2yCZI1AR!plC|Ej$W|*z}-$SmjbPbMSsBkkCttH|g36AU_5`VhUVsr7; zM3^~3z{VG5+#rAGQLR57zDK7cG|zFocoA4)p52^SHlq@JHk@!Y6lxA*Fa#Sg-@%f zuZ1jX~Z?R2) zUj~)QN$^@=1}9Bm(r6YaA3mo1m{H(87;o&M^TR;keR*#`W9!wv!j_ za!D$&+00IXE0#2la@dVG&R9^Lk-I~z8ET*16p@R!h0GJfljEtN%gYU5aH~-Z=RcmF z2mLb}jF49Iz13I%AG8%0VGl&h<*^1YbE(osbE_hhz2}9`#V$b%PmZJqqsf(m#{^@qrY5859S*$cy;i05V zuf#F$7%lf$=W2|2lDd6^Fu{K`4u`O_KQWlLdG7_e*x<98p(G&P3`wVIc$k^cJrUNnw z5)~{9TW`wllU$>>+}ECescsk})WrMWGFJq~+hw!8yf9lmTRgDC!=|jH|e9a=FoT&w1 zG8OCS;B?BMf0I34eniNsCnw6W?+KCzW365R$WpYk`{d@RNRyk=@*hguNyde!r>-nH{d!iOBwN8+(1 zL#ilRE*J11kW7TW>wTgc#I!tY-1GY6G|wSZ@amyOelqh~01hcrIyLekwU+#AgxRJ1 zYB@kXv*CAkQ}-^UEN`#O_Nfzu&wno~fU7a-yQY@r^sdjq3>uEqOmb!F*W=iIvE-~! zzCDa@kXhlvCi_ElgZ@!sN6wi~Ve*Ewcda!C;+u+1Y)TX8wQBnvXiTDMV)2)|{i-^{ zd;>2Bct7aIFz~0gu>R}w<%DgEV`Y7%9q>tum{pT4J3?axYdXZ!hLpZqRK}yv@4p7F zVH7wkv}0JZ*Id?q+n+yy1U1KZ7+3$0&f=Whzolm*SqXdlL0cQ(aw*dMi*v zwRcQfl{)U{BCIdI5LvLPR^f)qG9K9TII&4Ri|0icQ$>F+S$5eskLtcv!!~;SG9678 zP0(uH$(oBXh#xQ8ris`~+P?U@FfRE@U2aeK^ma}{oq*pzB|UTqBNoJv-~HZ3T&Omd z(mE7HaU?}M4DWXuCpUaWW-)y!kA()j$d(w(f9NZ_7o{%8`cW*wK=KJ)H#val395k| z2|i>$Wm}RQ?TSYZg(_r9CVvx&Xqfo+l2{nOwW{W{r>AX z>?AEltL8l#oQ-cg5|_=9?U)riV}Bcz3BJz3VCKKjt8(8R_a2=P42SEs0FHZ{f~t#m0~A zO>b{xP9u73@K@QCT1LJNnO>`9obH?Yo@Xb9R7_f;EU{wuaDN^z%zAo5)c9JIa(X}) z)Voxk$?cJmc!d|ph>V6iA?csjeeUM!z|WSdTD8sb`wcZ($I2Nr|2|B^HON$}cXS?( zkom9WJFJSO#viXqo(?){2)FrCp0|6C9PRZ}8j}fixH8z;Dp&Ef>lB0f62}u$rjpc7 z(_5}in2M~;VHp`M8zU}Pl4a)-+RXbtsuEj3VP%toVm>J}V`rn)1neDe({JyCpPojP zbDj1bX)u{JG;u~UVF941xs&}Owz8XGLXql9y*3jt^qigPDXZaCl#8(i$FcShKb7vN z&hV&Y8eg@-q^M0DvAcdVCMzK%_IQnE$)HK9?bj^;E( zqIi8|TFLoKH=Nl(jR8_mp)cV1RQUPO25BDw=JdnR@%`x2OLhebc!w%RR! zyDT2LYnouNHRtJv<%JN`34K*VY~kO{3{94?4)^Xsd?IzMh`?aQ`!>>IUD&UzIABRDsxhoA zIb2~b;Cehb=sy6uKbyF|W=d_o{p@$0;On%r{>5{D#{|eXY*#)lQ|!07*;=V{-fEMw ze<(A0K}xVVr5~1a3el|;cJ_GgkOq%qoNqCu7^KtsdwYFZ%&?l8n_ug$PFe|omcc;2 zqd`z2HuU&RtEE)h(;9ZR*5Nd@(Zb8<@%lwJH#b*Y^!bRY1$2-Bf|RpVQPa`_Oic2s zET0#u{b-C@K*_nEUa{vU1+l>VMcq7v7VwsSqPoR7k;)^6tA?hZakvS%9WQx_h-}U& zSCF0IAx&fD-WZsb zkF???jmbguiq!mRxeT9%RHNE{0zOOWIq7G_r@O*cL&YhOK|7;d1EOKhs?y@!wiPy) zr_tATRKk^!lZw4oTl0P9<7o-&hwMys=$o>f#Qi#xsZ9PVzV^J_4NECf@a!bw1@eG~ zS(=}QxT6XhL+&(idwyrvuwX*38+#1E8@esEGd&*i zE7#(aaYmr47_j@j*n);1OLor1Y$jj2&gaglW&b8+7+f+n@8!bQ_uI9Ssy^Rj+J`l@ zOPu%D3MJCa$T!haF)$Yz&o0De*-)Y1qR(r$PCk~*l2B_pRJsB)hdHb#+M41jX;?6M zYDDNTiBe^qYt!Y6VGlgk1l2sC55di`arEudT#OY^Keg*8Ww?DE{wl6F%#@pyz zI36Qo%;9s9i<5)_3P;`1nMPuq6p{O!${(dV#UQQxD@-d<(8aW#VUvnyY;2+AvAa47 zSSX<5kn)Gc@3E8oB~WnZ^-<%{E&u1s61*KpXyiCosKJP=5}i zgBv9g2#N*cW$^XX7yC$__2>?kJ}+gh2wW7-d|JbR2L)6#NDEJw!Q7}FQ+-f8}-+lC}TFseKfcFoi+9ni6{RP zP(IG@8rm6bOQ`n;exbcFx23Ae@Ey7-2*UFLfpO)${iKs$3{1AWi3tRp5-W5&0QYBo z-CK+0P?>VADLbQXy;(mEG7Zbd|BXxsW;88~zKhw#A1{D*djK15%icrLN{O#8v;bo6 ze(tLo+f$?QT^F}(gx}bB&7$aZ#jK?LCQBu|g+2SFGdS_Q*DVNaucyC7WO!)9cL&2dB4NJ`=6Svvs_FML)7r(a*9@3-}wo6d>T~l^4AHMY&aEChzFEI1dK`p$XqnXe6 zlKz=a=#EwFHC^i7Sq!dv5Ug&_jVE+95&T>4k@B zn1gW`&oz`CFKdBB#mLYq|DGwILk-B*$p1l{XgAV!eU%96mtcT@^9h)mq>eEFSbj}; zcKMd!GAe*rS33J@knaO%o5OP*q%&((6d-|J&7=f%WV+tYv8Hncxo z4;6~@K4lJJi{_#?hSam_(kYbRbIV>CHJT_QzwHr||*BCyfHZ4D1az3zO@ zXLk`b!E*H28|#bS&-rCh1}DfS)$H~BL7H%4C@kRB0qSvPx9PJ{ z-G<5QqxOS2q%!BLV{c-d!}d$p5b6eH#$^NVr--ypn8VXsA6%RTXI(GT$WQ}c_FH8b z@bcX?Zfe?p)-cO=Pf`kh)C&s>;jn*H`#cD3sZvT<-zz5%ace>`!YN4>l43v5TZt#^ z?EwEKuQ}gvF%RORa?ktBj(?54U-vzWD9{tkP?|d9iG4K96f&&-MIc(ww{$OC>3AqoAh+l}%9f5|3NK3t+1eebvheMG z2Ceg1Z!C?Ml!gosAQ29E(6PlLU<{v3tFj*zHj&pgQH`CQZRQO+!}LmoVWX{X&Dn-1 zaKw7Gj`Q;uCuTD<~45;`gds+1) zU^Q!3kQ9IWJe{2pVtPcAo*dw_Je`MyM?_~1GFJ=F;@aT8+f~1mvOXr;2RIxv*#Uu; zZn6sqP^#<$KV=GNl@t@&?W0nfM{26+9v3actKDSf&$ymXD?S9|7;GL*x+KK5#cs%C zpneZiTmhfpA7OLg6R=YY9Gwn(N(u(ET-6@?zgutqRt(L2FYpbT{gK5`Z+t`voqqcE ztR-dKTQs(Cw`ottu|1#V&=*Skv>UxfEnsiaN z5-6wseWbB6D}CeeT_?}+(8r=fv3N)-Z;fW}v;O+y03@&V;S{|8Z!DUW6+L7Na=JL0 zCXDW9d%L?cSl?$h5Pob+!5i$J9KZgY<#M`pYQB9F$4U?bW(Jatc^=xw@oko$0~Za~ zKe`Qz2JVg9I=Q;O4MyovI9`#B&L?P%IZy4#WA)1MTN1wGoZ9qI_IMR#BtFMeQ~L({ z;w=vUQ_>Yp7JFL}P2=}W>ZkRg*J+Uljs9&!JX;Ui*~ZGHJo5Ly=W4mw^fo~!qSMsW zY{kFHgH$;(Rky2gA@FP?r0iU^L|#cEppst6u*-_WIBTPp)~F7OkBR60_{5m7-oN`m zkIKuOP9cs=7d2<|P8=6d=8Z0xyqi4Ht6|n5??7qG9ou-Cz1zCKfRYh`SLG}+pmG|) z*<|p!N_}GEEFIh?3NAME=)tQ)?r5t0M#_2R& zB)GJG?b>GLXe$)rzb`%zzVMa+%=Ry5-^J&Tt;6Ko0t-Z^>ei&MLVd>0l1~g&A|Y?# ziv=!+%YE|6_@<@f;)}>!t9%rXQSp7HjPHf@oj>poNe1HN7 zqNABgp!=3bI6i));9f}C+xoN$`v+3xxqn1u;+I%`Kb`c~tC=6>CWkS&T!ku|h+`>B zV>*`A7mkjmCMJJ{uiGefS}P1UeN2gk1w0B{%QY9P26}O|N-O$VD;~#OJS7D*T3MZB zTy%8<6*=`O*kX+qp3%cb?TwtQdId|*ohb3(o>1pXTvvDbj7`9vc(&azoq`>f{t*$n zMWv;>n{ZHv{k!g6B#Y8i9T>)|$+brvy0+gN5ojyE?REat3{o1Gv)Ona3}~57P);`X z50zvdznCA#j$<=wlx{e$?)c7dTVpDPB)tB5^W>ycTc;#y$Vp&yC`9MXt9ZH0039Q< z71;O^BXk}&IQUKTo2sXo)I~wQ;%8w}XQaywJbb$>Y`gbfwldhO&g84=34imJi$rZEjo6Isf|4mpTSqo$!QDG+{E^RdY|3`PJ5b{pw5$L*Z{A zyV}JM89cDKUE)og=$gdydDyKO0hp-f3E%hcsTt@|@#%Ya*sj(~yPa;*SX;xIP3N|Y z^B(^OYbOwXLU}AZYdQ=JraKPrgtgtP#o~Wxl$+@N!Qfpa{<`oBYV8b*k5aX+R!(u8 z|DTFx2a)y9Ld$z>7OSK?837Fa{OWJ93W?)?LuI|y|ISxd_O`_nS$Y;;zx}xCo*NSn zxSWPVwVV`EN8m$5+|?_#DTo(4|sDLJ^6x-{fcLHgC+-XHE= z@;J{DVK+Xz$!+TsGj{oo1X_^vg~>=Xo=IZb^iAije`WsqiN7vCZ3rIE?i8j|COV?W z4#=lz|5(e)*)V z20PDvMK)!e$2G1}`uZ}P(!_zZ+40$Fv!#sv;cxIEL*Gl40n2p|*=ds4t6yp{(mOFd zbXDLpNBxEf{JFK`)Q0=qEGxcfI96_DRXHttKL3=8xVj0?_!~%XF6fV5nFZPS`z&Jd zNn$|K9-VGz;~c*&w(VRWG6&Mfa#;-g$Gn9eiwmJ4CpTG{O?g5@1fcyWngA)X3G7jS zc(a*GN5=N1?exzX1nFi2Hk>S(n3$)ck_1m3)Z{oH-t^t(qY~a**#9##Gn3_f4d7a? zyW53(;~-kJ`gNOERNQ8wn3UL2BjZE!7##B{YP97#Kckim*r|Q%MgCX+`N+0R+f9bd)1}(z@|DXL7li8AhdtF9=lW72PK}a z!LE`qDV9!Op=Zgxvt%Te8%fmLm*-u&;O}n2Bff8ux|SwglBDg@Y4=MVZ^BMVsaYX= zy{RO=Z>LGjX}J+(mUnSA#=1pZx;Y9`H!)mVA>HZ;?oo0{J-QkS9ZTs-ECXAQ`OYb> z?YH<7ssp$B8sEy9=FA79l+HW@*p-yTL{#6=tJ&ZlPx!d+tq@27DQsq{< z1LJ=_{{TZ6gpt(+_4)VqhF_%$8J*vn-2zOKoa?QFhN>WU_~Ac&t~9_=+8Fn~P&5>n zOIF}hu)*}#fT|LtE^wd}tVK~6cdGH_jAN>cgWV8ySo&Z*=}?ohX^rc_%%{qe&)=Kd zh*{2*uj7U1{EXsJ{j4!Y2U3r5V`j3NVoC|2w_zF62+}y6mSq{3SrU_&zKg?hYZ!?# zj~7!l7t*#h7JomO$OwZsa`oWYC^%0a1&noD^5Z)8Vk5ulUt2o_Z=s$&-)BuPE-am} zrh&DOMpE=1HrQiJIX}Y3SGBSR#fL!VHrD?%+B=L08;Q`&=o>vq9A=E=f(^J|F?vxD zXHWd=>7YtieG~e_N!Kx0kFkD-Ow~f|5NM$$7aLWl@cJNa7fbz8)UJ3RR|+S{p;mol z>^7z@5mBrT63fEccD_J|`-?i?+j-e*B~p^1)-+`cU)1*lsykW-t|RJ}kC>_T{JJb{ zqpfMKsquw$YA=t)?=0!c;CNmnxAboD2Kx7)}YYhQE&uHI*)WO(H1=0 z1pY`e{`5y}&I-4NfdzdTiYt`U6rBY>9lfSWXM`Nd&9`fhY#=+&ow@xrN*TqbbKDMmt=}1=cx!;< zqGxn&Eu^$@hQn3dsv>^>2G_U#)BUwsuFh^xP^Wd@wB-xY0Va7t4U{p-GSmOh0*E{K zE%E91<;p+HvCGeWpg|)0;7@jBZeKzgQ(kvTy%`7Oo>5&JTrZ(JRnLx{oSkT}MDc&E zR21;o%=FXEVhorNp}C%QA8xnCjujnrsCfu++$m>g(~ekL%4x%hUa4m`f<+a#Gky&= z$~fDM{fn{nQsBi>o=Su!Dld90+BnV-qvTZk>AnB}|7$bb-e8vwAE*h%D>~ClpBnw+ z@-YVcD{8~U0fXrJ%NLIwj>7MX6v-^r*w58&T_rFm%^Shbj(!zK_usiiv?j7Lkku1~ zA6L$~JHRZy5085z7o^P*6;8BL`+ND3H9Hzj-QjR=p=;4Ti$c8n8$fWd>G6DL!y8o% zY>806+l>>ah0D^5oig()BNl3he86T~G33Oc>G@CZcGn|_4J5};_y|_^C*+m@Q)YcU z!VA`;f2mm-*vG>E1Z}r>bRA>Ke;Kv0+0Wj1mJu2gGP1Us+KO`<^pe#FDTq|r-yK$Xbhc-R;b1%%(b#?|;rS{H* za-fi()5vmK?aMxaSG(!12dWkDSA}z=HEg}-R7)e|$2Ujg*AdI+rr2`4X9F`WB^&Df zpE+D(Ju=(|iZtiGOfbmgU3*)eT@i|y1cydlIgL5?<0-bzX@PO2*^f$8Q~6Xf%M*Wh zZx0m8wwc?wJ2EnIJp-hDA_VyD!KK;2J)c*I*IVMfAFzf$`9Q!xH%V9G)gw4V{_8 zHNI&NbA+Yfq5WXqEm&3}X4f&MBIr#O%6!U!IZ|5C1r7j4xR*LBBd8k2B8W-+QTZ=7 zdYhs5z2M(x66C&bF09CV_h2o<4tSKji6vY;#6a7x8u*QVeA`(TXkl0}I0j>VnOwmA z# ze-6*%=Hw0C3A2ZO^ohA2y}MG2cFqZhf?K!T57Dw8lwp+L!OGW=reetnt{}q8_mNZn zqFwn30%?wA#_9gr!7IV96_TLTm3t9M5&z~NU-WcI+DjL9o<3oeulJ^Q}2(T7hsVPSWQAGT1(XEIC|%xwMjShBa>F1K4zCQG+a8T99EvD39OePjrk^Y$ zhK-Pon>J!K{Yx@>%qYYg|HQU#dIWC-;oLowboT7tjlsTI;$!2X&51KIWu@>}URf^B z{_|N3KHKGzOD=_5t&#I$F#BQ(Y zpBp5Jr=?>TNcKnCF8O|Qvb`f*p#YG!Xm>l zN%6u?ey_xznubk$04n)VY1hUNY3R8_>c9A~aA8fL_L-Rxsx4&%wXZW~uEl3^%+3*b z?~2RQ$F1j|e;!|RN5H$y zG;9d|U-ffLsU4w!bN!IAfY z;&iKUo0kKKP0|amOp4-k?6D_W9tN$|i@dU8VY6<{K4Lx8F=U+=x*&z4@4QlJP+9ZcR0!bFb7rzC!rhc8*#yy=< zF_9D&G8?N0E`GqJ)fg&oPP>30ZUx3?YO@*GX37K3b<%_Ay2>_L^m3Ez+0rJnE{K*f z)1Z9=o@6?uPhK4mhoKN(^wL*T3Xw%fiY_uhRU>`PyQz2Ri}+$9%-9|MwY zAF=ghKK}9L#ZPzynC;kdRNEGG=}l$f{CDN8H{TNOCU)I**Gg747AX;YI};*H)AG|7 z-$!!a^37w{V4hv~myxdbyIpBl;1AlIjfLPV_Dba&*nkF;CB`Sf)nE$DFT&z5P_wV# zj|a8;)-&e%voBYjA_-P;jh+wj)g)dBELP3L*LYl zx4Rtr;+vXnE{e1+0T(T*RXcEZKAL?j`MA?P;dqxv8+SUl{k}Uscz*09@cL`7%N<|; zx;*f}1M>9KPb&bYGXlEIUAJzXy!5YsVXafG+;RIIifN8LNB;#SxCdbOxdj0{+CPfJ z#`i250zV8g7%%t`>kecbM>@xn1G3qNoggkyJ|fL^yDWGCyPo~LPL_V;lRZ1zf@#|M@!I~e3|o#(Q&rL?OW$gi9qU_Fqe^>q&YC;x*ZZJn zXqmcNzB~Nj2U)nVjDsp^@n!Gf5r5>DzR?L_l|S&wE#XN^H~&FirVj~&{E)sKD^-@f z(I}7JUnjr)Hf$PS_X2rkNq&h>Z|`HL zOWrem$gB!yKE}~ToZx)UoHOLUd+w8IQ>V#kGf&gDAd7Fm{f_+XfB&D9mX*rI7hjC+ z=();26#c_!znMnc)CixK$0V5cF=g@Gmf3k^v3w5LH;sJl@c@Rjft_?nul)p`C~D>x zO4Hst&Py>F1ZDI#slCkF;9PMCh<9M`j7JS&FjxLrD9U~0D zWE8<5SES>_gkHuFj!|ZuX980`a0wRMi9GYmdU^ldW=YTX$hBXKm-6B^`S2~T#3wtM zm_;*laAPru$C0Pp+d{`Jr+iZnq`-A!O@}OexmjNSSA&e17$IlPjZqLD6CW!1qrxR7 zPAM3A_;m*s*U2;MYxoWU7$)FvsB4$)Yg?sYSF5zZcWMGQeHb*-Bco4>(0pW(z=L#{ zmq(Q^;#x6rXvSC8@^YWd`%4qt*|kava7zg4QdQj{1M)&;@R*1|p4E$5WGN;jGtP~a zanqu7(JMg@!TXAZK1oUokrAjL%Y=o5 zKssuPDJN4;(mAw1nVLZG&9wwNu|RYbUiJ~(SrF%lwaJe7$TlFqR>RHfTzq!B{f^sZ zU*SG1>{=+_{qA?+L-q$U>x@~@#7AknF}*v)V)LEveD6D|@uudWsi|4!y*^LwM?QK# zx%%pBwy|fMVH+}1^JLD_3WXb9^4Z5>pHN4K) zP+yN1nkytFc0!34U#yU5ykGY3Z;@AC9@_=TA1Yl}5y$QHSjnM375LE)2gwH?)?o)> zpZeoIKl53ws{UP(?3kuTkwASs!l^6Fn2Wy`u&x%5kMlA4LSfpzlz@jJ~jAEqf~ z;KK_bJz*vm|6~}OdjecSfF{V7eD2u{AGIiRAHGC1fL~Uh?M?X%$ik=fOg1HD!iMRT zlU^Ls5Az$@vg?Fla--%oIP&X(?I(vsvX8_TZ1Tt#jJ=*PX@Y#~+uu^Z-K(%5^UGhp zS#G}hX86RtL^3inu&G3xnw(hjd^VV$_H-LJY{aL$zsa-DK8Nc@Y~y>C+GGy=& z)Xtc)*k_5yo}M*J_h=k#ooqQQlauLsxKN~t6DJQ`Fv=!0X-qq^?4J13braf0&&p6Lf5Z&}uU z2*7Qg3|l4K?X5aC-qOaTmwP2JI{ExuDSxw2eAuKXJSsx`u#qkQcY*Gp=}GJKj$?T` z!djjX*?R-sWS*e=``;TR*M4C@m%3biRl5A-r~4HA@x~K>D$zfiXxOl5*|EcHK~BHY zrhl8ev=e{Vu`TCDK7aUQvEHEX>La0R%RZjRBjNX8a-fF0uP_>3M;5;~LC!j7y>KI; zRxFga+rqo^&YYPjkNkG1J1Tho36Vey-v3*+Y|(Mx!5{xvUVQOI9qVX@Lf_z5UwyTF z``h2fLSdM%enNOYq1dp4s2zyOd{+$~fey8vet@nfb(z)(4PI?cje?A1Y)F!olL;+> zb@AhjF3g0$fr46G9bItC*T(x!8cosl$H}H?j3yc2yS80k|7)XsxS&Ob3=NknZcUIO zV?45XrB_WnPQM_^(a;)NEzKRW6u|t&e>6xM0B1HnPZsUL=iG{R0L%_a9S|nh--!ir z*fneK_Evy+uPj;9D62~v#Dl>w1_+Yc+Ya=oaNyYJI1H_bXKgG{QK0XzZg^%*@ek%C zK5x6MTHGQFUTVhn<89cqB~+%J87b|sd8;gM#{>c{ea?uIX1v_re5q0X^h_n(=!uL< zj#W)K$N4a5c;oPrYiMng^_7hh7w(sglu(&@F*ld!ki9$FBsL*TV(@3>$w@~|O}iwa z&M6rlHPaxUWiSa@0@)E5!?aYj#Wj${j*YDn3C(d_ylD^q?%LEQA0aI@+-IGOUCTyB zV23!|fyRyXY68eE#)RQLxb<57AwWV)xOkA3aw0AIH2a&vwzo~sYXa1>&OA#}lT)#s z^&|2Q{G&ef(+B0b=blkb_!(!+k(}&o$RB>KQP;LMKen^3kcICql$ZbYvXoYo%AmY_ zx&3QjlP}(QgMxNegbObjx|eCoJ{Ctl{|r3xxo6!X_#!X9=w6n9N@ReQY*JjdJzoSwbH&#o+EX-zEJtuULy^Z{oA8A)SFfUy4 z21Y>>7bo}Ko75avme=K6N2Ds6|0IIsNUd1$kDG)aD3wB+vT0LToL z+(BWI05gkBn6D6gZd~3V#rxV}7K41v@K^!9G7=(W)QAWfIUK+fz%&~Ga`Y*Y5{ZR= zWXTSwwhCQ%2KeUn_BE~Y_6t}b3P3#P+-No97&WBl<5oMiL)efzC<n0 z#tCi3xwGc5Vfb1J;BLd=(zW?}I@Z~p{yaGj18#cJ={L&QR?C4wzGZls)0PkInUGCTclP`UH?$&zyT6}m=O7XdTlPtpQ zX7T&)%cgak@yQ)C9;#|N$Ux22)PikXH^Ize%{n;Rzer6iILPQ=q608eA}OVU;tUmb6g@2B?mQtPHMKd0zAI^?!l0Gm@@?g4>n&XkJt9|m+uWFTzz!_6w<%(+) zB!6^-f;zH^8sU$>o$_M6cu()Us?-MalKn8psIKzM*eMY*=kqbJS2rLz5*qDDcof@! zIz9XQdH}Q#bZCzZ4~dm?&WMwXE{m7cj4(_H;Gv}24^4Kcf#_qmNbmPdkbhLNL2%J}gU@#%3ZR=Tf|&EH!uCrz0u zU%l;CnQ_{wl97>tHaD+Z%Qy2`x1H+wgw;Tws(5w3cx%0Qmck@FJ5u6l`;FZ%yNllK z{Y2%0{MuhkZw77jbB%Crut0mtvT#q^2+x4_H{M5HLj!Gw{LY2mamX@>36Q&zK?J=P z+4mJ`@}GBaqZI#PvqVq84wXa3IA1qVCjU9_y7mT4;!)?Yc)X+7NpVl*NZqKa#^!PWa3Ms!!yf94GodtZ;uk7 zOzDxs?Qgorggr!NXGaMCdJ0EEw6EAu75R3s`UME386SCx0XGJaZwWDN9Ru^=>q;Fabd z(EzJc;r6caBQ)Wl$C)Z7@xmC5u5`b@*Udd-K zkCIGioEbO_bHSUoC9WwB@q*Zy&AL@~zqhVU23mrpr5orkCnUiNkNJ$yKG;EBO(@u$zvoDClB3OKJ0BGN| zrUmwxp^}yrri(dgO=0>1WJ3NZHZ~KFhZv9z+4<`P(r_>wK?oO!l2?SX!ZPkthQdh!W;ieD_X)eW))yMOY_ z*F~S7E0nM}p1c)pw&C_A=;b~?5T>dkt!)wfFhI;(wMMLcslX=CO9eVp>rydX5O_I;G8@n{RR$JRUMM?oq?tW@}8Y4IJSeZaK5(g6r& zduq@;ILDF6A_ia`#KnUoA}2;dVFqE_jp+&2o1h)9UR(@6-abh`Yaq6g$AU8U4b+48 zSr;BiOY6!yu>JAA;38?IgXgdVYcL4l<6x#!y|P@*P)MM0XRS1Ct}y^SK#0VGAt-^M z1cDMcwi2L&fj8m(gl-4ZFqYkW@4YasO;XP&S6+Fg79IrPeYAnTSyT8j4YU+#9&4EP#HXAFw9Em z%|nNl12|wT0TKZdHA`WAv=Vt?sE2vP3V8I`g@uMA#zx50GosZln!xn+zc)$)7AdN0 zJ|rj5gmnhAx#2QqQiN2&?z9!=9_49Ps0Mxl^=TL-B+4q z>js~Uo=C4G;gXi=kug)lB_SzJJ*&I}8}RBzEM$av$A*<5&_cHf*XPsxBn~#}1Z;ko zt+d1K$%f6%k^nCnIhaJy{H75T6Jnrnn8$D+SO39~HMeEw#Gwj6dnZ7(VY@>norWD2 zG1<@sp2(X3O?52n(;2GE{jv)a6y_TVzv?OAlhtOM4I3XJ*Fjd{m_WSzWV<9rz`rX@ zgBHHhEZbqqQ&k63C2y0{4#3T4f`5c|jJ5s7 z4_hpnG>|^yEN=noAfTFH1#6_%HP}2h*0O0>a;{tlrC@GqrIi;g+){8IZ92*WG|oJG zjtm-?kz2p|RhcnkhQ!8UktXYCDrJNV{%I=F zw4y@1`x{{Tgh?J2U&fD2QgCVH;*^czR1VA!IyP9@>SM)ET;{})Wk9kvxgI;PMxhP6 zY_`K>iv2At23mJ6T!hBm5sRj?xwSalV5!;!lRc(Eg82o`8Z4a5$eG@}AN`s(Ha z$xzU=K_?oxXx>sQ1%KQqbt@~xAJT>|NSJhuOE!Iq$_JX|AzgwI{wu~@tU3&?k4A?s|?Gqqtof*~k@pag;v zIMxyu-`O`e`v3I($pz3ZN_vNJ$j6c7(Pt0bFpB;K~ff28~`NTj9`rLTaQo3 zrvnj2N1NB3l;<2cIoR%phIR`~O1LQUB!Fg`W3bqAENWa0`)OW8aR)sDdkk{?tlI(` z=AF@Sw}%0=4huIUmxlp_!>%>FLq@=KWjJidE8!xKE)4VFT99Q?5!C{K+l0Jij#_5= zzGhh*=af$!|8apN!~yt!U~t8!LJK?xizRblbH08>i|pO$m-#S_S-Ub+CP4!|3vT_! zz7N-3(K!xTFxNt7@0G zpKp>_*r+F_!1j8uM>1eq!$qFS0Mtc0u-LQA505G|g@N5UOn^pD443hzVj(E%QdxI+5ccaa31|?PEG!Mm^Kb*ZggZOyKvNrA4*55IjH#HK4b@qi?emP$Xf}Lr z47m&HB++^1x^Dro_7Tw2(m~snM_}p>)G+tf?=>9=C>0*j(U_ZT>d_(O&d>6?hd`Xe zkKR`r4oF6XErWyu{n=(jzA)5*?||VWhs#&c`*|35y-~{C)%c zaz{wsSH?=z;5c;ySoPL^NjN1#5@u%_zW9#BKVMOk6#i|yR4px$^wV=CdQ^f$61Y3{ zf}NMv%~#wcjhn00j=XVut@tXv0PB897?LD;_fC+=JSPw8N?xJ|Ctv~)E8Y#w`1Xi3 zE{*U=w66j6_em)HsB8bwI)U$iFjb<#pP&SS5(rA*s7hcTcD`hI?9oTn-R`Zo-6rYj z>GCCj@}FZrj63hVGYG&BCUFZ2n1<&B-~`_vy#Jv*_0*r`!w)~gpjv}52%sz_S;kKq zFBe~Qv1+;U@~~+JOit{;&Ab)}@M!;R`5@0yoJ$-Tp_d$LIbN8flc4i}bi6|wbh^(NSKOXaUwO#Br|ZB2c{V@SQH5NTma*Am6ibQ>`3^wrq+1D2lY}~(J>8=`t7wp!ay8QERapJH zaR3cQ;Lpa>b)w(-dSM8nWK@7#!P9dOfo~hW<`58vLL1ux8y-6W<&i$p^$1Ya)@poU zQ|riyoe$_fCOR7ALeUmnOxXeZ=Wv+xV6!GXKQ@pTKZUAXvF7MgNK4w00HCdHun)#R zKd!k!PZ)S=#iABUi5g;kK1l>wTQFnt<6cBWINVbwEd_X z>q?*HoY)e~4#(ts^5jWapbHx$RD1QRHM&k@U*Uea{`%|ioaO1)6#_#w4+3y*hTSyf z!DlEOfHo?RE)9)r^j$)}o8dElb){4-ERyEZM*Tt%8ixtxrGq75avJ!8O?Fe8)Nij8 z->wE|r{Nd7uugmp0POIr5{~-i-Z%SiYY%KyKa(dB+0jzJwo;1!wHuZ2 z7*8Zx2YhLQ&3E~~_DcDpLW#>tl&q_VN({?pU^_z7U9xtcG;gSt&|Q9MECAmqyB(8h zN+ARag!X|rW4Cbv-$5PdcOJaI;-{uc?ZztQy91Mi<|1g$OS}>V0B$?K(y^I?$Djm) z5(rA*7)W5ypg}rr(TtKC#zkXH+_H5mlrT5IRPjlyTWbjd@czi%4uCw;+riJVi_wPl z8+1_SNHA*5Xu14~%Vo})b9B&+f$JFt&Bw9T(c)PUZI9i{=D@(fT^@-`Fuq}21~7nm z1Zj28$APh3wSEAcuvsnyXyw2}o8?3-k|aH9*^;3lKIigSDJf`^_h8)JSgg=o3(vN%bHW|%hL>p%9&qIpfw^0v##FvTE0`bWCXIjV*M9iCe> z0ymhz2G^;L>iKAD&ic{xCN0YYvy(728Ce69l$YU7aXCDY#6oku0~+O=)JVB>ZX#@I zqhvk&X>Z@!CMnQDGw3)Ea?Jq9Nzop;_?kHQ2NnQy_z4Hlt|*1+M`b(gu>G=sw_h@I z!esCWkHlAp$_{ud0e+Jv0QfoQMPYHIS-c9|1y_Ou0JNPp+J3qhtb$!Sz11W`*3^De z78Nj~`51oJ7ryC*_YQbx!Nz7I5@O_}$x(8}mlM=B{3GlfN*;1B$)V;xg8N^PebgT~ z(BcGE=LiFdIx8aWfneGC)(wn?T0I;@V7DS*O2X8;&_%u>m2e)J>GYmE_j92d$DoImM zb(?9LPSdfU6YP>!8)#AHOvC=gi7ri27(xMr>jIM?MI8>9)@YAWc&Tnt72HdF7;& zJQ7TY>0&?DozE4H`mg~bgGMOsg~1$xbtA|p@Mrx@T9{RU2c3w3F9qO`j{)M41Il4~ z^33#6QIYsk1nsNV59S%+(eSZcDu4d-llbbAB-cY59*4CdFg8)mvUhL2GyyQG7bJ9q z=q$)E4U>5AZOR9;s8>DAR0{sQNj1X}>F7LoU)1fdl$dNx7$>Gm^O_ndSiDoVE`?UQ zTqLDANojb}BPAjge)Q8}nidl-bsNj1cz>}ZM%l?q&hgsV_+R{ zUjI?Kl)hbnF9z_ebj1*f83W#+kq?2nO3T_>+4QFsvU=T0NvMmJ+_o&kH-L52@I;Bq zg6Rn+BuUVyN5Zb0z?(ta5T_xSaK}$ek?@z$cVMDMlb#lst+W(3L(86|?ZWi7yYW02 zf)WTyASi)jA%R4^fBfSg!@v7Jto=$yH;+zQ&$7f zy3!{t0QJBU2mqBBlG3n&MOFqDKEftE5?ZEJi?Oi`?3L-MBQHNp#=xzh8M;h~1VsTY zC_ov@ASVQn9N~Je9qyYXdSr42ii1ABxgZouNCmQcV9UN5T4^sd(nA1-bD`DWy&E1! z;2k0fnza>+y|M~BPbR@EWY+o7@YrJfc!$yk6*R+8!AYk^tF1FP2Wo}JuYn*OcHXr( zdSOO00H!ZFsNY8HNcrL0aDxcbl?iI{5)A+cumvGfj%*JC5iV$@1ezP+KOAN)WIO?y zY`Tgp0KbdjCUFBMICKSh3jCPY!u4T(VvGz*kAPO*FYjZw%q`gAGZvcnN${?cg+-wR z651V5jvgtHBMhv+k%B{r6E#FKUIS#qJ{Tw$sR_2}tG>3T)&ZdON8XOFJXHpCfW1?{y@*HsYMauG9uHdmH6HZ}s_15Bc$d(n__@v-o+ikTMb z83r)dP!DfI0MPWqN{u!Buaa+qYX>R;LJ`3zRajM-STp2;7*TY zQYGv95z+x3>sD7t;;9)h2dR-AKVKm$x2=|n`U)90c${Q@Hea>OQA6V;JSkklVxifG zwmZb_+l zOCfjg7&naGPUsh!85qSQ-ZPAWoc#?xsa#Yn-l9esaC(j;&4FnO%xhQJ&WWl-h{%lAMVcP)9R@&7Qy9HL`r4OKAfBVh>{y9NijX#F+SxDL z%J8n+-xQc+A+eD+cnXG~1cDL>O5j*YfE%+t`q*Ro<@N<^#zRHVs+B8s%=-TKzpvQP zj7GFA@bml9>T6*kBbkQl@1K(F$uA9acbd%Xz@ki= zu$(_KSb+70#%9P8?Fk^(05EPqJIqkPLsL_u^3F1uh7*Yhn2QjA8*Miy z8{iCr>p&d|z&XM2!@P!ktC<6CGd%<8a3VlDQRoz$C^W-th5auYtKT=`3)%d4-i65v z+~8rN5RQ)GZEeDrnI^c!l5p|>35yGt`XyyjRa7k<=ynswPLauXo+ioTu_Zem8k%=- z!6ZR!NE9X~!;>UCCs!KEt0fehu(XzWr5gF8@x>Rsm9*l2zzoqe+2N59|{KvxM1B}BlX*=@ZJfRlyh^% zgD+Xc=q#iIFD%cXSV@mb$Gav|Cf#v5_)ZsZLA_MGSs+dO>m_=0vUn2l03r|NrSBuO zhj}kyLcsQc?81{l7q;J;1~&4o)tF4c@_A|G0!G`TCu=!cHR(*fvfRALNHXn4mEf;Ew|e|EO*2DL+XYJ^?y7W@FGU$5!X`flbY7 zJp6UDE*!&Y$GiqRO@90qfC(Z}U>ltZuN}Y!y3r1B3>F}^$~!O6-Wi|>V3%6ovo45~ z$pDfpKLlPyIAU_pq$vhL9e3=E0$}!VaN}T&dwm7g$poITJdqXe`Q*K~xSJ$s5*$OB znuQH>PP-}^o=74k5erI7vAujXOl>>>A@y~z$HtD9XI>N~C(SWHR%HQ-++>Czm)B&7 z;3<@--3j!3qKZ&NndX7LFlddMia{Ke;(Z0*s1WPOG|>R@c5*}i;KZgBEIn}S+UkSX z7=h*%U>|KT=ln>S4f}EWFi(c9J^h>)7a8D6z9vqOlv6(wt=>7+3Vy+1&Stipczc{+hSvv2tkvIt@4MWEk%=$8rKF@iVF}${QvGriO zuEF*%!za_ROdhvx-6sEf`DH09FO`^xSfi2mHcDE0vRr@Bb#lpNm%!X>vVMY8{?QIB zv#pr6<^bU227S@$1j*EJ<2kA(bmjq~QNH%Ah;PNiE;wBOdL*Euf|81o=CV9?LM9Q{hb`XJDx8+a>b;JfG~| z4DTE%_~Z@X2+J5L!S0nixB6A9JplIAlK=v#edPd1oAG_{ptABqAIt%a3%*Ebzs|&> z%g6P8cU2psz20PY82i~DGpY0!?DfLvvPySmAS!dEj*vUwFJ7-2F4J7$hYJ-F>X1A#V1Vb#cg_KH~e4hL8_ z7)B>FY6pyT?zYy9oK)bS1=UvYDsyne2@zcn*27I>;eJd`V7|f$%rJO`*}uy#Mf)im z!Gh0Fd@YE2bQs@*GXqYVR5Gc9a>@hFh9Y9ZBOOv zh=15thAxz~(Bv9|8f!#m+6sJ{z(~{1w(g8SKwc(s*I$s(bWSDQdEDn+Gnp%0KY&xF zWBm!D`V3U6cMJ4`%|1#xG@T$Zqp{}mgKPdd_3oDS&@L?<9ufFefMzxg4UO{fZyv@% z;b&yWj$IOtcSZz&EloitO`ITi-E|jC*3OZX)D)d;=<}?M;mZM*R8`A88o?k4jF!f-_yc}NvhrnMsOk`C4s`aDO@PZ9rqnDk(gQebk!ho#B~<>n_()PNtA#s`^T2udI*fuIEXkU({H zwM>{Wp~qB%^7G-$RJGF7Y`q}>}_y&xXirNY_x48cY_sh8#m`!vH zXyd0(liv>hIO_?*bst${^_7X6O`Tw*fP13;yD1F^zr=hdo5DD1lzF#kcLOV zV*~~{KeVvafM&t8;@qoZWc11Sl#4*jN??1@jd9q?5@sJOu}BT&65I|Q2QLf+lHi5F z$PKLN;*h&+64b?D$4!D%4siXcyP?I>4e;c`%W0p9k#VO&(++i6L^R9|U`|5E71W+l zz99hq=>X$b-WI1W1-VEx0TT>@X3`@q{ZE!XG#_+nLyDGZQxS)UIn;;3XbD(=WOy z7PjTIMTdC;WX1ZLI+^mc=m2jH4@dDp`ZTrSp9UPLm8TaJ%9ZkBAkXagpY*`z&bF29 z|Mu9OchetD+jqZrIM?=Bw|VKsg{C`nOL{l_yT19x8!E@tgmipC@?sa*FM4N~YAUs+9eYY?a#Ailuf# zrJCr(=O#!BHrxq=`G+6&=<2F475&}zC3LCQ8UX4#yj!r#X*6uxb+INUCzXHOB|D$k zAgR+cp;`CC1!}Cs!e%}$Jx&s4shc6H6jCxP0^9Hr&1g^(FqB~<-D(f^d%I& z!6UMemhTRx!=$XWvRV9a14@w3{MK*0A%=)x=$8cEey3XAd8YX{DC0E=9RrGpJ#a?SWu+*2^X&46Rx=K{)0Z%&Xg zQ@q$Z9-n++BV7Wo9DaO$P0o*y38zG0;a>!{m`AhgNQ6uv!a66z9p0?D&?>_;f;VX& zPWtMBf@w4szl_vCvke2*2+*0whCmV<(ZjJb4f3IU0&1FUV8qnabPY%Ft?BSXph%A| zvGMq14nV-5#RfzJIp7?>xMTRV_PUxFc}a`SWsi*4)J_&&=&6&2SE^M-x&iPEbM@D; zzzlR~wnLf4W7D5eQ|a}>+M>M?Gk5lqz(< zFzqo~e#+HMT*xE-_#eWf?8w_EJ?Jw=)*W8$^V{>Y$7kBK{n4zKZA134e?Ie1x$oQG z!s>Pa*_bq}VThD+EM!eeiIsb9zgKR(?JJT9vkKA#NlK=gQ;{RTo6o0f&(tf>Jw!E`O>e?D)!r%!q^}aJC zaYhC--X55X)JXY1c1ql-=@K^q8hHF`uQwiSzzMqmJSDbCJbY;|uT7Bji}Iy@Wu@%+ z(Q4SdV*xHqR$@<0gZv-^hhJry*L-*=C`OTeh|Z$>XLD$N_#BNoGtr$|_?Rlbs;BYYvpVDJu896faJLay}y}@ zSOJUyWTWDtLBl@b0?6)qc&*f9Q=1_%<0TfmYoaqtJ2u$@-rW+OIf~!0HOsJ|~}< zD?{LcBrFyhZ@3Wj7Q&Z*D$HBpm%My&k#sCBRCBg=yjzD{G)8h!{ZvE-FS=5#IJo7w z`5tS=DVm{JV67lAFsN}9L(u#NsG9+gG#%ied|MUu2V970>UC_nIGWWu zSi5$=*IBL$xOGMz(wTivaDvWq_Uzg%KmOs5EaJ&V`wCLlcBr75XXBAI|*BeeTjXI2R<($;Kf@A z|J>M+$5-DX(Xf$D@}vL)!wbybTB%!GF0BPkQuOprsoPZ}DW_#(p=5?`qN9BjUBlne zre-NoBjAr43p|xR3i7~{NXF$uU?<)##n0`O#^MIq|HNh)^6e=y>X*}{?xRw*)lZs} zqiz97kGzvPa!3y@xp$8b@EZg3kKD_LOPPPSG+`kpJNsjV9{t3HYBRsUL{E*lO^MVe2LIafk3X0(18hu2b=DML)+k2yKhWVV0Po-0!{=Y zJtMz9NCj_00{7mxy9@Z9IWtl2zAHy2PKcEa8=K_$=gZ}V7c1eF$V_maeXd+Cxg_m~ zwbhOITH@AJ2OA?OffG#vbd~$f`@Y$QZF<~#W8OSnfX^HMwHj~^g6=_;Bix=nd*w0s zZCtf-75w&w!xfoVZo1(nx#gBybkJ1~1&&>#9YjgPo_CS-VP6gziw#UC!8TNG};})H0&fGV=H;)MthQ1;G}QU3-)Dw04VEl80{O?zniX zMr0k^%f$|-sJOu@h!6vx8Vs?%%20BfE~xsa2+B}FtPabfi=(nErQGeiYJ0IP!MkPI z^(NY#-0G$T`1RE|GRVlT3l~qI&IA*>dxP~7t(plQQk$QJ!YWrPSQ& zX8kpSAGWz1^ZPfr<*j4cLcdW>?GYdQ(cWA+s1~hsT_%6WFC2L zCT86gHwk0%TP>VdB-cMl{tWh@vU*?_N@Yf5hYt+((z9lT_wsxc%_p|UoDV_6{Ikb0 z6lkNGv%*b65t6Tj$Y7{XKlz4bA*0xG{{?z?QYluMSv{ISfJc@oE0cmIS0CWv>sO*c z-TmS>Eeh!gEw5X;r6}9mg;H*UWG?v0^%hONKTuH6=0Q#@7f0EpVLG3>A^yR;Halxh zi#mBPN|Jc46TCPi0=0T{eboJN1~KBQ_HuUN3;#=#77n%L#Rz?i1j1{gJL6b)Y(F>E z1GnQ#zA^tv3U#{BnflY9frqSoNV#hH9=we`0k>eo;bW+hMTO0g3f1YjOtbhPp$UBeI8X{3|BV)Hu z`bdddF@Mb7ia}`1A8DVOm&TZj8<})k=;|azKYX*NIuD)LbsvX8RN-|`@Hwk zx6nzWlC8zvH={rQ41e|O8QB-^U9PKn#YlC8unNx$?+L$%{%1LS+*m=yO0F^(M?llu z!+9iLv2#%}4+n5&l7yBn6$Q=;?&n%G<6#}NIll<%o9PgWBB+uXIiZl9?T&n}Sa_G* z+x$0jr{6D$J%SahjEX6D0*9XvajTK+H~Sc%`_`aKN2tpf6emZuZk&4QlVGWmotBeg zHyC1+jySa$ls$?u0Z%#;?BgQmo<%&S_^}p~mW z0wW2slUYmZ;Yyv$!CA%*)*4;^x0APeS8Ku7_X0}FTEwB)m>&C}bmxOb6Q;dT($#LQ z_qz)upRysJixoPww?dH#es0NC_mv2-<}W=V`g8M7d)#QaBOC9ANQnf@3)DZ&Eh9!l zubiCu9|b=CEUmYs)_z_BdTjjBOYzmG-`lg(P9vanVU|gVAQ?-&_%M#^xN~pZ?|2&3 zaXzWi@t%mBAoe&%R@T3qfB`C;g<3FbLvx+Y%?G^*ds)?H1sH0PAQ^w_A1Gq*xG>D^ zH|u3_63fzeaclFvqc6^a2-*ZYB|wZTXjbXY7cmG4HlQfU{PkuFozPquRr-kQx}<}( zwB6+7p4RTLO6Y)qFpM*Jdi3&t{E6W_$$gPhd20Lhm*3XO-(Z3JXDhjAGkET%x-V}s zxq9IbU!iIwOYDF3U73|zpI?VraxSMBl95t)c9%GcbSPNH34Z5`=JDV?cLCKe-h1Nh zqA^ER6$rW)%wP!z@njB>wC@Str!wFPLLisnM>C>NKT}C`hu22D4&gNr`EAFYA7ra7 zViZUXdzkEWT0W3~$3}WB^jq7m;z-eInIH9) z9;IB7$~Ectd6*l0@3v1a_xR>KPS>^|05ifKGW$ZjQF~ApCc9|9){4pD=OE$+A}i{;5&u*3 zOY#G3W4y_I5>F;3zm#t1NJ=S_e53RfH+k-#WdjgYBkRf4-ou@%wYpgTJclV)zcf3% zqm`a9M@qtni}6D^z2N(qC9)JES!!}w*^d~w*DXyTOa5p^RckNEW#!mAfLkJ?iR0UL zkAApj{WJp>TxC`H9HV8$p}64L6ng0iM^R9AKAYLWibGrEfQ4oBKAR9x26wYwwwd`)#80q^`rGF3jex&19cW+Q$cvedY$`t}#|syyPdn{>$;tVN$-?Y>Pk#Oi+#`E|{df$MR8V#;cBfc4 z6CP%VMbNP@w>TuFl5Mx^lIzt+!>f6@>6&tVrNFR1?S6Wl%_j9o;K)lj!=vb;I4x`~ zLB0RC1>$4ZQ?;~|>$Uv@U7X?j*NPFDV_S)s>}qy4$jzjNgyoe6<-1-$1JWs8e<>~V zrN7z5X23!pw#{`NlXo$9WA(GMm_8Zv4NJnf<*$a;F+uf-scEY!Qc)q!qcL1me)qeF zDsz1p$Ev5*bYL&=xAQg?AFp7`C4K03DxxT1!P>ILDZ%bYly~<;5zf_j4fV~Utx$6D z&E+c=F`zYlN7^aUE)gq*tk4U7OJ9H5{U@0HG8L^oAXHdT=eO>q(~c>CYM_9?GT4w1 z4;PxfXij}LHbQ)H=J;$0SuODk)9MneuUbg9Fs-U)Rds4T8)clZuts??721b|RUo$A z!pFgW73AgkP;ApBG`!TvE9^&Z=LJM%LCb;=$X@MYV9nV9n-IFG49-?e?hyUQ^$Y?e z6k^bW>U%2EE+iszCBPKj3M*4Rm6ueT zhlOQ{f4j&2${))Kb+DdGxvBNT_q5b=R@HX%J@A-}h)m?Y6^vbUV8>(M*C+*Gu78<>%{|)8 z%8&BO@(EOR*m0C5n-Mg{s6M$ibV6bS@~z7iBkyFY>lWvaC<0D6Sd$DhozhVvfm7)X zE;_~X=pKHLEN75#7(y)&Sk=F`I?qQDWT4J+FB;I*((X{8DomJHPGkTN{dHMbN;0WR z!WCLGorj%3R^Vx}*8b=g){4qk`*;l}?m+Iv zzi-^DEXqjNYw**ZRZIIDGzdf-n2xBY*#>xA&!q)@8f7SV3Pji6w-d1Te%Bbm%qRNu zmAU4~@RNElxj4k}PF}*xefgTDc1c!+2A5TQ3RB;FNMiex?GyeWM0zaKn@u<}0Qr#G zS#f`w^D_qeG2uL#@VwqUSP7oal}H`%xuIkT#;?s2ep5D7`-h)E1|2fP4)kiX>jBpkbX|;p`X#g zWfM$Y#N>j0oIFUixxQ!G!?zp}QBi)bT>@N8?yeavT86({iYXX(67RxK4eca)NBWxH zysutv&)t|w^5o+b9jLa}*FvYJYPOr{)PH`z@})g2u=~=KzfAZh`V{ZUq7&CJjefD$ z--oK23Kt~w=31tI11(*Ya!_?*2vd5}`Tb^}i9Y`s$bu)p1K5=MVJE}m=l?4z>hpwV zm^4qncsQ4wnM?J_C(9vnUov@|7KxkU$AB1|7&4$}N26FV#t>EL&(~WaMfHfGMjkT_3Nao!5+8Mm|3rUyV^n3UvrkP87|p=ID;>u^sJHX z(>ft4<17=$wP`+YFrBHrpUGS%;-y6J3*~K5Ywv#U@#i= z6rk3h?x*Xb6IyWT?)Fh8-D{B#pl17c`)yVyo}1g7|4oei!@W8UaqqwLD3D)0k~x!^ z`1hg7VECyl#CXwoqg$)oErc4ySM_p@fE*EqRkKnL`><`uV;H9-RGsLtuQB>^F+buH zRsJc8=^n`%Rh+SU-sY!q{_LipsxrMESmXcI<{MwDx;6*z)+2J7fve1g<&+V(J>xYY zx3%dn#6e%(==^7SUM{F*1Iq@7fjjLI z$tfme>}cc?;?3_UCJ;@IClo(&b7`{R;M)*)cbQE%DiUIgqVi)ALGCc8smG|-4bE^D zuR}!x9V;E6WQ<2rk?+Y|L(oKbr^5tm(N2g)4Ox>ffaNdxfsU5+3~yiLKr(>5)Hr@(TXKj|ENfyjqN_9;Y4jYdkFm6BPOc*h?aWc|pO{-dm4 z|AsBrYf1aDd}s{Ibs6%Y11rDxv)}qjVeiM?z@{Y-_=dBLB1B5JAsBdXs z8~V5sl;Y#9S-oyRH~HW8%*B|8#$(}v{ax|dfd@8Rv~6UDw9w`rebNnGyz}9cA{^0M zrm3is=HMs4mTveV7k}ZGDslKyidgDF=Pv=c`kwgJh$l*+MpyB*&d~WhwgP*WloQjkSoi8%i}dY8BE!7U6r^Y{wKv-cQa368KRPfhiij zEPnX&qDYG)s|V{6M(FFQdnrk-fWZ*=dcjVuuahGIOQ#&U~c`EY7ok zVD%~&0vH+v`ErYYR6@3!!Ci*ywudm4m9e8&>{HuvKt@4ghl`$J9MeCN#ait1%t~C! zav|ysx=73C#3==8Ky+Z5@hU8l=oXzEqAf6#?T{hx^1cJO*$_~uN-mTcO8j)kj_uDcLV;pAzsQA?spdzqV>R^eadHgTqf{Fr>fVwn7C=03+!Ei*3x43y6VxAMT02B%kL><%o{XrHrYbu1(mwi5WK(@_bXhtdJV_06nWCJjvE+##$DRD|f?PE<8o*!B7WmW=ow9REYPKLWwyLJbrxl+Po)EPtb6TVqJy&zb+>zl!^6Y{uxPy~9cVZmSOSX0qmsM^Sy1 zCNIbOh~;pf9J{yzob(C8-_mQ|K%auA)Mro?T?f8?2@D)E85-m&%>iusJFKc7F!YbP zQ+pR5i4bRJ`gb?BmT2@x;0ZOT<<2~*^r)Mu#7LBrLu%Px@`I6OAPAAXihfC~u3xlw zWuzK{Xd1u@&BTS*J@Cy#wpuY_t!&19Np*2&)Iq0{{)G(!Gw;bvQJ zeq_BnBaKc%b3&?hr}quRV1U2x1rwf@Jus4+g8P)HC-><=cx zQHJP(&5^jPjkdQq5go7ASn)&}B~3(6GqVEuOg@dl7*aG6Ot=H9;}@7!bgO5;w(IKE z=O#!%Bjw+KNK_+Jz4^}6Ir2zINVgrSjy`?f^_ks|YSUUyP7SsqzhN;EsqerqjHv%4 zq3(l^h-UWWX#nbqS+gSH_*+P|$+ihoS0(4fV|<|rhBvG=oPf-&Q<+Cwn#>PXtcfZF5dNsgFvi{Ip=Aw%jBwC_FwXx1Fc!P2!;!ia9+i@T&a0@|M5eqDguqQBR4seauta}^~=iv27~dQ zo;wB{9RRo8$Ubq@|y|v61LB_@1i&K|`y>v^9!Q1wa=fCc2 z{e#-PCeItM?gf=Mhu*Vec}@pr)BC5*SrO>PTZE7qZ#zqR>T{pa&CWrp_Gv%RwUD(QR601W?QR=Z+GPIVL5YnzUtzT>2NtIp<+uUCjX-?kY(4=vfw5EU3@ z5iK6eIAO5uEks1rzbjUhx#RISgHF9fpV?6CfcWWrZP^Bz1!o=B<-7GrP+1r>i*Qmr z-ZgO$t;hPRSH0&IJP4FVvGrIh&u>vq^L{WF72{#qxQn? zgBY3L1r#7qM%|cTAh1EtS~nt86x7gckyoBkOq=t0BNqr3>kr~5Yl@4cD<_f%=n>wU zq$6NFkC4D-lqYH35UmW}AXLmlQ~zb1WVdo=v5shB6WqBp`DRBSV%36mP%MqjY9Gc4 zOC>8W9wD)ewjhQXP1~e5+Pfl8@`p(F7(dpJ9JGWEPa~+r=Y|fQ0&a+fR}B)Uy8W5Y zk@@|aXy-DnRy>E3H1-Z3D?2jS*yhQes;WgB5hfFlVr)Fy)8MO7^Bn499>Yl+E~KG! z=VnXK^5sHUyF@WbGQEaP*-&J&@VrWykxEF6tMe`25R?A{SA|E?#0G}FcKPwqQmjvF zQN7BKlq6{CtbVj$wE0MARjT-lt`u%zk~i49SbLIT%^6d0?z;y}sM=@)K46&p-Ms4c z^?=a)v_+ycHq=9VN&1AGUD0}@C?SehcyL>;4cD*~9Rxli3#tkB{kWwY?e`@}2j+8X7OHLvN?`sh~Eb<=+=b`ww}?K=!n^Co@E^_(ejCXwON zP>}g`U;>;eiVQbi{W_Q!AorS-ZTFeMu_xwbk6u~x}@r^N-uM- z>qW|a4T5lm5doQi1A%KdVmSBxmWH@OrVCB@)iJFttKtXUu7h7jAR(A+`g72d}?L75e`wGQ6 zOO51Dg}8TBAWcFFnn(*Vr+lR{%dyLx;p5bR|CKhkny6vpO(mll^?30>os(=~PyjvD zswgLF_Mkziu)f(U^C_0oK?sgULTOV{b}AXZHg>;@y{Mv3gX>G9Fggob|Eu4jRcKU> zw`&SOAT#gV$)a@Kzj5EJ?tJ%Avn>{`=QS zQkW0X?bkO@Ekt6sE6?i5<2F@+7UPXVa|`yr$oEeimxU_o;kvL>FJS*2_PiuAz6#66W>MJ@HY=t(Z#W7tyc5$)RERq4JcI64d1oFnz$&!U7BG&9T!3*SnF z4NCCTxN}Hrc6{Fg9^x9f{&wb_MzC!3G6thQ?!*bB7LdlYk+E}fYP`R_-n4-!Gp9-r zTV!Qe2&PJsLch4Qoa}Y9-ca!-Q75$2j-~O6$MRjv#LK>Jqs7~h*0Llz`Ui4*%8PUf z&D3~Lsq6_-BNqSL_$ z{;}u~D$H@gHWSmJ8V#94c69KCNw6NY&Ni$qM`j|jR=)uKZb!pso*zcfFRp#`glh1xu zi0|+sWFbFyc#pkPlN-c7JG7SRIr#7{ zs#Y|yCoIx{J__}3&sqkOGc^WNlKGtWBpO)L@K8eX{bl}~k-|m&D)Iih8hPvP?6-ft zh%tjGSAdnRN?436bHC5(;L&h%8(v{#R77G$s~+KsVcXUODE0wYDiEU)utI^#G#e2d zv62vqLV%g6OzAL4S>-=<3S~l(70E)K5 zW{43D%9M?wP=mqppiMY^htcv9B|W6Ya=-&g+|DffyNz&VAol8X?!g5YO!o6`owauy zp~)}PA<_spBZ8n=rO~8Gp>&v|2fnrGx<887htrYQH@PS&a!eBgtJ4@OJ8?=8e{CxD z1Ku)Fsc)!z)i*vwDcYSi#BKki1$?yYb9dzPA*>O5ASd21Ym=-nCZ>)bYFsF^8qHlL zP+}#Ae%yOxnygArZvd$5a27ayBMGww0WS#=*(PIn%op*})=R}jWW zm<5BKXU9)=Zzc&%9Z=FP4-3?iUnjGs-!)Ampll_Y$v1~GdNC-aj1@Fx<`sERVrj2PF8Kkp}B(#TPUV_`Wvt?a8A!%u>)lNZ$;=i3R3lqNxX*kE+?p6~$ z|E%HT8gzs6cs4;d7fRwSekE&-n2Wd(pn-9CZb{b zu52BmDw_>wfiu>1A!n8A?^R>+4Xg5x_c(wIwBBjm#Mxtq$IW$~ zrY4mM#+!PgF6=_OV0p|7QbfTqLUCQAxKHs&eWD-y@WHwYiCSwa!(5N zntjx`O^B>7-PO7D8c|fgmuurFY!W~C#*F`a|C1SKWB-cVe%-uD zvtfATqLZGp;v1P~;DmtVn6OVg^6Qmnq>`&!FLsYR^23lu@%cgHq9OgwfjdC|rQrO$ zJmkJ!xNjn*tPGLO*VEM2a+J9|&OtD0feVOO`O~p^RNn07J&J9MC;+P>UrZ&IagoM& z+Dw-KjyrB;)>%-}*Cc))QLD;6iPdv>7~JF6fF-TB0+>${k%jz5~Z?5r+7 z_==wB*@8txMIB}QWWI1xBjYXGB(a?Fow{)2Q)PR8FfotRYUpW>{F5C0lQJNBzwdrW z2AHha#fP70$~tS`v`R;o66xmZ$ioFQ(O)q)PgHs_S0S0drHF6aJWEuJ3NJh^zV^BWrc@Zy!!-xey;`GH~X9coV;oeYj!UYuF=ogBX7Go_4%9VrnX-JH+X zF@&jmZUr=wfK{g{eVL^8_lE!zy9;f`OH1L8)(%b|_YJ@%YNT5ry96cj+9!nE`ztrk zxg+wFewg=hSqV^AiXf++m=kiJrGE|F`O&TL7JDHxL=^rV4}YZ)TbA%Z*|{OJjtS!i z^IMErDt|V&bz}U5kv#H&_e2jGij{Zpf`urPnGC_qRb6PBc(!38zu2Q4S2jTursJN9>93Y05+ioEz=1TGOoYL(16jeZ6)G3^r)z2B5!ZcR;bAl@mZ zEYD8`6cLOdrLp*Q>$6P|1=g3!_J7$E$jiLi{z88wPSe=k@&GJt6$)ljB}}>IU8(IN zN4~2kTNe=(v{lXkDE-A^a@y0QD!zE^I}tBkswT36v6{^icXo1$@~>`nk8$+z&VVbl zf3c})W-ctOvM+9~cAykF2iIi~p2Oq^v-v%{mZm)KmC@uTnTd{+;jYtAZ4wotAacX_ z9?g8e-o=WO0|{ds!+K_O+6&}x44n{fCi`dJ!apxKK7OUq<=f$x<|pykbyYM>#PO$@ zXzue;n(-rXZ3+I>t)P6+vlOIPK+hAKEzXgXI9Crm#f}9FF8Rw!$qSE-e#tiG{YO#Y zj6Hx7sgpvp>R(ynel_q~T&JjWHWXdLewE#~ezVRYe;_!tW6(PBr0|?Qka@%ZT*z!& zZ`yy#<9fNwS`~9-2xD4&>&WgxG&3;m*$(p8C*is;opGu}RWba!^eB3tyFsBdjGj;GC_ea-0%z`i-#&!8VS>pmQh&Cm5%l$ z^^LzP99^_rVcSVU&i|f7ljDkGr>bzk{Lpmbn;Cl}x%lyJ&7=P1EXIoEsN%g;rNDgd zaQgaslzH@1?4pvta?*3kR^*w6BYjnxOq-t8-=G9NDgVEXp9pYUU zm2PZcx6%gJxRLUvAF9mTrhU$#H0;+`bp|1Z)C>`01wAAU`1O9h?h6VLVn_Ryn|3|>X*3`2HMD?IVj=vt zE&a5AmWVv?29)E~WrKMhx^L5WPa}1X{Q5yd^1SIqDE~pUUn8F|1KeFNlTfSy% zYzbb3&-bN1B!TURx#K#ws$)50jYt+X(sO3HtT2`w5Il=E^(DsON2Y2y=3yNk2@!bS zb!+)8=6g+nf)sjCOZwFJE1Tm=hRcf%R;fOJGM5efAuGa*l2z|S5i1c)zV)%~*@`wncL741p^1fDme0H)qN!?wN1kD_!<{cJJ>FOb&mH~D@ z`^{8p&4gyY7hm$l`Mea`3FgP&Gx1e983j0*{J82~kae*xfB|8eq;MjyJpsUigpcMh zrcihm^`OffM{=|rE=Q+B2y_SkN`4*;86~CEyORZzo6=}hGj?1H`}Gc;Me=)IDEm33 zWxs?)!%Qj^1r$&^HpsU$dn}Dr9QUM@j$X@m*^;Ivm&;O@mG8oJJuIhmILdt4RiSjuAv7(#y})mjGk7RSc3VHm1gW6gpgzcDv*VeR$bp zwoR#jS~0P}GA9hETB@c0Xdnl%A?k%fi`UG(h{Ro>3GFs@qZtyeP(_J&kk{_+u#BO* zYaOlWF)bEyaC4jC&W9~Q7x^WY@qb*x>$JO9y~3(q3Q7M(DU^Eq#B$$V?`Jy54yOV* zQRdms9?RJ7(&%Dc`)x)i%p1BS|56xZ@IWBoX<%zAw2Fwe8PzNHG|k9WA*pT(+Z!O^}A2E1)M|9ju&!vh~k>4+}>srQbs3KvjPbv|$*a1R)_)c}{xGSR9## z<%yD+S}jpPgLjRXK`!^B{h2wC*!t8cznNLu>(oswrYml9*#9vg3)@8uH< zW*D^yx9BhnqHm$QKF3LPpE%wn?TU5p6UMU2%FlZs{u&32OZP@B%iaB}A#_1yd?BG~ z9j)vMfjjImHDawn(!<5XI(jqcPbdG|6B8)MHs2x$c13U7ls=Fh+}5Y_a$@Jo2F9mc zTTtcoyh;FhDIz-btYVdhI9V=*#Cz8wkUGg}r^9XRe#suGofMfmMP?A&_M!eZv8Cs~ zY!Cwqf1V+h0aW%DPUZXcM*n@4J}8v6wM)O|_?)+1uU+_aYd)b>w@L|pyt=fW#08w{ z!3Zpfry8dwC(}VX!~kRz3T0!nICQ9qS;4@;9^#33ce!3-fz#$s|8RuBP@Vxo1|c;x zl0%}OWzE6^`;LG39@nOe2hZVZfEOaP!14Y-Ork908=qQARA3AFmK4Rh9@W;j0jZs2 z_noNMOf%Q{3cW(}1t|>MR^k%A6a^43wbu2ASp%T(DeIjw8yo;pQ&_e9A7A{5F+4Gm zGMUO4GX9VkA4Tv`Vf3!bcsVU$k~<4|@b?6HLEHD~`lQ}@&lwR`T$Ru+$-)0c4+%~| zLeqS=omZ66*o(dmXJi7NyYX$0sSC#WT0rf z+p-n8?F#*BFHEFYZ{EVwfo3aQz(#n=PrR#QgDzz}t}4M17!|w0IL?SEFPGFKO!DPV zOd0AEoaU>U;lO#Ik^Bn8XXM&6mdGnFmmQ4p;PN?L{rF#_(hDl&L7p9voll~1z~d2S zSyjbXBS5Ly)wEjN93b5FL?jM8!T0&=zZ6r6=syd{APZ9DsHjNFX?04_k}OJdZC|u7 z0Dm|4FmjVRT7j6VzHEoWXhGIu!)j!R2lnx@B4E)Nju!4{7ouKPqD-zt@#dzje5fd; zlgrw=qngn|CxxoznV1HND{3;saD9F$)!2!}7_ZVVHC|VnVsBpii$UEq2HGn)|JgvE zNFYBU8WBt0XcrDPwhRmUAwSIOiX7@q8D||${h9>1LCScP=uEXr zq?w!uG1xfHT$R|^{V4N089Rm_WG3F`R>}U`xPCcjTfojbs;h!+Hv`mvf&k>_WMccp zNQ3pP`D@EnFKoI3OZtz&HX+f!3YI@vjdWCDzpwMVe1EyO`4X=Y3O~f9w#W9QiS5zi?`&**Z42+`4<9*&sQLSnI;JejQA-YL>R0(9;ZZXgAd3# zj&dqgRTJ59UXmH7j@h`8RF&FMx4Yf<_nG4>NwCmLnDDa50cqTkZf1chd$TszdaL>m zEzOEuLK-Rx6EIOOC3H0b<9i3$oZV#yq)R<@?zS7*OH+4U!`*+(DEQgK0`%DMYeou4 z?KuYTSLvB^$)m^Ov5M{Y3UC?Zl5L(H^3R-oDn-zJ)1_w|$0#eSNZjpL*A`B%1glG< z|HbPTtU>wDKj8$CghGJ1y~^nkwV0}(Y3)GHR@6A8it$OT)|hsPCr!8h0U{&9bSb|x zaplf_o0b*@hW$YR>7vnb628Yjdhc*LaR*puA0n*$b+_Ii1E>(hnh9|<*xD$og-FKx zE`AO2eaX}8b9v_i^I8WYzq|o}yli@wHJ-=b?Xlmj1C|TM=uZA?+qYj3xF+wfxZUyj z;t?Y^lY%s(qP-tzUzqib-*!Kzqo%HElypS51jOlNt*z6OG?L67M)Npii-@?cWWW;> z%c3;O@TYIyK-=&CjmF&QWa)E3<5ZObg@t(j*IVgqyKRW|cQy2d$2&;G8&L1@pVHCIZ+gz&;~}bf@{e4cseh#$v;ZvBDo1od&yRhHyc0Y zw*}pw8T>nBz8zr8aYPT?3JJ@2sc(a}sXU$`jy|$k+y0^65LzzVBXmYK|1aAsBVe1f zd$m>6bm}rs*t3{1ja??NI)_9VNuh62 z81?e}Rgn=9-6O?`)k=t~yf@Sp^52`?{%?9)$>sJq6W2c@j;+jha7FHN~m`kZ(UD_)H|WXJ0|=E`hi0zJ*i! zyxHXyc?{X&+SKn=B9i%a|4D=*Xd+~kf(CkZUN`&NHdZe_3za&cq$GfUclQfo3;EJu zTq6u5R!RRq7gG;7)x^{;ItQ$#oSI68RNB++83o=SzcQ#e4)B}#-B>#RyUoo&a*+iT z>6_5wMDiiCu|BbZsbN?A>%=QePXH}dZ7gP0aJD0ZwyjNX)7>8~xwZOlU|zHMLZ$K4 z{;jfj>YV|aU35T?tg&EWJ2axS~lpl%b##8vu6f6BTi6t_&L* zEAoVu4xbf^Hq1RVlz2FD!q}L6>)ZXxf0)EQDYt6h@3Wdi_2xEwXBF1O|Dup{g;2g& zZ_@^GnG9xf@CCB1ndCK`iC)C5g#Y6mp%WR zw{|cbi?&cXrEJKliedfhlT^>o(T2GC974hvmof?W>et)T_64^8@xYDpG5+k zmFgp+DkOxOOGB#+>51ovn(yQ#+VOB-DFa*K_$1h+o*LjDB{5Gy4lypfq-T+yB)MEN zN7hxJ1HQ>~9Y){2|7n&PU67LL^eewuBM==7lo{Om2MX9?0cL{eufYC{syB(gJ8@_0 zYrMU5{r>-bpU))&G0z7`eaW=+ox&KjTa-V=56FiqTqb#?tJ{vhRduYm{^jNI7S7fX zR)^ZYH==<+p-c&BI9c9dwf9j>ZL#QwQ0d-Y~NoJPYQpiL`smE?Bm?6wv^l#$ZE@!C6s$a zQJwwN2a=?cN7j~N;z5AxaR!}Dc*8(W*kH8LaOD-Jj6uW<+V7hg@sVDrnx(YQQoP0J z?C#B6Qgu<^3pf~RQZmqFi;n-OTI(zprs(>nRsKewYA{XRHunE*PLj#|SsJJGBjT5H z6*T_VzUVL+nRmG#{VcIQi&+*JXJ_Z#uQJ`%ccJCeW`ofQpORyPy8rB8vS#d2hf!sZ3mM9?IyH{9 z7G=^Thz@j5jP-n+!se<1-4Z$i3vn!D_Yq++4^vB{IQ@8+xXF_?aqA7lvnPq8%P+wQX1UNmCZ{)STy zh<5XHb|`x|>*sJ^HDdF(w!8Wt1K)IF4CoedsuEF7xMaj#3T{spjlz-7(TLL-@Zq8N zKKolzI)Vl4^5{m<9Z4>Jg=TWNvG8j{-!Pjj4-H%j{@8WglKLtzNz8VX*`S;LR{wv8 zn@$W!Ocn929%a^g(U#5ld31I8S6Fgj7=iTJ(4Ol3j8vHcWFxjVwfxT^9)w8G(LoD3 zqi%)r)Z|EEM%w&W0FWe;99S?rfCiNtD4FSxe(hOa9)CC_|4x3oUDV~1=RR~ow+FYi zuFkx5EoI$rI`#Zp*P5xv$@-7P$rMBq^zs=)m#y7&)!uB$J`k=cNhvplS@hW3EYpjR z9lmAW4X|A&L-?P_@|pI(1RWp$;JvNwYqM21G0FGsFbA(=IEtGeRqU7tvQUjlRz#UE z*wlbC9VSvVDl}!?6q4sYYeI29N<&U50Cw;$KWkyT+CBXt4ULkpq{Ubkxj0gU%*Xqi zy4ZQ6?siqhOWg%D$8Qy~~404Q>aBVH>-&z-7eMn;rXj136MWJ1v~Rmm#W-^vmP=Nna@ zRLUONU$>Qzev*D;>=C8g9)-YHl8+a$M<`saX?JLH5=0w=YYNYwG{8-uwT)E+ts!5p zwR9vZZah(o={IQWY1Jji}}nZ$Dz#G`ccr!GnB z#rn(l^N!Y9u)((kM~T5$!l*NzxS(Yqfx04yEbIzsV1P3G^y5w7u^IkzFnK**Gz1$@0utTe*SsuUy(Pd?eqffLMP3PU;!viWfATU*rmpL)Z>O7G>oGni$0qpf&<(-NIkD`co{t;^7hEkbO>Dn;On2-uKvt z5%;zOM3c{fdLexH;!Y3WNRcD3Nark1BK~{KXo%o09t<<*^Y~I@j{M8?{Nmv4M zXGCT#W^$SN-&%3W;z1ya56|0aRK~rD0W_h z9;teA1M9HD>h;t3lf0enS?rpPW4B~4$>paxD#Hc%Ahu%_Tzkjk14Gi)j)}*RfFm#r zuM%%y%30U^Qf78qJ4oq>HM%ceD!H-q4aLFN_zMw8DR$YPXCq^bLn;Kv)9q#HngT;l zIk~8g%k8L{xw!_*Z?qQ59Bu2~(HM`P2=yf4VBhYSbFq%%Td>mm4R!1839j5$t=N1Y z7sz(sOnk<;RDhWo&6_PkOv|TmPIDf-;xMU3H%5sj%%H=o^U1;!MmL+vf@Zy$@q+)p zFge*|xAJ}dn6s@s2Wv>?FrcIW)j^jTkIVM|s*-1HsF75#{u(Q$Pz;C%KqUBGz&#nc z=Na*?f3_|`>9HbaYSA84j54)oXh`Qqs$sTe(Z~^&>E=|@6T#KHL-%ixXY4BFyC~fG zwG&YbDxLA@3JpFM8`W8wJ|c=@kfBGtzppi<%pf^BYb3_6kibw?hiqx>>UyU~+1_c- zlm9z`i7<=j)z@QE*GivH{czs!{RDl|Hdw-}BLBTbAvbI_EqZ-A_c6Y33_?B?R`z%8 zLvjB1H5I6-nR5J^E?|q&*JF(Sjn+-Ip%~!{+Y5M$W!!mZoLkn-pwaiSho&uZ!XV%^ zk;r{G-+9^EXgY=}nu+^n_9*bIDd@A;LqRnLHmwT%h8_43^hU)>S7@z3B3S$1&o+6? zlz~^1(Q$R-ZX(gVOwI*FF0oO>(Bf()xB?m=nmw5XEqGu91H-sptrg~dUuKi3Fl^|` zrDlQ!OePnXObb-zp7}FC=%-{xx1Q~CfER?!|SO~~O0oI(btA^CwXEa#UA>Jmo zyTMs<%@yR(6!PO+Y`MIT&utd@mW)PY|I4uG4iGU zlcA0V-K4-xbO)zpQz3wQw7Khv)cN0DYWZzC%4W6gpcC2t@f19Xr_B3R+)y4{y~1EL z2@SW2WnQ9U4paC0ox?7!@JqVy^A%fJ2)H*u@IFmGn@`gD#yRzvCL>Fn8B|>5ihXmS! z{tQ2uf#xs31WAbPAMa3%%iJ8fTUXtZY8?51h3=1B83--~nM$uy2U0WOwGNjsk7Q7j zY+Lr#vX{+~AJ0!g>?NlRt>OQC*T}=A6xnjYU-%>u4GCqCaVR!IkN2crx&76|u`k4_ zwQ2DW8B1rEj=V#pQB3_aDJacW3rj;^FsDlc)qENL4A;8H-8`%m@SI!kJit~~TACyo zwVryA>tp!Kr2D01Cob70z%%=nFp1gU2;z!)WP2bgSt1j1`EmW8Ep|caSVagI#-ubg z2pUdyic|E^E`&Q5<-fJ8P!t4^nU4D%+|vE9s~+) z{+fGyOpyNn*!l)GOPeLg)=X>Kp0;hWgl z%*u$!NX9qn7l0(l_<9@Pe(=O}nC{rqFl7OlI|+3bDM+$LeiA8~n^-+{#s1-`GU>|e z$O927DF4s*DlX05us6VfErPis*IW=Qy5cwb9n;~YQD=+U2;yYiMM=+ki*3l92+S)m+*@nxn)Kt>Ngx;W$7j&bi(&hgZNzJWLT??7V8YOT>g-dOBA+)t_&_LekX&^o(KI#q9- zue>v5)whYI%>-7H4jTXOuyG@z*yJav7!*4uo#fMRIIJe+c*`Pz&-WBJTO|Nb#>Y7iO(mVjpe0F9I9s9{?w zQhApW=I_dgW}@hG>^=U?9AF^(El*oMUo>yEYlyaC)IXXEqsA7g zHTk=n#7We?s(bZY{R z`s0(&QS^%udpfx5Yq2J6Ji!JMO&w-Z%jCnk7Jnxvfd2uMylMpV?dWC%92GLcE?_N}*>+H{mk;2G&M`9($IWC(@zTDKEiNxxkJGa~Q`q#5oG^QSLC2SO*f4pu(0M95g9$Ob9FaOo36FOu?dcbV^n@QMT8sy4zDn zAcOb2EBnR$9A1fxe}4KJPMhYSKa-+_j;3l}DKXOqgV8X2Q$i)TGJmKZr7`iloy777 zPs;GFqTd;y(arRqI_8qA4lsMK4`)5GlEi)=<`HXzAl5_+^*5Pt zWh5;v>r$NmjEdVXos{fR33FP&jTp@)L4t2pU&R^Mt-B5GzqDCl5{8dJB;T5{zhJ2u25l9(?tNH#VXdx_d-{lRHA z$xax=|K8ZYKMUabB?C4(Hg+&xHyL-Mw&%LM6Fl~(I?Vp_<)|#vcCl@ZW1FZgXq+J< zY9RUL+qYJrS_m-ri?jv?&t+T7&gsbu82v(}H(zj^m5nrxOBXqvCN8Y+9ETt}K(#^)LSm_t|O$n6BY9wkhcNrdP9 zv5wQ&`a5|2`+5K7N30;q)oKKqS#DUnULv(VKCUQn3l6%|ZCcfF+@EdSt(yS7bKNHN z#C;tHQ;B?Z#Sehi;1kwZM*TRe zB;hft1aDJg6Mk0ga5{1@8&%LNmTIe>|K5my52r7`|95f-WQBC<;}OMZ&QCV=>q`OV z_>MKO^%%1xDUivjVR^y>$|7%Jo{xJ)^%nC$uFq#A0Kce)5t>P_Swr2vLT+^((_?>t zqB?q-%e*|>tUNn6YYZSE?eFCkjmDqpr-`Q$Tdu|vJ+9hhSt_@F*khBbdhbDDgha*M zmkF&Aeq(*8lLoc~Lk_rcFq)OQ!wO}6n2Z!g<<|d)jQDGN|8to(0{C%+?$hXwfK;ja)_r{TFgUJf^$`V`4mn#zO2)%N-98fhwP}`{j7i6BYrVkMTbIwr z76YnKM(Y;Q$hT>fu8*j-z_1gm7$tE0g-P1sB~oC-L$|B(_^ppem)iTfDg6&3sv&Hb za)2^;F4hQ?s8F8U28}KWibP)8`CGiQsUh9)5p3?`+3C*l1&o-L>ZE?HwYOHlSituv+K(_B;Uo` zdDU-AVydfj>YxzMiZx_Yx!mI3Z3E_eRG?3u^xKVEuot z%>{(5*M5i3%NW{XohVoa@y}Z;&r)}}tu$1*%jP@SmOG!z1Xa_H+*6V}w8>;Q=))1v zXf8Z6@>Im=dKhY_)=2KA+M>HRkhu6n;-U-&!OGTG3ijP$LW8g~k=+T$LY~*d6n&b` z2*2No9|2Y+6&1;cg{abo|L@6q1NB{9)c{QUp=>QL#htIY>Ui4ZR^-{rqu(c?Blt+^)=jBVhc`OYwgWc?6JhF7NAc zo*Fe*p}IhfCi~A-Y)20&U{e#p<_?}c^4P-3Ck)f9(2B#U>4mJTaW~O0$)+*q;>?7e zOBVw?qQJi3;TSm5vc4FLk=Twg!WF!c|s zy~boGjQ-yp*$CltcW)g}XQfpULz@CKQ8yxkRz9NPUzKUETEzvN>-DI72;jX=**73LdAL znjWOw{)))WsJ*2zUTk(4~(<1I$8D9<# zxk}7R00W1Qq}YROU1;X|J`K#c6<;?<3cE}K-eRlkYa z|MdCuslHfc{7(q{d+mC<_{Y z{RRQ>Rqdd46w!#x4`PG8ELZIajG&Rei-9$LNMfwD@uD@{@2xG-jsH+B|B2Cm!-woA zAqa#VOmT$}M^o9f09A1EmCx7PGExy7Ti#YCEcW$&zfnWL>?&%aM2hF16EqIV&bRKs zM(w{=;!XnM3N3zD^?FqaJlhl*V(@SE1JE%1-otC4*YUVuX5R@w+P5&xriSBs&Jma~ zCE7kWQgLp~H#HhQoLH-O$o6VgGB-CTCT3QCf%rrRRQ>-d2EY;UNDK1crk>Ubq0wj(U@%#ZIR;XzLA^g7mMo&* zegK==6#F6iF(X%7U96X)Q1W+M7`wS7~y8o5hwf5^}+3qYW}V8s_^6h_?=>r^xS0x2BV1hlhcw; z8D~d2&m$xX@*IXwH?8>cI%!n;jh;|dX(eic`t?y?v0|6Om_97Y2DIxchakr>hi)1Sdq-X)`M6k`j$ciL{ z-0R2qoNSrU`dSr$@Hbn}$Syyp!eBCj7p6p?@={RUfY?a?ZsTM1pZoL!1ZUSGe@>#g zM8>a+ZJ0)butH5z&<}>;0`Yv2OpIliOShyO8W!}Y+VY=&8dsM#=&z=T8>ep{fr5zqi zPDn6U9nc`@nt4RIjzS01&pM|*R2J5bfX!UoCDeR&=`hfB1Ya4Zu z%f5oL0NRbQjj@ig;*hgUH1CT^sAmZ(F!sn~LS2xMw2No5u}T2A!I)Kqi&-ctX_TfB zudb7H?t=k1%Gv4?8I3f})q-6JVlYp@v@@78-GHgbEN!wij70r!M0Dk zA*RcPR^6qSte3iv6UQ;}OD~)eU;*jUpLRrca2SIQ7wTA#li%7d{!>LoqR*6B;X@~s zDb|rSz|GfSX}CqDStsV#T*Cs}>|KDEAD94e|M7s_t`xb8p<5nP0zbB`dg^dly3o?u z582p=JpkpKf&~dHhN8<+NLXA^pNm7ikoa3LZ`w^v(6XFXqwHVuiyIBNlK*{W%7-SJ z_p^C^adC9|kKlp%@>l2WC}qP;N80an@f2?QeE01f-&tH0Sr)5x3P?NP)VE2k;~mRn zt>U73eJ38YUmWq^q?V?)$1TZIjpGEk6(U-Sa;%wlw*7*Z;x@$Lm z*!`Z6oy#`M@U?1g!xp=*B=&7Hl!96?B-;8DY@O5rVPbQy8YS)?ZAu+FRMHR96650D zPV$_}vc*#{*4Wt*7O?@*xY-FgM0!v9y3Tx6h8J-RW~JtF&2*4#Yoo!$5G^(2-~6<) z@(vmsg$iEppgx{IPvaAAFyg!(`JPkp_D14jqHYsH^1QGLSJ2*5-vYx~)wBK$Rip&) z1DG8E{fUGi;rx|WllO;v%gJGSK|z5q8RXRhKrI>8iIL*q;838;53ExpLbMMjA#GS- zxmhbbh|8c8uLwR0(K8bWqLbw;7Ny{j&qhxW8*Xa1TUvh-p?0ZZ16@wT-|0!QwySlF~?ei{ zOALmLpu)qSw$>~{v82LLNM$%-FSFv zvhq6ov&7XZeLsVqk0FXY2B2C&l+CSj#2#4(hmP7?1q=&Rb+gg68z^vXmg@ILRZBSS zS^7>50?r6%+e%JuGKe4W1gK5gjPH+YIzXbJ&if}DWivUHS-17`*j2UNPYPNg;^8*D zL_wmW>}PLwP#%hB39nsZ{)Bl%$aLx)hIS1|!+^`EhWjszqif5}D}z@+~ediDE)}qPFl?MY(pDO4wp~ z+)Pl3i)Yq+>UD;bn0c$=60Qs`ouLT(?lhn;8iW0z_W>Lg5%4%4wh(efB++k%` z?TDBR!5Bc6Jf-g2V;0B29{p_q2k10eMRI}^K=>8ls3@cnMFeIlDew66IoRb{x;aJy z2F1pn$G=G)hJFyU5n-6qMM2@8T}dt_eUJ|i5mbCh#M0*p_qtN*{q=ajL+nj&HqEw) zecF`mp_}tqyn?~y(HbgF2Q&aDcfAYK9@zLiak|~lh#pwPgv<#@RFQ6*;F(-S;5;|m z85#j9m0G7U=frZ)*AY@zztWh`kGGY#6~Lo?c?tskcClr(u#hB`*T(n#!Uk@v_uHK@ z0P)$+aF~ikAQT-wrq3fHBqStxa16|Dt-E-Az%)8B%^rcKZO4=4dC4!=YEh{4<=LPg zlIlAXKM#OD!bW>Vls}8m4>q)ANrX)o!-+=`+{kwmtWcU;UT`IBs+k0UUDPMoZQ1Jo~gWQNDz1e#UM9ODa*=gbrsKPjDpzo5NLm7?veda zL5_6om}8E_VSUZhToDB|Y=4`*0DEL}5qeDr03wR&E!)P*=KMK$X}Nlnc(e;$Sy{0^ zqi!0mXxfAt^CZ`J^?KSM&CB^?w-?EJY6sX|o$>K}&U)M8AGkbHhk{!q^eI%t1;haV z2!oeXbyN5Fr)OK6))jZA6uj@SdCRO7MBq2KP}GS|t{Lq2g@&D3`vy;Fs|~#ABD4Kd z?=R5%a6ZqV^U{l>18CY*x?THctt$S|)Fl*yO!p^bCfiG_B9 z;(!1rM8&z^{)oh9{U9S5DtLb&PvYQj+l+I7*2~?yPCIT6E@5pbketO@JcMK)(l@^M zLjRCZ5cimkU5VqO(JtksNInf*D*!fu(@fXY_u=ASJ=HAOy)fus5PLlRIl}Ih696@r zLvumCbW!=Zp=7=59(!f2J`=8CnQo47>tbzqw6%XE&7JN6pE6dH#X&C1$t7%EfIz#{Ol3jyN<|>zVCyh z2Hl!C(JnfNlT&nCo9v&9?NLJ!8g=fGT({SK)xoXemG6VB`?X^(JelgJ)oFcb(cly)?!k<^49tVqS*7A4GN7}ZPM0yDLZI<3!hpbyI zjp8S^w$iMyst{0n&?EFCX$gsm|QX8|B1Y@K{@a1+S5M6-oI z(ol@-WW+r;*1SK)z1E3M#;1GO5nk7>6L!{v$dZd5Rz_QJn=MRE6R^bnCf#0V*cCp! zA&DOGb}cMyWxEI2LR{|-MOGf~IjB%8V%zVah@H%{Xb$HsA_y{dH(+5-rYSuBDI_1k zL6C7~a(>%^e*e)udG7SOqmd4R4*l1QU$PM6@F>M>XgCJn-}r=-Qm>IpUMH1VGE86J zSK>M&;w5a3YM2S=foKaJB~zx=P{u4<)n&sw9eHFEy^Qm zZP|}ZO8TKfgu$nzJ?gE14xZUG!HFPH!}4zAr634XRsKh`S`wBdtf-S3Ql*jG^zueP>cJznI3^Waw?xya2*0{GtdLar`D~^fmhCwHaqL?J-jiyFSkv> zrsV5E!XM$>2C^pVEbwU*+22!~$X3y^HUw+r&X?%w2Eg<#VouRn23>xD^;@fD^PjcSZ5f~1!Grmaiq32BoSfk|~ zvIHphc+}_>5!y9|cI8plmG*pZBW`nrVv{H zy7&YH8N>}C(B|7|%VPcq;1s>!pT3P;DoL!}G;yHk+fse498H1-tOhQ#X$gkUB~!<<>iP(NheZdwlQQK;jEVLrP)6IHG5OksLz6_9SKxU+7Id?C)QpMJrBVIb zexv`eP7*$UhQ}V@e2dK!86{5EqR6KG(n_jO`@V6F_Q#J!t6+!w;^|^QzpA$xEdg`y z^~B2miY4VO4=jDH6YZ-`EKGM5FP6d*Dca$N95T+qlQ-r{K#f^ExtZwba^3<4YvCxe zED!@R+>pCQmHHw@J=}5BDWrDJ{fE6)KT=t=bGKsAzIYCh3V%?c#7gpi=bh>sPk%Ow+>X;Oeoz{cOS-5VOPL`xCBs}a@`S7LJF2&&kCSzJsd zz`$L+~l5Tk5JEN#sqYTC(?EcfMge{@^|K2sNaGX9S(c=%CPuvoKyoFIO?< zjxassskM8IlmK9U^2T=y)aDi@EK+Hh3PcD$bSB8gk|^^u)PNN~*Zdm?f4F@ZLEVLV zDB)i#S}DlF+AzyzwCsdBy~~fSC0V33931pA?_|jjbzG;R@^`<@`zrL0qYeCH0>Ijx zgB{ButG3jgL5RKCWV0WGQu!tVgkDk&%+}19qVl*p~$5O!}FUM7Q zAmt5KWY{A~z51UM(1 zAwNjm44c*$l9G~A73`a$EH{ZPuS<1yyVp>AbfVoL*K~U`a4y(oPZ*)kz5!f=h8OPJ z{Vtqf@usnY9Nhdopk=OO*FKM#f~;7SrI014uVx=x+CA{0H`*U_-sxBBBn38m3#5-e z+XeStc18N%po~gsu1i z_-`hTMVuEAiU*LEz^$IbvH5|s z#sg`IwLmIpGO2E1GNYSZX*)nZ>|jY)=-uP9cF8$Q;HL$&#}Nxeb|L5F!5N z^-H9+aF9%l3X;A^rx6+q*os8m1~}H3s0B})Iq+Cni3btzub;zw0`FA@NdEpQp}&Cy9exf^J$$60V)-!0jGy z)}}?Hzb2OF$r$t$-j#wt+}Z?**S=KeY@eW2wfbYc^#!9p#x8OCS>#yt)^tYN3KeSb zGQwaU#HXSWjpfLD=L@nA_dPaiWoR!`q7kZODHPvz03>}kDiq35T=up3`u^C&3v`;I zLkqj4+4coh=LVBK~A?Z$`FSofuefx;EpWgNF5EA zNCQt__r^>uC<<2cQx6x%#1(NFFV>9I;BoXu1<2wva z{?R?KU^d0|^>5N1kE<409k&PL6&0=A^kya%)=>}Wt$4q7&y-(d@?7IrNjz4fNO^ft z912j-q5@9Fu~hC~UJXgPoW_^RwC*Y?*oUeQ;79O^k774_+IaxHsRR5T=|eu)!mN^Zt)RyBmn;vJWYD^22pEod z!i8LwcsFP*ZjvSeT}ksNe{r7CGx6K{pVvCu)`QQ*ho%K2j%84~M(Mw}k4I<_E!&rl zvdU9AG~%th!wE%Dd?oo4MX(HimQ0Lxv&o3U2_4TDVKHP)qJeA@hr>?7Z0Z-GL0k@C zB9v2jBSLE&nDZlAL;{YXB6opwIhLOopgPs*XpFZk+Ol=FDY z#@=c_lzG+Q3o;Wj{9p07Nq$M7jqv09T3G}NVcNZxCb?x^TD1I7B$#OAa}dNHGvLm6 z7C%;3!CV!HD_qcbm&!>q05XRX(yqQH0lr{KlFb711L;1#kd;Ovs=-Qc`cldJ?2iY4`c?_JCCLmvxZuU ze};}@#JLT|<&B5>jAK zKev?R(zZqiY^hQX>aO0pF>1vSWkf^IA1l@h8Vgl|U@<8$)c7zY{lhbUdz|gb^2Bpq zz2;f2w9DUJdzx95yf4YxBt`RRQ1P~Zez&Y=W=N7nsAKpw8LY$ipFs=6u^g)(a6u^Zy zml2d!*Hku(OMTPxPI_eOCtiA!6<~IOxALGM!z3^_OKK``7pT^TkKQ5rV!keqq!Y?KA)XM4Is;J{NmSo7{-MPY-aI4O35grwO!|m$VDU+O^Esdw3;`!v0l3aIrm_)_cdvv(V8dME5%d@u68{aJh+K#}GUgMY zsMH)NAK@srzzJqj`pVgKM54m4rZPa|s^ouq4a8?3@kx1DJkz=Ok>GQY=+Fe%Dj&O;dn<0hsYvne1cAP(Ytzey5* z(vkemyXAD5wT=F|Z5)9lxABkxBG7Gn-s zV+vewv{Zv(^yENpOw^E0*K{vedT8<983Ge8@1q)b7Bavn@m`mQUx(PKJw66rdqy2NO2u80gyx8%&TdJ)Wrj*EK+uKs zVInMYvv@g|POx(nL0uIF3#75v6RQ`wH5Hl(o<$aBSeRG>DLqHDVC_S}pKPasv*N zeZ8`6hl6rUvByA7s!P@Z1usfEO+^qNN%aMsKV^jE- zY!x{Co#sPw(-L&nl?wA)_DWtECb*D4F|n~mMcFDwTQ7&`lo;QKj+lQez1OTKQJGaw>C(>@ zcF72JqD#qATNu^JFLFi)QMk%(OIn1SzDC**uZi+9^j1Fil1Uys`3S%x9Y^^nuld6* zR`NYMC-r4fR6uIrQ!%0G z+ylQ&RQdR));_QhRs9w>ZP>Vf(mc1=%IM9BXr=%ySi{}bIdef6Mp)1F(ZG*@<=`5C z|b zcYz<&Ldo!#6_wLMnEZ^O?TB)+@p<&4B@Q`2*fg8Vhly_HS8z-;s6@>adnZPPUgpNo z_1cC`ZTBl1)JNCN!XhHFlOnW>Z5`yW=yQ}_+$=v;pJZ!}}xb9$*o zu`LN=57z8&5TH?*j{G}4yQ~45SKA^p zGIJb3d@+93*pMb&zh&shgLDf)pgUz}@Mik;Ht%~WNAaiUa_z&SDCX(>PAbc%rUUg( z`jo~)t;CimaSd>p_Q*%o;%drQmhs3p_(+MXmwt*blfIs_X`!D;6=eOawg!*8{8#Sh zt!IyXK#Qx{pG*r;snOuGsu(I&)-|;6PpI!e^DE6Ysotfp6m{!1x3DtbF`qPLiHj-| zu^;okR$ylmjFNRYu%cctx8&P1>7a6)M~yxg$xkp*lBg|etGunsxNn5GJ29szF@9A2 z1hruY;ld&GEncMAuZ{NZ=ebLr!rO#eLN~4!#nCh}<#kD#$l|2r8NqbMoxz>?o%|de z!jig)W!W`>ih0wzBQ*3^HayH}^w%&Cf! z+UK4}x!w*LOC6oM0i$+;GF9K#^yz0?H`E5d;Zb#;{Y72&PwPl&?QuAdiqh7(&(|gH zMk~!CX8l#ixkHw>mnliB-e*Tl`rQS)Uezk|K{J(#aLy=fOnOkADUM+G(ukYAxQjhp zT>nq;IL#{a{@#TLR;AU^qqGj*1%0ctuMfRohg8w%sKe+bq{ifUmz27GCdemR(hUo2 z)0JBh&3jwyyK{@oyEWFXCL-WUnV+cm%oUeY>_=GZ%DPr(OIFqE8J+-xo}&wwt~cya zZQVwC*Ky<~YO49LFA-Z{d>1uMXO$&YN98*a%7fnTRwHB@v!gHhxVbv9mRE(K+6CWxIg6afVcim38uJ zF2J`?HnO*s$kOf3#}88{b^aLGP>YST7>1i z5i(hD9#xC;1qbEO=t%KQo|k9PO_Ql?i?+|l!{c%uzz|cG+pO*B{1Bb1FN){%SJMTz z{;q2<`F;uR&niUbwN9sCtJT`YE3}OEZibjp*q6@Iu=dzcX0)55iHecmoT)G69@XRK zJFg_9IgHb@*1~r|E7f1SLbNb5Sch=gVAPPqXhnANv%`^A@%lTw_Wb}QyvH(JshY+C{x`*u;_&p`ITSQ;eFLZU88g4sS*F2A3R%S0dy#dhM^~?#b>lw$H?h7tY)5D4`O|4bq#B;NFWW^8rcGsI zyv%5m>*aM^0nB4vnZ(RYaZ}P_2scHp_u@I%8+!l6H#B6r z&5QP@O8yp(q0aY$8Ruz!j8AJ61B$Nq1M9Y@P4jymjtH*KN7C`yyM42dMq} z1s4UO>poWqCuvDZ=UmU5UBDS35$t`$zUcgv2tIy?UR;UF#FokUdS&F~ zcwB9pByY6ww5o>@Ceip)UJg3AiIc!XDQ9ctV))%~u)y%mEyU~g7JxEwPmfUl*vds#q)8p<2-mG z*xo;(xtbF1VlbTBisq+jUC}MC!95A+q9jB{vO?SF z9(Hztto9=#SyB-Uq|yZ;sz|sw`s+nwk%`I{{|m^g`H{8^t@9a8NQK+uEN9#RL+C|h zD@}+?#zsRpOcL$mzWu$WOL@aE^=7Rijx2J_ydUwp?aNHC{#f+D9TpS>r>DZ;wF{?u z0|Z^`Bk5c)Q(0&JQO2H362wc;K%D;D;4osOUf4El^Ko{v0!0!VIzey4wuEdAOgN=N zd!UrcWY}W3%hwG>SMM|Q3Njkn-iLh{wRUa)Wyh5c=WQ{e)$G3br$)fM65Bpnh7XU% zSDEE|zzvJXBUM7TjEl=1KAbivKFzM?)55}n>#}D^-rdi(=Ulj!2jl_hB8?j}$FD*N zJ8Tgze(b!mO3+=8q6SNg`|lfL?i>L9b&?kms_ZY(P^9B2T@meFY54_E5QXCFO$o1_ zP>4&SjE4j*w#_t_1BH=g>2CVzh=pr=JrI%F3hGoHYG}?K-02M)f?Mt9 zNnLMtH843GbK#mA&NR*yvo|fH<*tTP_!2awt?IaCahf)@^uO-*LL09m@V&Nas4uD+ zCpE0fR9S^phRGUBS~k4D-Zq>$i(uW@YB0$XuFSx@QaA~*+{IYT_}%jd_NQ0~S?Udh zJD3e)^yfnocxoRXoU|W%;B2bfu1YQ|!b>%yU-rOpiusQU7il_O^FQ`Q_bR@D=wRkv za2gDu?(3)oVN7#t!=KZEEpz<{LNmyzshOE{TWZsJCb%5Y%mRuIAt5m%S$7n98RB(o z;cj|mFDt7QoMWFzqS7%~`ncerqDg4R-htO;yA`GlDp%!DA38#~=CRd55I+{&L`&f_ zx)%DXk+sBzRlj&DZH6MSG^kg;#~Yo!yYPbFaAj|WF*A(TT@Scl%>uBj^1N@p9@W$Q(ZE&P9$&v|1dR;`8GN zR8@o&vc1-6k^ub5cIG&TNXvbhDA!GCgmTj8Wi)cf9S{)+2}p9f&TTSBSrq(0{!$^I zQ{uTZQheUFmHBZEYP)Z#b)Xyt4yfumj9jpXTI?t8b*)M&W3@Cee3p+n;Y%$JKu>ffY??Mf>o-u4q4-E>>GG zY1YIr+0FOElgM^Cdv*Oj&U9Utxt{=5|VUs2!cudPNA!hXwZ}{O$vjFI(aQ2^V(jbGgI5`Ad$`$t`SL=3t?tdR3DieLh*!Yv6Nh`${jkm`Z$>4 z^hy3Mx-r^wpYSn0`+a`G`N8(we(j>EZaNxZEGa-KAU=*phd@jZLh$+X*1e>RR;-5W zdsx|%yz36sb^sN|PvLuNj8e@0x?rk|)RRJ@yn3k+Cl1MhHFqSWeC;@h zpb1z@_`Hm5S(Y{U0FZ#)`4V z%8UAobj}sFM71YnRC%5_i~oS zeC%AZRST5)NyG<^_mIBx^Sa4K-LFH{6!163C`gHIAZkZ>WOXr&0W~ak`}{um>)k^l z!(TIX(?0v_MM4dFOs^A{;B5ztw3#~pDuyZ*?kQ@Eg$-4U!7kz(QquS zr)w3~H#lcxR(CD<1#}x>Qy7LNc}C(v-f$ng9bbHLkT@hXA2{t)X5ubQCzy<-(8q{3e>uUhPHNR1Q(m5zYRjsfC05+{PTq;@}TNg0+} zzWwYCZ`Iuc}i zwLcg5Ey>I|Aw;>ClxI`F?vN+auhE39Z_xe>7-^f+rGA+>+0quN^Y*)z@&aVdR@kvr zLQ(4sr|d*s`@64r-g`S(zy07EX>9aXb>{*r3gXT4h0PdVxjA-ajbZ%7+g7^Km4_~o zJ0U{m7*8t>LED5gp|1A=t;7YIs$Lr~ajHd;o-9h5sdH5IiXgHau%Wg-=I@g<}{~6?ly+wnzt3D(NWzmXF z@!k+`&B&hD_DA12_}0-rb-K7I-H(vJngOpLAkj+N@8cvhSemh>QkvF1!?hpo%Fby4 z?TVV|Kyc84F}w@Rl$6k^^~WJ^*X0P-9{{vKOTUTo^0kJXAUtb0E|O!P z4tDX`?9K~EU|XDHzuv_+mL|ZZv>tTzxmQFo@IakufenhFA)2DN9AT8I_d9|if+beF zXk!tJFQ+3T1 z2N9DJA!!3HIo(Om!XX4UUcEKQ$J+ozRF?MGvPCWS{E9AXX5ni`5lJ@S-K!;K%PEoY zC4%2^6E##oGx>OyvG(IapH?|?E@avy=)LziT^0O6$R&F8D!)U2iq9qw2L)%Jb2hT2 zYuvfc&;R%5-gi!4xDY2%k?rPghvHKt-KjVV;p#tL$FDFy=&B%C^-+*0%mKj>W#MV{rE%KwT%9#8tFpapI<>*e zpI74I6~a+u?>*g$3ji)>it-S5LAxh`74b#?n6|)Pw;2D@|=%9Q~X$+ zxq%z4r^+cRK(Kix(c`##y^jU*P+HaHWd%ix!QwkkCFd`tDBTJdjI@R&5H5+HjShv+1D)eLpe($Oq-TzzrT8NFIMC2=)R9s{0MV~k?_Cx8EZGc0X%d4JjwV;w1Bep!`IIQx@_%=cH6kRlT8p@W{%*rR?9QlL32{<__H$*vCu@QyaU0QV70`3 z^o|yL`Vkamn3qJHiaT9Ul4up=S-uPTv|ZgeBkHlzojukJ$3~M!3qIZLWvE@tJ{hb^jopw={+i@4}wBV~fYRbE9=f(~zg|n^u5#v(q;A7LQ zU?>{{@T)qlTGC-l?rXK3#F4DF!P?6Q-xw#91kkHIt}GV(qvO zuF!$hCh`UF^}$HNqVAJT_9hieb6)*F{sS^Ewg8L1x>&|gj* zZb`CpE!_oGR~?ZK>4Y&GzI`paFISCJA`llC6h~Lmc{#w<3!Ac2GFDo@YV7jwmslF3 zU+S5c?XT(Iz~(1A{whkN3b@ za366hMuJ2Zp2kGoVmQ6ao^G;dp6x3qu9QT3*+EUZ2LgU2MBtJq8|-QFT(-OcZZ(r& zT<|Us_xt7CO2jZ929)Mm1s|hyfh6YqGtMZsuY7S7Vig0MAH>3?9dWosUiwz1wyql) z@d&n|oQ}?i;O-y9{lDgq$q?WjD3x}@#g_n#L4UM?+bH(CG z*S~V_ds50|nEC?RKaa2o^ha5=C}^>v<5P}11L+q@6TUBZaf4&u0ANHAMAT_r2m@Wi zD0-u31Bc%1h^X=-7!z#;T;iLrX@W@IYjftMI*}^Ew-j-P_W5ZM0Ztk(lY%0`42Lv6 zL8;Ve5Qj}ceHGmFicZUg_>*jOZ50Yo^rE4olPqrtlLzSntg2TG6>^A{w{+Um546~GPj`Y} z@mNp@+0kw5p3!lE+49A0xN*nBBXQ-lK!mrvZ>HdlQrus;yBAzLHg(#X711P^1;@Rm z1y7*BrnZi7nP5f3;f7PjxG8Bi68Z2Oe%0vQ^0Mt+HtVP~7JK1Ym5kbCE!_0AD?3~} zU-)10P@5eE7hH1MqFe;i=2e~c*qyDmesza)df%w)#{m05su^K;n@$#zYzIbz0Q2sv9 z^t<<6jNk?4u(?oPDz|-mgSBpNGKu%4V9gQyrYll)k%xIc*1?9r5-%44xtuoUz;KYa%pJhe1fxU2`=#H#{I3ZU z((HeJG|6Sk)n~+z$j?o-apMLiy5n=!25;y9-aw`N=>3q$si~}kah6Cz`g}apiaQ=c zAi!&+{25h;6IOZm4Kbpt*1lZed#T_j)BPfeL#^S_KkO-wf31Lo6UczrNXP*7mE z-*%gQ?DETP$^HypjTidTE&KYw_HWJAC-&IHt%XkuMs`lNEj)7} z$6DLbHoh8S2zjp54CkuJUG^8h_$8NB4QXX(SRwrGe+?DV01}d0AQm=l+yvLP#af$N zIf83&?vP~6$BY}pQP$|*{7B&gk8fX`d;#TNoMIoc8btfXZYXMiD-rb{<3eqEzTNJ) z9wj7*;iRuUZn($j&E!@~uFeqjY1JjhL8XC8JQZKwPB~0=T9^A}c*M z_tY7etq$=G?cfhPCe@BVGt+XANp@HTGa;xK-+ZF@6x&yc8u#OL(k!1(XPG@rfv6_=$ zqp73nybL0DI~?tU;2w+1P+28{I65v>`-xB~2QO`nyn{Q}zPH2?t zcZ@5uV_lcscT=;i-qCEk+S_ebLbfe9Hrr;;PxVcJV2}uj>LB7@aOyq^LVBz(#h&eZ z7qvkrCl9Vv%6mo8BGf;;@Jas|2mV_)6|Ou`fD_@g*X%j7?W{N6$OT@(T+fz*FgComSsaZ(BBP!8*bgtEsECnKNdfsedt61s2*cykx43 zz;RYNp-f%>9U;sKJ*=p$bdqbYz1DukhJGr_Hs`(fJUeB$Pf z4dV;CuHa1fts5Gxg^%)1Nl&)?ub(4!jgc^MJU&YG3{7y;ftl z0-`<>d#3Vby0E9E(~_|yqG*vG25H%a?PjMwF|ULFisJ2s@R7N}o?-fk^xP_7@5Zbhe$vAlUjR*4xxAB@1)x~0Lo z@fs=u+;fcz^n1ZI7+#10iQGN2LLBYr-K2?`PAKZvlXynF_|gq7t1Zs*LR{9Lea;X& z?$|sCb#!HlmO_BTVF0&@5Rxw^tX|h@mtDTW)^BKcCeSg*lDe01u0t zYU~SDY%(7@w8Sp`$ffK+yIuT5gmQXnx;^~x!^oZ0;Dq4wcG_w0glj4SpvxEcz_|)} zhHJ=M3s`M!o&5!ImVe%MJA<-;Pm9&n)X?dZtdL#N#Ho|)9AwSUJMTS~lbhq*CM}4* z2mo6UJ?TKXLu+Z=#@QRK|Ch>)RxK8(81XGG_icY}vOS0}EIdEUPJDN!bAKhqkdCaI z#-Gy3+vbWz?e-+{&a;n7wc=q(wt9IRA~{&x;GGt};S(TiQU1H_);hZu6GeVB+?Zv} z5VUfT0G((PD&f>JS@?=c#^M%)MODx_=Z%%Hl{!`JMBD`ChKT)>&P=y?C&J}~6QM~# zFiB}=%LXPh#6ra75RS!>Yk>IO#YRB{ww_BCx}p)Tb5)Ct>&Ubcl&~hisV*6jU=J^i z2=W5B@+ZFs&N9S=b4(~>+nNsBwV8ZaG)AFx>9W|!)k?NeoPXSH*6s&4AT{b=6fs!P7ve1Y#4#ebzhW*M;=a%o@+G+RS+-y6MGarWh{c#I3 z><|cWS#M}zLzGvP2o4f$$Lcm)$KPI-bDRkV z;Z9fojxH~_7rfeJ$P*^j@VqoZYfMP0nCv_U)h8|s&*!!HiXSV=&pFi;G-AYXyZ9p) z+mXl2!#v?qj2>>@X=sJ52M7OY0J zTiUR68~T1szd?l8KfK2(@7f9RyVu5jcBbVVSz_HS9aeGuR;zx#!ZJ8f>}f}Lp{m`= z{=U^x3p1_YtWiLvzDj$lzUqX007KRCu@KE0U{HNLbVTARPPExj+7K6UB;YLj)jF$s zXt%Z2wIVv0WQ}DFmOd)i<>PA}MA;0PWc6-sV6({ zHXzUazrQO(yaJP#G=^lP7oJ&Y!-ooahGHJ(_(Wh6CQZt;TW_7tof?ffmN_hNs`WcI zG;|v^c68@$nH1}YeaF;1g3Ys$gryx(Fg@q9gr%uiA zd0fx8u|}*%v2!LCSVGvotsUoLFC@(MUWC8a8_nU3$cW2KWC*hIvJ07cS@G&0W=@yV zHW&u3s!BvaQOcY=DU$V;NYP*ZTyEd}!FHQ5Et|PE*S?H`W`;XKiqA=o+%#$jLw^kX z;0Hf&(UUKJ@r(XSr@az;)@S{O4R+y07db)x-~$i5^+iwua>C-M-{Z0d#7cl=Wx8e+^+ci=WO|MuAlo1a&JXWsOu#~R25n1W6C9BaEXG9MHSGh^4mW|n{vEvhL%oJppk57xZ->L~SmGakw zC3vEsg*W29rZB(GEy`igCqrPSyZ8m55m$JHQw5h?=_GQ}23H}SiA+Ri6L86Htn$rd z@<~M=v8pMF4GKrjaJWx9HN)o4&w!BbhP%zAR10yjl}!xgr6RiLj6o)=3PfPaskh{) z3yTx2l4nAA3tXO#HZsoz2E3$eGSX&f_*jmxDOVeST#i_W4u=>9cM=~FvD3u64bOIZ zob+^XOatm2P70A5kL1crA-r`zlnu_Y?@YJDPJr_vLZ0+Fz%EnL@WSw`j6QQBh7tED&=5!ab>IThekMZwb{_n|YF zy*loPpcNOm_URXEj#M1>VZ-`R_0sU#GI&&1n2sCE5BwFCtN8u>?4yb zWjG4BI?iYQ6KC7|VyxeJJE#lN-kK*8AUql#tF+y}!v^*97yaUF|jo6L<{}uQ&Xos+Wtb_Z{^2e|ON@ z+&_$lpgxO@$?Db3E^_nmBh~h^Ut;V5Ty@ibmp`W zj;X6h--y2b^((j8cfQN~27xX`)*t_57i&>ymA)kxqA)px6XyzaqE@YLu*V;(x6@B8 zAbyYi-z`bm&kN!Y;Fh#(Mk1HP`q;HYB^y_LNrG_rL%D?7sURz)RN` z?aZ^!aM{%^&U?f)*D<Wav2YC|{2w*WE@H@*w81TyDHL{o+#i_E~4%U!R7BR2w8n+S(p@H>3SL9>}}W?sl( zFsCz{Nq$ba6WNytH%+Ghc5OrSgB|>s@#Gtm*%th5Y%KP6CnA=RWRqr~(1QG2^JWNi zaFhwtVC*!A(+Nq4t{{HG#L@~!U?lQ$X&E{~69|#%z7}Yi#Lh$;F+9b^I>h-FA)Es4 zgl8Sox5G7)6&luUP6d)}{j=>p;fn(zrAUP&qKs$hNu)79_ay?QocG`Ar-0>d3ZZ z(dD>zWFQxhVrRraA(0aD&BgQZU$HOYT_IeBWp9V*vHDiB@Jrp5OY|LbjKzs%8v1wi z^tJg`e+l`D2=)F0e1*k@HorKUpav>QM7KVQ{{rGQmLTLiQ2LkiEWoC6ynlj+@a)CX zhcdMR6X862)-3z!Pkv%E4w`0H|KSf-QC@CU<+b*^tAB6LKDW|-^rIi!k@F7s30WHu z9|dZl5NMPh=9;$BX6vZLDG7Zg1+I7Im>fhw*o1kTgF_cPVf@hF>ah{N{aY)oaaTRW zln5vBx+4Orr4+Fc@-I4Pj3s2IxZC!IC(Es~sl)P<3#{?k8r%BCr>$;jmDM)Z*}B$E z-WldhpJP)_pJpke(rx!mTb!^@K_e{U8*#Il~Lu*Rn=tl-p9ewLozo@#Ay%|~81 z*%H_&Z~s-jHDbS9Vl?R!P*797;ABcvW!HUMEs6QgQZPUHg*nm58Dp>dfn8Q}`%dd5 zUfPIE8?tbO%FiZ>`HbO9os_jOyxrtk^vIjbx8S zC~n)pJY)-`#=VfIPVHt#$`Gp@Sv8NHqdGNs+HZ$G!^{;QW`|xV1Q>mvQz$42s35d~TVoni{l`$(T zBOzWdzIcRv{|7sLbMepHt8Lr%Rzz*GZ0*`673Gxanh$w&4vK4w@s`; z-}&BFdx$oy#I)kWkJQ4TitLKF^<$uq8JC6`#nJZ7Z|`uf`HeSKLcllCw&_@tY9M{I zz)hH#YM;1#cyE#3Zj5K0OfOo5z3^RaHj)k34}UP;X2fyrdlS4(Z$`rDerVAm=XA>( z=KaulTBmYzbMfYUf_?6DpF<4lXvfamiU1Fjtgc#!v`FfzMYaSY{Ij3^EbCPw2I#M& zleJo5UZJ(4A-Lk1Wp?Rhm)Ie{I@mtXvBEL)k9I}QU>jAKR309fx6;zm?A&wDbVQ<{y-4 zryiAMdF+hW;;n2u1YcKck~@9r1}8({6c1}lvoSdtcH+_5cJjNkEZdb$fZYj9!2&QU zN9rj1>d}>+ZNv$+4Gi+bEgXCUycn#*gGD{M}+dCN8$LRoK?4x z?tlGW+qe^3;K-~dZ^^ck8ZwYwOfr|1#spS6g#8l_v?3SZ4Odg#ZgV+q$<@ouU^0{3 zrd60SL~*o@at}Hf3n@ot*zbSD#-*9l6iyVNtq?Jh!Vd%z4i4_ewBdFvupDw6_~i6J zoN$MahF-oHvx6MpRzMUr?#-~a-=p+e0~envNStQGIBfDd@eR2ap?e=v``_`tHwx+1 z$>-Iq}yIs?HQ#*Pz<0@^>lrR+=iO74^$gP;Q$8I`LL`c(KG zo~e9oe&gZt1aV*SY2qHg;5O9J39(q7%74HR9s{0lzPWmO()ospMA4?47xd{8#N0m1 zN!mOn+^c?Zl|A&}LzbDAVauLchRxqk*mu73T|4o_6TGNYWTLc5?B&XmMdzM2Ygk?d zv5W{Djh2NHp*{&X3-K`xoWg-NQLWqRZ2J#ZTFov@@FjOy@r+?M;o_O@5pwr+o2+(q zm6ct$*)k>~o^ez$oOSd=R#q7XJgp}IF4~s0R=l&w@>6oGC85L8u*W$nZ-k9Kb&?Ie zY_iqd2|-+rm5ebVHqZk`Dibj)Yk46D>(|sO`n=-S+fg$&P~NqOwPnSt6>A)1E*Gf zsRQ_XaF;GP1dZD6?Wl;U;nj}#QQr_@C3=Q(Vm|X0bNuvaxlT07oq2rt;6+32?z?O7 z{MZl?a)_izEG3z-aP(0*J`NTy7D3o!_up4%A3`icLEbfwnV$z?Qs*4wEr?ji?zd!} z&xVuyuRDtE_S-A{wDUnkH}p}TnmacW=0+->8f!I`x!ZQRS9d0r=REM2HUS~Dcd#DE z=9cj6rLI!uSCAtfnSLE8OvYT_Z+|(-zVPKOh@jL`m+n614yBwrdsd!(_uFF-SJ`LU zPkDXrvmyl0?;^)Ipn>`}zM~MQ7fK zI0}P4w}cMYY>!|gyNG56B+Z)iv|%4R$Ba~-y9 zYYW6#f=w()x3S1nAC1VwXv9VS@8=wMm0?RASwM*~xXd@uxy-ZR)JO9ZfH_>zZG;b> z!w~IY_uPnVxw!EhM;}66PR1dPIQOaS(o-Nv+JFOHSU}<)Zp)+pY_;3}*js@ zG|F20OeaKahpF1oYP?uHO)g0R@wl4cid$hL`a=TCiChTVT_8)bCc(na02=GhLL zJ~zc?ADwRFdEbnAs(;)8=dl{wuylYqC}0X-$%wVc^x!+F-(j??2r_ykUrzQn2GN9p{1NU)!4eBJ}4>3y$k z`LJ~OHS}d|X!VS(h{NOrsN)1!sH8U5-4La229)1|-9^W^`lg!-2kxiRc9Wk(6y(xe z$kekC1dC3(1d4!&?xV%b;S|19tdJ;FD3)%yS3aRGvu4k3)e*0Q!1QImYk8dh5~T=C4L4BOgN zYGoZ|Haw%mh8!{!JNHAZXi=WqWzWo){1&wAs{(I?f*I5_D zcQRb|Bsl$fBeJdd+%YJLCflxGtg-Ujw{nceJjU+2yA@mCa8xH;IMYhbpMV$)15~(C zF|=vg=xj?JnPIJ4n<*P{p2l{>d8#daGR|EPVdGKMwxQ9K?rjj{b#lRiA{#d@Chrkf zXZ0%)-V?FOJ$+V|ow0BTb3Tab7YZ>)_`Baub}&Kt z>iCt7>kuYRNVhA$IvR!s9x=DLK1o@e5wO20;qXhw}nwWpp$)QQbf78@wp=l;@{ zM)`dBt#59JsO-Vw2&a*ZJ3qM$nfJTubs@8)U^~ z8kSgIEQquG>t9W>n{JY&CfwKKnqK2p9P%TuseL)zv!{`1i)~e6n{EMH+OpIynMlnEi4%+W#~u6hw8VIglraJv=Gb|*?$&CM)$+=~zx zH+q6q*H%G{*E6A@FgU!JqqPML$PyN6#93nAU$lGGBBc+8Je+GJ260&2_rV(Ow9xB) z0xs3z)-UZqb}qpVLnd3!3tF*(t;J6S!p_Zbk0CH;LzqjBuLpVSdI-@S6&;qmEXlGU zWM$doJ!j?Fu#wo+#?;`X*~oh5CF0PAyx4(DrY)5@h;`G07BJyap%+fz^+$qBV;&B< zmN~D6zI?Gm6oZ9Y9Av$dL!M=r?b`%rSR8fP#U6rqi@5uek6y93-R^!=mK8cI2S-Vh znCz+=l5BWWx(!3V^;o#~qv0ltpchA38w7FA@w?<31QFEhR<_dy5DCc4ro%xj#EOe# z7jwHY9o3p<#o5SZgDb%!MIF^uME9|$WUyh0WT%z?zy8+3BpGcaYRbE;8k|4^Dcl4qM+-XQ$b5XJ%m9Fv*r99<&_`EUnae37$Bgd7{(K{%{uD zQt*tJjqq54C$>4WAsm<#w<15D!|8=YTqK7lPNEaxLfuOQZwE)b@Bq$f-xSOcc2kZv zK{911<<2NX?WpMS`HPU0zQXR^yS=SNAnKVo)jbehQpD0pPX~Qop@WXA{>H(Hb4%#} zJZ1Ajr)eim2YLj_$hfPocOjrJ+SMKJuv%*IZ)iY?j-8Hdu&dWdp;5$fYHFI7*@4!t z2yAV9b;Q?#V@$1AhfJxj;^5?=yH)AC$6JnO}+>R_&I|QUQ?@7SjhSGpwh~Q;B z^SrsGnZD3QT~Zm#q@^>;x=T*B++3r0o0(_zH4XOdZ-2`+uHRt)-_L%E$+cYiJcPam zJZS4g$*==QG6Eox{4?M@C*?C}G_HuExVmvdU*nuMO$MHn?i82b?x=0YN=de5&u0Fb zl5Op~TdV^Sj4l??#^*R9hiEe90TIt?rR32NLRTDQ^~=hw@u5oV$pRKmUOG8-YTMQ1 zocN3qPQMN*bfR3`Z4tIzSa9gBgP0;O??sH;u4+!vAgTp>lElD70H|}*DSNO+A{`3= z=eGzc6h?uq`sPY&DTfoDk!pEI7TbsqPI9Hw&I-H;{(8MtEh;5{<^}57fk<4|=u8{? zxjB}1Vu?B}?H1eaWj71tqtx&npeT`|YHcom8^hmwzl0+mOTP0W#tp$e(k1%X&B%UWfs$3#XoP3YM=HuxV_y#ful)SHAKUyXDqf?bd(% z1LFY)*%M2bzC8(Wp{ti@q*q0Y81r{bBqfO%;e->8wf+cavXo;A?HI! zAwRtf4f`EyQmhCQOAAiQv_lWivQ2AK;Yj1k9sIW;gD%2A{X>K)^qdXD#`)~|?l#o+ zU@|ZprN~;23adG0lyis;70tH37I{^2ld z+xdu-EDo|duZl=wBPsm|A5&g_#5GoXcS3|JKLt5V5fJ+GgaSw%nIPg_ruvd;06EA| zIuX6f-=lk3aIIsTR+Z=QQ-=DBxYXLzs?$mSS=omAsE)eVU;hobvVT2;2z9}n0%uyA z5p6p1vhuCIrP2QU*FV|&FZ_U=eDaB2SHaee@>T1mdh3E%RRvR69Mi`k|DC2ClVc-l zJNmgIrBKH=^0}!2{V38>*}qOHb{f}oiZZBI&mP5*%H;IuWdu##uN20Yk^NgLiss%-7(%B&jGuh+ z$%y%^u}2?!EhdTe1WbY0o?j z(S=7kxZ6E&QYFvnM6|N#o%y+iS`=5iqYGy-$SPqsF z6hir0+$8VqVggzi{O$g~h~|r8Lp7E^Hg#Y|F%fZxG%JI{D#A_-ClN7PWAtMN9wx^j z?jYON#n|>9+L>;X$EVr(AI`Jshp=EX2CJ}tT~*p)BgS(C%$zDtC2KK@91A7KUdcLE z?zd)zwJTg5YSkwOpWsl?qzW348^fDl>?m*pPIQ)H2C)h|<8tOB517Mn39r1AUb6OM zbH(uS9aNn}YR(Y;(s0Y!?Bk(_?S^__79TK3zsFjJOb zldvXo{3+Qsc}~P#S+lIuy;9c0Ay++VRydAAng~4z7=Y2{p%u7g8(p-_7KrB6D9miF zZS^)AQ=E<j8-&U^x~7r4yWX+!Z&oMG;T1L`FA^wSWTkr+^AIWCrID{VPXP zod@+15zeyXEi;gfjc`yQI@KRFX7ny70%zFA?P_bmk5=3DJf_HgO}OUghXsuxLz( zU>8SSeNC>eg$HdE5+RtQIRW^*BMWEX&)-G*6ym6g+u`j2>clbE`&0}gi_@+;rn6}j zXSNQ->xyzXyyBK~0-!$B2`63K1X*`UBaO=OkGQ~ur86#cuA#Ck^%X&I}xouojL@(?BaB;0i{JYi3w~BlOWiO zI`a_sC~&TL+h(lK(56nfs_(N2kS%c4H63@{T{!co-&AeetG8Q4TZNnYE4Y_Bv@tH( zaMrFv7I+o-1I21%*%I$;K(-$|bYbzKab=w)u&K0EfTJL7w(2+6q8!bAUaAd2;nEd0 zdEJe!M@J=N3@&+1XPvcnp`>=?Oe;EblmK*7igLcA5N#q!6zvsdj4H zO06c z%BsmFm;IN0@ZyVX(Zdhh29Aq&?%09-%ncBY*vUoESV!Sne02<+k&%IDNihn0&4{o( z<+jB$Q?jkOz1dDW=>%jxKTV#aJnz5(kFD>%=;`=h%L$j42wJS8C{hyKg?())OXg$N zu7KmA1x*~qBRZ0k5662G^2lpD-F692X*VUBG~7pygJTA9EI04>;w+{L zdF%_X$h9IwQB;Inv^#LzYX>aQVns&)4Dc=Zg+_6u1gNZw$dODgR!nMfT$3*oN|f{P zzi_#}r5nN-<(*T~>>xP5;y$YcKW-+Ex=NfUA=@fN#!TX+!)2CTT5-vxWFxt3$-pbf zOQc&Xfxt>7j`9(nM4-!Nxd`e)lufh2gF*&k7!t)OD>Y6Dr05c>pF}=bz$MZm0!O%9 z|4au2HL$=17XlIyQOSkcA5n!n0L zL@ake@Jn<>??e!4tcj2lk*oLmExGVUbS+$jgHG3hDWNn(Kg7M4IEcoxHV&$b#;8QY zBtoO!A)-MY7_wzqsxI*+p4`Pqtk}w0E%i&<4fv1#E4IoOC0S zla-U@VmmTxnuRw{IS;!2Z`ZrHOTykPC+}j&pZ77BQ z0dcO?<+i(`%o@6pI|n~qob=_C$jS+unlyAV*Z}xk#(nGVW;|0?y8UvuPYzLynK!L9 z002M$Nkl`_#yKvJiK)Ax4mn zoGF~`I_&EH@sEGvxH%aDT(Z2)HgDb$_LZ-G#b(T$;aSI*q2gG>e32LNsozp)+D9A( zX!>5Gc&sgON@XW|B;4&x7MnusO_z+uj2&DiM9%Hz-(dLzVlUWWm9k4ZoG%s7-4%&l z$e#5wWRjOHX-D%K4jcqUw2(#4XY@-fdJ%=byy+(&hdp+Uhi>0H;>XZgP?BgzpPJzw zDdn9~^3a>KF~IKle4fu+kOA>RzGS4u*##T;(Fkl!$V}(Mw31~M)p67W$kQsE${Wzg{Z35qLw`f6)WM^fEOPCNwV=JgH_UWaRdSy?AeNkp!)#uh*RxZQc@ zU04lR$w`M!1|ZZsu-G$tq@DMkbKTk3A%`7?C|FcS^;I8gSmBZEb$~ks+3zG2uEK8z zKUN{>O{jgv+3rR;+50@ADvI0uJa)nFWp|0?g7k4%8Cg+g&p)=pN^5snUNSa;JBzHY z74tr%?<%9yDI>Ax!D*5>9|Du-SGsm9*4y=JlF5= zOp0FP$B*}qKV#9-vA8&f)m7DQKm3u$9_45iE*!+Sh=VVE=}UIZamPTIMKZF

7;2 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

8vT(iu7`;CNX3xN1l^0}vRG&CSjC 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 g|1JH0pG&Xs??Ya!NE=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +all = ["Deprecated", "GeoAlchemy2", "GitPython (>2)", "JPype1", "PyAthena[sqlalchemy] (>=2.6.0,<3.0.0)", "PyYAML", "acryl-datahub-airflow-plugin (==0.12.1.3)", "acryl-datahub-classify (==0.0.8)", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "cachetools", "click (>=7.1.2)", "click-default-group", "click-spinner", "clickhouse-sqlalchemy (>=0.2.0,<0.2.5)", "confluent-kafka (>=1.9.0)", "cryptography", "cx-Oracle", "databricks-dbapi", "databricks-sdk (>=0.9.0,<0.16.0)", "databricks-sql-connector (>=2.8.0,<3.0.0)", "deltalake (>=0.6.3,!=0.6.4)", "docker", "elasticsearch (==7.13.4)", "expandvars (>=0.6.5)", "fastavro (>=1.2.0)", "feast (>=0.34.1,<0.35.0)", "filelock", "flask-openid (>=1.3.0)", "google-cloud-bigquery", "google-cloud-datacatalog-lineage (==0.2.2)", "google-cloud-logging (<=3.5.0)", "gql (>=3.3.0)", "gql[requests] (>=3.3.0)", "great-expectations", "great-expectations (!=0.15.23,!=0.15.24,!=0.15.25,!=0.15.26)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "grpcio (>=1.44.0,<2)", "grpcio-tools (>=1.44.0,<2)", "hdbcli (>=2.11.20)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "lark[regex] (==1.1.4)", "lkml (>=1.3.0b5)", "looker-sdk (==23.0.0)", "mlflow-skinny (>=2.3.0)", "more-itertools (>=8.12.0)", "moto[s3]", "msal", "msal (==1.22.0)", "nest-asyncio", "networkx (>=2.6.2)", "okta (>=1.7.0,<1.8.0)", "packaging", "pandas", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "psycopg2-binary", "pyOpenSSL", "pyarrow (>=6.0.1)", "pyarrow (>=9.0.0,<13.0.0)", "pydantic (<2)", "pydeequ (>=1.1.0,<1.2.0)", "pydruid (>=0.6.2)", "pyiceberg", "pymongo[srv] (>=3.11)", "pymysql (>=1.0.2)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "python-ldap (>=2.4)", "redash-toolbelt", "redshift-connector", "requests", "requests-file", "requests-gssapi", "requests-ntlm", "ruamel.yaml", "scipy (>=1.7.2)", "simple-salesforce", "smart-open[s3] (>=5.2.1)", "snowflake-connector-python (!=2.8.2)", "snowflake-sqlalchemy (>=1.4.3)", "spacy (==3.4.3)", "sql-metadata", "sql-metadata (==2.2.2)", "sqlalchemy", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-bigquery (>=1.4.1)", "sqlalchemy-hana (>=0.5.0)", "sqlalchemy-pytds (>=0.3)", "sqlalchemy-redshift (>=0.8.3)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tableauserverclient (>=0.17.0)", "tableschema (>=1.20.2)", "tabulate", "tenacity (>=8.0.1)", "teradatasqlalchemy (>=17.20.0.0)", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "trino[sqlalchemy] (>=0.308)", "typeguard (<3)", "ujson (>=5.2.0)", "vertica-sqlalchemy-dialect[vertica-python] (==0.0.8.1)", "wcmatch"] +athena = ["Deprecated", "PyAthena[sqlalchemy] (>=2.6.0,<3.0.0)", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-bigquery (>=1.4.1)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +azure-ad = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +base = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +bigquery = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "google-cloud-bigquery", "google-cloud-datacatalog-lineage (==0.2.2)", "google-cloud-logging (<=3.5.0)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "more-itertools (>=8.12.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-bigquery (>=1.4.1)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +circuit-breaker = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "gql (>=3.3.0)", "gql[requests] (>=3.3.0)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +clickhouse = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "clickhouse-sqlalchemy (>=0.2.0,<0.2.5)", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +clickhouse-usage = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "clickhouse-sqlalchemy (>=0.2.0,<0.2.5)", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +cloud = ["acryl-datahub-cloud"] +databricks = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "databricks-sdk (>=0.9.0,<0.16.0)", "databricks-sql-connector (>=2.8.0,<3.0.0)", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +datahub = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "confluent-kafka (>=1.9.0)", "docker", "expandvars (>=0.6.5)", "fastavro (>=1.2.0)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +datahub-business-glossary = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +datahub-kafka = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "confluent-kafka (>=1.9.0)", "docker", "expandvars (>=0.6.5)", "fastavro (>=1.2.0)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +datahub-lineage-file = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +datahub-lite = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "duckdb", "expandvars (>=0.6.5)", "fastapi", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "uvicorn"] +datahub-rest = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +dbt = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +dbt-cloud = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +debug = ["memray"] +delta-lake = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "deltalake (>=0.6.3,!=0.6.4)", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "more-itertools (>=8.12.0)", "moto[s3]", "packaging", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pydeequ (>=1.1.0,<1.2.0)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "smart-open[s3] (>=5.2.1)", "tableschema (>=1.20.2)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "ujson (>=5.2.0)", "wcmatch"] +dev = ["Deprecated", "GeoAlchemy2", "GitPython (>2)", "JPype1", "PyAthena[sqlalchemy] (>=2.6.0,<3.0.0)", "PyYAML", "acryl-datahub-classify (==0.0.8)", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "black (==22.12.0)", "boto3", "boto3-stubs[glue,s3,sagemaker,sts] (==1.28.15)", "botocore (!=1.23.0)", "build", "cached-property", "cachetools", "click (>=7.1.2)", "click-default-group", "click-spinner", "clickhouse-sqlalchemy (>=0.2.0,<0.2.5)", "confluent-kafka (>=1.9.0)", "coverage (>=5.1)", "cryptography", "cx-Oracle", "databricks-dbapi", "databricks-sdk (>=0.9.0,<0.16.0)", "databricks-sql-connector (>=2.8.0,<3.0.0)", "deepdiff", "deltalake (>=0.6.3,!=0.6.4)", "docker", "duckdb", "elasticsearch (==7.13.4)", "expandvars (>=0.6.5)", "faker (>=18.4.0)", "fastapi", "fastavro (>=1.2.0)", "feast (>=0.34.1,<0.35.0)", "flake8 (>=3.8.3)", "flake8-bugbear (==23.3.12)", "flake8-tidy-imports (>=4.3.0)", "flask-openid (>=1.3.0)", "freezegun", "google-cloud-bigquery", "google-cloud-datacatalog-lineage (==0.2.2)", "google-cloud-logging (<=3.5.0)", "great-expectations (!=0.15.23,!=0.15.24,!=0.15.25,!=0.15.26)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "grpcio (>=1.44.0,<2)", "grpcio-tools (>=1.44.0,<2)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "isort (>=5.7.0)", "jsonpickle", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "lark[regex] (==1.1.4)", "lkml (>=1.3.0b5)", "looker-sdk (==23.0.0)", "mixpanel (>=4.9.0)", "mlflow-skinny (>=2.3.0)", "more-itertools (>=8.12.0)", "moto[s3]", "msal", "msal (==1.22.0)", "mypy (==1.0.0)", "mypy-boto3-sagemaker (==1.28.15)", "mypy-extensions (>=0.4.3)", "nest-asyncio", "networkx (>=2.6.2)", "okta (>=1.7.0,<1.8.0)", "packaging", "pandas", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "psycopg2-binary", "pyarrow (>=6.0.1)", "pyarrow (>=9.0.0,<13.0.0)", "pydantic (<2)", "pydantic (>=1.10.0,!=1.10.3)", "pydeequ (>=1.1.0,<1.2.0)", "pydruid (>=0.6.2)", "pyiceberg", "pymysql (>=1.0.2)", "pyspark (>=3.3.0,<3.4.0)", "pytest (>=6.2.2)", "pytest-asyncio (>=0.16.0)", "pytest-cov (>=2.8.1)", "pytest-docker (>=1.0.1)", "python-dateutil (>=2.8.0)", "python-ldap (>=2.4)", "redash-toolbelt", "redshift-connector", "requests", "requests-file", "requests-gssapi", "requests-mock", "requests-ntlm", "ruamel.yaml", "scipy (>=1.7.2)", "sentry-sdk", "simple-salesforce", "smart-open[s3] (>=5.2.1)", "snowflake-connector-python (!=2.8.2)", "snowflake-sqlalchemy (>=1.4.3)", "spacy (==3.4.3)", "sql-metadata", "sql-metadata (==2.2.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-bigquery (>=1.4.1)", "sqlalchemy-redshift (>=0.8.3)", "sqlalchemy2-stubs", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tableauserverclient (>=0.17.0)", "tableschema (>=1.20.2)", "tabulate", "tenacity (>=8.0.1)", "teradatasqlalchemy (>=17.20.0.0)", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "trino[sqlalchemy] (>=0.308)", "twine", "typeguard (<3)", "types-Deprecated", "types-PyMySQL", "types-PyYAML", "types-cachetools", "types-click (==0.1.12)", "types-click-spinner (>=0.1.13.1)", "types-dataclasses", "types-freezegun", "types-pkg-resources", "types-protobuf (>=4.21.0.1)", "types-pyOpenSSL", "types-python-dateutil", "types-pytz", "types-requests (>=2.28.11.6,<=2.31.0.3)", "types-six", "types-tabulate", "types-termcolor (>=1.0.0)", "types-toml", "types-ujson (>=5.2.0)", "typing-extensions (>=3.7.4.3)", "typing-inspect", "ujson (>=5.2.0)", "uvicorn", "vertica-sqlalchemy-dialect[vertica-python] (==0.0.8.1)", "wcmatch"] +druid = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pydruid (>=0.6.2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +dynamodb = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +elasticsearch = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "elasticsearch (==7.13.4)", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +feast = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "feast (>=0.34.1,<0.35.0)", "flask-openid (>=1.3.0)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "typeguard (<3)"] +fivetran = ["Deprecated", "PyYAML", "acryl-datahub-classify (==0.0.8)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "cryptography", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "msal", "packaging", "pandas", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "snowflake-connector-python (!=2.8.2)", "snowflake-sqlalchemy (>=1.4.3)", "spacy (==3.4.3)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +gcs = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "more-itertools (>=8.12.0)", "moto[s3]", "packaging", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pydeequ (>=1.1.0,<1.2.0)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "smart-open[s3] (>=5.2.1)", "tableschema (>=1.20.2)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "ujson (>=5.2.0)", "wcmatch"] +glue = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +great-expectations = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +hana = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "hdbcli (>=2.11.20)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-hana (>=0.5.0)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +hive = ["Deprecated", "PyYAML", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "databricks-dbapi", "docker", "expandvars (>=0.6.5)", "great-expectations (!=0.15.23,!=0.15.24,!=0.15.25,!=0.15.26)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +iceberg = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pyarrow (>=9.0.0,<13.0.0)", "pydantic (<2)", "pyiceberg", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +integration-tests = ["JPype1", "PyAthena[sqlalchemy] (>=2.6.0,<3.0.0)", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "boto3", "botocore (!=1.23.0)", "clickhouse-sqlalchemy (>=0.2.0,<0.2.5)", "databricks-dbapi", "deltalake (>=0.6.3,!=0.6.4)", "feast (>=0.34.1,<0.35.0)", "flask-openid (>=1.3.0)", "gql (>=3.3.0)", "gql[requests] (>=3.3.0)", "great-expectations (!=0.15.23,!=0.15.24,!=0.15.25,!=0.15.26)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "hdbcli (>=2.11.20)", "more-itertools (>=8.12.0)", "moto[s3]", "packaging", "parse (>=1.19.0)", "pyOpenSSL", "pyarrow (>=6.0.1)", "pyarrow (>=9.0.0,<13.0.0)", "pydantic (<2)", "pydeequ (>=1.1.0,<1.2.0)", "pydruid (>=0.6.2)", "pyiceberg", "pymongo[srv] (>=3.11)", "pymysql (>=1.0.2)", "pyspark (>=3.3.0,<3.4.0)", "python-ldap (>=2.4)", "redash-toolbelt", "requests", "scipy (>=1.7.2)", "smart-open[s3] (>=5.2.1)", "sql-metadata", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-bigquery (>=1.4.1)", "sqlalchemy-hana (>=0.5.0)", "sqlalchemy-pytds (>=0.3)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tableschema (>=1.20.2)", "traitlets (<5.2.2)", "typeguard (<3)", "ujson (>=5.2.0)", "vertica-sqlalchemy-dialect[vertica-python] (==0.0.8.1)", "wcmatch"] +json-schema = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +kafka = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "confluent-kafka (>=1.9.0)", "docker", "expandvars (>=0.6.5)", "fastavro (>=1.2.0)", "grpcio (>=1.44.0,<2)", "grpcio-tools (>=1.44.0,<2)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "networkx (>=2.6.2)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +kafka-connect = ["Deprecated", "JPype1", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +ldap = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "python-ldap (>=2.4)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +looker = ["Deprecated", "GitPython (>2)", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "lkml (>=1.3.0b5)", "looker-sdk (==23.0.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "sql-metadata (==2.2.2)", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +lookml = ["Deprecated", "GitPython (>2)", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "lkml (>=1.3.0b5)", "looker-sdk (==23.0.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "sql-metadata (==2.2.2)", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +mariadb = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +metabase = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +mlflow = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "mlflow-skinny (>=2.3.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +mode = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tabulate", "tenacity (>=8.0.1)", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +mongodb = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pymongo[srv] (>=3.11)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +mssql = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pyOpenSSL", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-pytds (>=0.3)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +mssql-odbc = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pyodbc", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +mysql = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +nifi = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "requests-gssapi", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +okta = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "nest-asyncio", "okta (>=1.7.0,<1.8.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +oracle = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "cx-Oracle", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +postgres = ["Deprecated", "GeoAlchemy2", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "psycopg2-binary", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +powerbi = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "lark[regex] (==1.1.4)", "msal (==1.22.0)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +powerbi-report-server = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "requests-ntlm", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +presto = ["Deprecated", "PyYAML", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "trino[sqlalchemy] (>=0.308)"] +presto-on-hive = ["Deprecated", "PyYAML", "acryl-pyhive[hive-pure-sasl] (==0.6.16)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "psycopg2-binary", "pydantic (<2)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +pulsar = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +redash = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "redash-toolbelt", "requests-file", "ruamel.yaml", "sql-metadata", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +redshift = ["Deprecated", "GeoAlchemy2", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "cachetools", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "redshift-connector", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlalchemy-redshift (>=0.8.3)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "wcmatch"] +s3 = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "more-itertools (>=8.12.0)", "moto[s3]", "packaging", "parse (>=1.19.0)", "progressbar2", "psutil (>=5.8.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pydeequ (>=1.1.0,<1.2.0)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "smart-open[s3] (>=5.2.1)", "tableschema (>=1.20.2)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "ujson (>=5.2.0)", "wcmatch"] +sagemaker = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "boto3", "botocore (!=1.23.0)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +salesforce = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "simple-salesforce", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +snowflake = ["Deprecated", "PyYAML", "acryl-datahub-classify (==0.0.8)", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "cryptography", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "msal", "packaging", "pandas", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "snowflake-connector-python (!=2.8.2)", "snowflake-sqlalchemy (>=1.4.3)", "spacy (==3.4.3)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +sql-parser = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +sql-queries = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +sqlalchemy = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +starburst-trino-usage = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "trino[sqlalchemy] (>=0.308)"] +superset = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "sqlalchemy", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +sync-file-emitter = ["Deprecated", "PyYAML", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "filelock", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +tableau = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "sqllineage (==1.3.8)", "sqlparse (==0.4.4)", "tableauserverclient (>=0.17.0)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)"] +teradata = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "teradatasqlalchemy (>=17.20.0.0)", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +testing-utils = ["PyYAML", "deepdiff", "pytest (>=6.2.2)"] +trino = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "trino[sqlalchemy] (>=0.308)"] +unity-catalog = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "databricks-sdk (>=0.9.0,<0.16.0)", "databricks-sql-connector (>=2.8.0,<3.0.0)", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "pyspark (>=3.3.0,<3.4.0)", "python-dateutil (>=2.8.0)", "requests", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqllineage (==1.3.8)", "sqlparse", "sqlparse (==0.4.4)", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)"] +vertica = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (<4)", "avro (>=1.11.3,<1.12)", "avro-gen3 (==0.7.11)", "cached-property", "click (>=7.1.2)", "click-default-group", "click-spinner", "docker", "expandvars (>=0.6.5)", "great-expectations (>=0.15.12,<=0.15.50)", "greenlet", "humanfriendly", "ijson", "importlib-metadata (>=4.0.0)", "jsonref", "jsonschema", "jsonschema (<=4.17.3)", "packaging", "progressbar2", "psutil (>=5.8.0)", "pydantic (<2)", "python-dateutil (>=2.8.0)", "requests-file", "ruamel.yaml", "scipy (>=1.7.2)", "sqlalchemy (>=1.4.39,<2)", "sqlparse", "tabulate", "termcolor (>=1.0.0)", "toml (>=0.10.0)", "traitlets (<5.2.2)", "vertica-sqlalchemy-dialect[vertica-python] (==0.0.8.1)"] + +[[package]] +name = "aiohttp" +version = "3.9.1" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, + {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, + {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, + {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, + {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, + {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, + {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, + {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, + {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, + {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, + {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, + {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] name = "antlr4-python3-runtime" @@ -21,6 +252,17 @@ files = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + [[package]] name = "attrs" version = "23.1.0" @@ -53,6 +295,21 @@ files = [ snappy = ["python-snappy"] zstandard = ["zstandard"] +[[package]] +name = "avro-gen3" +version = "0.7.11" +description = "Avro record class and specific record reader generator" +optional = false +python-versions = "*" +files = [ + {file = "avro-gen3-0.7.11.tar.gz", hash = "sha256:70f76794cbce1e2613d8e9d4106f4ed9513ef7d19cdd1b125f4392d0d21f5cc8"}, + {file = "avro_gen3-0.7.11-py3-none-any.whl", hash = "sha256:8c4489ebe76d25dd58e27965acd32f78a245251e7e1630e0983694816109905d"}, +] + +[package.dependencies] +avro = ">=1.10" +six = "*" + [[package]] name = "beautifulsoup4" version = "4.12.2" @@ -330,6 +587,37 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-default-group" +version = "1.2.4" +description = "click_default_group" +optional = false +python-versions = ">=2.7" +files = [ + {file = "click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f"}, + {file = "click_default_group-1.2.4.tar.gz", hash = "sha256:eb3f3c99ec0d456ca6cd2a7f08f7d4e91771bef51b01bdd9580cc6450fe1251e"}, +] + +[package.dependencies] +click = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "click-spinner" +version = "0.1.10" +description = "Spinner for Click" +optional = false +python-versions = "*" +files = [ + {file = "click-spinner-0.1.10.tar.gz", hash = "sha256:87eacf9d7298973a25d7615ef57d4782aebf913a532bba4b28a37e366e975daf"}, + {file = "click_spinner-0.1.10-py2.py3-none-any.whl", hash = "sha256:d1ffcff1fdad9882396367f15fb957bcf7f5c64ab91927dee2127e0d2991ee84"}, +] + +[package.extras] +test = ["click", "pytest", "six"] + [[package]] name = "collate-sqllineage" version = "1.1.5" @@ -420,6 +708,41 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "deepdiff" +version = "6.7.1" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +optional = false +python-versions = ">=3.7" +files = [ + {file = "deepdiff-6.7.1-py3-none-any.whl", hash = "sha256:58396bb7a863cbb4ed5193f548c56f18218060362311aa1dc36397b2f25108bd"}, + {file = "deepdiff-6.7.1.tar.gz", hash = "sha256:b367e6fa6caac1c9f500adc79ada1b5b1242c50d5f716a1a4362030197847d30"}, +] + +[package.dependencies] +ordered-set = ">=4.0.2,<4.2.0" + +[package.extras] +cli = ["click (==8.1.3)", "pyyaml (==6.0.1)"] +optimize = ["orjson"] + +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "diff-cover" version = "8.0.1" @@ -459,6 +782,27 @@ idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.23)"] wmi = ["wmi (>=1.5.1,<2.0.0)"] +[[package]] +name = "docker" +version = "7.0.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.8" +files = [ + {file = "docker-7.0.0-py3-none-any.whl", hash = "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b"}, + {file = "docker-7.0.0.tar.gz", hash = "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3"}, +] + +[package.dependencies] +packaging = ">=14.0" +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" + +[package.extras] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] + [[package]] name = "ecdsa" version = "0.18.0" @@ -506,6 +850,120 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "expandvars" +version = "0.12.0" +description = "Expand system variables Unix style" +optional = false +python-versions = ">=3" +files = [ + {file = "expandvars-0.12.0-py3-none-any.whl", hash = "sha256:7432c1c2ae50c671a8146583177d60020dd210ada7d940e52af91f1f84f753b2"}, + {file = "expandvars-0.12.0.tar.gz", hash = "sha256:7d1adfa55728cf4b5d812ece3d087703faea953e0c0a1a78415de9df5024d844"}, +] + +[package.extras] +tests = ["black", "pytest", "pytest-cov", "tox"] + +[[package]] +name = "freezegun" +version = "1.4.0" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.7" +files = [ + {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, + {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + [[package]] name = "google" version = "3.0.0" @@ -747,6 +1205,20 @@ grpcio = ">=1.59.2" protobuf = ">=4.21.6,<5.0dev" setuptools = "*" +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + [[package]] name = "idna" version = "2.10" @@ -758,6 +1230,104 @@ files = [ {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +[[package]] +name = "ijson" +version = "3.2.3" +description = "Iterative JSON parser with standard Python iterator interfaces" +optional = false +python-versions = "*" +files = [ + {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a4ae076bf97b0430e4e16c9cb635a6b773904aec45ed8dcbc9b17211b8569ba"}, + {file = "ijson-3.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cfced0a6ec85916eb8c8e22415b7267ae118eaff2a860c42d2cc1261711d0d31"}, + {file = "ijson-3.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b9d1141cfd1e6d6643aa0b4876730d0d28371815ce846d2e4e84a2d4f471cf3"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0a27db6454edd6013d40a956d008361aac5bff375a9c04ab11fc8c214250b5"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0d526ccb335c3c13063c273637d8611f32970603dfb182177b232d01f14c23"}, + {file = "ijson-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:545a30b3659df2a3481593d30d60491d1594bc8005f99600e1bba647bb44cbb5"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9680e37a10fedb3eab24a4a7e749d8a73f26f1a4c901430e7aa81b5da15f7307"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2a80c0bb1053055d1599e44dc1396f713e8b3407000e6390add72d49633ff3bb"}, + {file = "ijson-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f05ed49f434ce396ddcf99e9fd98245328e99f991283850c309f5e3182211a79"}, + {file = "ijson-3.2.3-cp310-cp310-win32.whl", hash = "sha256:b4eb2304573c9fdf448d3fa4a4fdcb727b93002b5c5c56c14a5ffbbc39f64ae4"}, + {file = "ijson-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:923131f5153c70936e8bd2dd9dcfcff43c67a3d1c789e9c96724747423c173eb"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:904f77dd3d87736ff668884fe5197a184748eb0c3e302ded61706501d0327465"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0974444c1f416e19de1e9f567a4560890095e71e81623c509feff642114c1e53"}, + {file = "ijson-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1a4b8eb69b6d7b4e94170aa991efad75ba156b05f0de2a6cd84f991def12ff9"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d052417fd7ce2221114f8d3b58f05a83c1a2b6b99cafe0b86ac9ed5e2fc889df"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b8064a85ec1b0beda7dd028e887f7112670d574db606f68006c72dd0bb0e0e2"}, + {file = "ijson-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaac293853f1342a8d2a45ac1f723c860f700860e7743fb97f7b76356df883a8"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6c32c18a934c1dc8917455b0ce478fd7a26c50c364bd52c5a4fb0fc6bb516af7"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:713a919e0220ac44dab12b5fed74f9130f3480e55e90f9d80f58de129ea24f83"}, + {file = "ijson-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a3a6a2fbbe7550ffe52d151cf76065e6b89cfb3e9d0463e49a7e322a25d0426"}, + {file = "ijson-3.2.3-cp311-cp311-win32.whl", hash = "sha256:6a4db2f7fb9acfb855c9ae1aae602e4648dd1f88804a0d5cfb78c3639bcf156c"}, + {file = "ijson-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:ccd6be56335cbb845f3d3021b1766299c056c70c4c9165fb2fbe2d62258bae3f"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:055b71bbc37af5c3c5861afe789e15211d2d3d06ac51ee5a647adf4def19c0ea"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c075a547de32f265a5dd139ab2035900fef6653951628862e5cdce0d101af557"}, + {file = "ijson-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:457f8a5fc559478ac6b06b6d37ebacb4811f8c5156e997f0d87d708b0d8ab2ae"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9788f0c915351f41f0e69ec2618b81ebfcf9f13d9d67c6d404c7f5afda3e4afb"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa234ab7a6a33ed51494d9d2197fb96296f9217ecae57f5551a55589091e7853"}, + {file = "ijson-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd0dc5da4f9dc6d12ab6e8e0c57d8b41d3c8f9ceed31a99dae7b2baf9ea769a"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c6beb80df19713e39e68dc5c337b5c76d36ccf69c30b79034634e5e4c14d6904"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a2973ce57afb142d96f35a14e9cfec08308ef178a2c76b8b5e1e98f3960438bf"}, + {file = "ijson-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:105c314fd624e81ed20f925271ec506523b8dd236589ab6c0208b8707d652a0e"}, + {file = "ijson-3.2.3-cp312-cp312-win32.whl", hash = "sha256:ac44781de5e901ce8339352bb5594fcb3b94ced315a34dbe840b4cff3450e23b"}, + {file = "ijson-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:0567e8c833825b119e74e10a7c29761dc65fcd155f5d4cb10f9d3b8916ef9912"}, + {file = "ijson-3.2.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eeb286639649fb6bed37997a5e30eefcacddac79476d24128348ec890b2a0ccb"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:396338a655fb9af4ac59dd09c189885b51fa0eefc84d35408662031023c110d1"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e0243d166d11a2a47c17c7e885debf3b19ed136be2af1f5d1c34212850236ac"}, + {file = "ijson-3.2.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85afdb3f3a5d0011584d4fa8e6dccc5936be51c27e84cd2882fe904ca3bd04c5"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4fc35d569eff3afa76bfecf533f818ecb9390105be257f3f83c03204661ace70"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:455d7d3b7a6aacfb8ab1ebcaf697eedf5be66e044eac32508fccdc633d995f0e"}, + {file = "ijson-3.2.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c63f3d57dbbac56cead05b12b81e8e1e259f14ce7f233a8cbe7fa0996733b628"}, + {file = "ijson-3.2.3-cp36-cp36m-win32.whl", hash = "sha256:a4d7fe3629de3ecb088bff6dfe25f77be3e8261ed53d5e244717e266f8544305"}, + {file = "ijson-3.2.3-cp36-cp36m-win_amd64.whl", hash = "sha256:96190d59f015b5a2af388a98446e411f58ecc6a93934e036daa75f75d02386a0"}, + {file = "ijson-3.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35194e0b8a2bda12b4096e2e792efa5d4801a0abb950c48ade351d479cd22ba5"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1053fb5f0b010ee76ca515e6af36b50d26c1728ad46be12f1f147a835341083"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:211124cff9d9d139dd0dfced356f1472860352c055d2481459038b8205d7d742"}, + {file = "ijson-3.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92dc4d48e9f6a271292d6079e9fcdce33c83d1acf11e6e12696fb05c5889fe74"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3dcc33ee56f92a77f48776014ddb47af67c33dda361e84371153c4f1ed4434e1"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:98c6799925a5d1988da4cd68879b8eeab52c6e029acc45e03abb7921a4715c4b"}, + {file = "ijson-3.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4252e48c95cd8ceefc2caade310559ab61c37d82dfa045928ed05328eb5b5f65"}, + {file = "ijson-3.2.3-cp37-cp37m-win32.whl", hash = "sha256:644f4f03349ff2731fd515afd1c91b9e439e90c9f8c28292251834154edbffca"}, + {file = "ijson-3.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ba33c764afa9ecef62801ba7ac0319268a7526f50f7601370d9f8f04e77fc02b"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4b2ec8c2a3f1742cbd5f36b65e192028e541b5fd8c7fd97c1fc0ca6c427c704a"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7dc357da4b4ebd8903e77dbcc3ce0555ee29ebe0747c3c7f56adda423df8ec89"}, + {file = "ijson-3.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bcc51c84bb220ac330122468fe526a7777faa6464e3b04c15b476761beea424f"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8d54b624629f9903005c58d9321a036c72f5c212701bbb93d1a520ecd15e370"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6ea7c7e3ec44742e867c72fd750c6a1e35b112f88a917615332c4476e718d40"}, + {file = "ijson-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:916acdc5e504f8b66c3e287ada5d4b39a3275fc1f2013c4b05d1ab9933671a6c"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81815b4184b85ce124bfc4c446d5f5e5e643fc119771c5916f035220ada29974"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b49fd5fe1cd9c1c8caf6c59f82b08117dd6bea2ec45b641594e25948f48f4169"}, + {file = "ijson-3.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:86b3c91fdcb8ffb30556c9669930f02b7642de58ca2987845b04f0d7fe46d9a8"}, + {file = "ijson-3.2.3-cp38-cp38-win32.whl", hash = "sha256:a729b0c8fb935481afe3cf7e0dadd0da3a69cc7f145dbab8502e2f1e01d85a7c"}, + {file = "ijson-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:d34e049992d8a46922f96483e96b32ac4c9cffd01a5c33a928e70a283710cd58"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9c2a12dcdb6fa28f333bf10b3a0f80ec70bc45280d8435be7e19696fab2bc706"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1844c5b57da21466f255a0aeddf89049e730d7f3dfc4d750f0e65c36e6a61a7c"}, + {file = "ijson-3.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ec3e5ff2515f1c40ef6a94983158e172f004cd643b9e4b5302017139b6c96e4"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46bafb1b9959872a1f946f8dd9c6f1a30a970fc05b7bfae8579da3f1f988e598"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab4db9fee0138b60e31b3c02fff8a4c28d7b152040553b6a91b60354aebd4b02"}, + {file = "ijson-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4bc87e69d1997c6a55fff5ee2af878720801ff6ab1fb3b7f94adda050651e37"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e9fd906f0c38e9f0bfd5365e1bed98d649f506721f76bb1a9baa5d7374f26f19"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e84d27d1acb60d9102728d06b9650e5b7e5cb0631bd6e3dfadba8fb6a80d6c2f"}, + {file = "ijson-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2cc04fc0a22bb945cd179f614845c8b5106c0b3939ee0d84ce67c7a61ac1a936"}, + {file = "ijson-3.2.3-cp39-cp39-win32.whl", hash = "sha256:e641814793a037175f7ec1b717ebb68f26d89d82cfd66f36e588f32d7e488d5f"}, + {file = "ijson-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:6bd3e7e91d031f1e8cea7ce53f704ab74e61e505e8072467e092172422728b22"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06f9707da06a19b01013f8c65bf67db523662a9b4a4ff027e946e66c261f17f0"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be8495f7c13fa1f622a2c6b64e79ac63965b89caf664cc4e701c335c652d15f2"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7596b42f38c3dcf9d434dddd50f46aeb28e96f891444c2b4b1266304a19a2c09"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbac4e9609a1086bbad075beb2ceec486a3b138604e12d2059a33ce2cba93051"}, + {file = "ijson-3.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:db2d6341f9cb538253e7fe23311d59252f124f47165221d3c06a7ed667ecd595"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fa8b98be298efbb2588f883f9953113d8a0023ab39abe77fe734b71b46b1220a"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:674e585361c702fad050ab4c153fd168dc30f5980ef42b64400bc84d194e662d"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd12e42b9cb9c0166559a3ffa276b4f9fc9d5b4c304e5a13668642d34b48b634"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d31e0d771d82def80cd4663a66de277c3b44ba82cd48f630526b52f74663c639"}, + {file = "ijson-3.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ce4c70c23521179d6da842bb9bc2e36bb9fad1e0187e35423ff0f282890c9ca"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39f551a6fbeed4433c85269c7c8778e2aaea2501d7ebcb65b38f556030642c17"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b14d322fec0de7af16f3ef920bf282f0dd747200b69e0b9628117f381b7775b"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7851a341429b12d4527ca507097c959659baf5106c7074d15c17c387719ffbcd"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3bf1b42191b5cc9b6441552fdcb3b583594cb6b19e90d1578b7cbcf80d0fae"}, + {file = "ijson-3.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6f662dc44362a53af3084d3765bb01cd7b4734d1f484a6095cad4cb0cbfe5374"}, + {file = "ijson-3.2.3.tar.gz", hash = "sha256:10294e9bf89cb713da05bc4790bdff616610432db561964827074898e174f917"}, +] + [[package]] name = "importlib-metadata" version = "6.8.0" @@ -841,6 +1411,17 @@ files = [ {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, ] +[[package]] +name = "jsonref" +version = "1.1.0" +description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, + {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, +] + [[package]] name = "jsonschema" version = "4.19.2" @@ -959,6 +1540,105 @@ files = [ [package.dependencies] psutil = "*" +[[package]] +name = "mixpanel" +version = "4.10.0" +description = "Official Mixpanel library for Python" +optional = false +python-versions = ">=2.7, !=3.4.*" +files = [ + {file = "mixpanel-4.10.0-py2.py3-none-any.whl", hash = "sha256:bbf6ebe5b79556931e1c6b67b4b9e0a1fab60f81638a35c167fca62e7c05398d"}, + {file = "mixpanel-4.10.0.tar.gz", hash = "sha256:87733b1fa966bb4edc265a97d10ee71e22174e7a17a9ed6c739468b0eba88d3b"}, +] + +[package.dependencies] +requests = ">=2.4.2" +six = ">=1.9.0" +urllib3 = "*" + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1100,6 +1780,20 @@ test = ["apache-airflow (==2.6.3)", "coverage", "databricks-sdk (>=0.1,<1.0)", " trino = ["trino[sqlalchemy]"] vertica = ["sqlalchemy-vertica[vertica-python] (>=0.0.5)"] +[[package]] +name = "ordered-set" +version = "4.1.0" +description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, + {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, +] + +[package.extras] +dev = ["black", "mypy", "pytest"] + [[package]] name = "packaging" version = "23.2" @@ -1137,6 +1831,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "progressbar2" +version = "4.3.2" +description = "A Python Progressbar library to provide visual (yet text based) progress to long running operations." +optional = false +python-versions = ">=3.8" +files = [ + {file = "progressbar2-4.3.2-py3-none-any.whl", hash = "sha256:036fa3bd35ae27c92e73fce4fb18aa4ba5090a1880d880cf954ecb75ccd6f3fb"}, + {file = "progressbar2-4.3.2.tar.gz", hash = "sha256:c37e6e1b4e57ab43f95c3d0e8d90061bec140e4fed56b8343183db3aa1e19a52"}, +] + +[package.dependencies] +python-utils = ">=3.8.1" + +[package.extras] +docs = ["sphinx (>=1.8.5)", "sphinx-autodoc-typehints (>=1.6.0)"] +tests = ["dill (>=0.3.6)", "flake8 (>=3.7.7)", "freezegun (>=0.3.11)", "pytest (>=4.6.9)", "pytest-cov (>=2.6.1)", "pytest-mypy", "sphinx (>=1.8.5)"] + [[package]] name = "protobuf" version = "4.25.0" @@ -1302,6 +2014,17 @@ files = [ ed25519 = ["PyNaCl (>=1.4.0)"] rsa = ["cryptography"] +[[package]] +name = "pyreadline3" +version = "3.4.1" +description = "A python implementation of GNU readline." +optional = false +python-versions = "*" +files = [ + {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, + {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, +] + [[package]] name = "pytest" version = "7.4.3" @@ -1359,6 +2082,48 @@ cryptography = ["cryptography (>=3.4.0)"] pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] +[[package]] +name = "python-utils" +version = "3.8.1" +description = "Python Utils is a module with some convenient utilities not included with the standard Python install" +optional = false +python-versions = ">3.8.0" +files = [ + {file = "python-utils-3.8.1.tar.gz", hash = "sha256:ec3a672465efb6c673845a43afcfafaa23d2594c24324a40ec18a0c59478dc0b"}, + {file = "python_utils-3.8.1-py2.py3-none-any.whl", hash = "sha256:efdf31c8154667d7dc0317547c8e6d3b506c5d4b6e360e0c89662306262fc0ab"}, +] + +[package.dependencies] +typing-extensions = ">3.10.0.2" + +[package.extras] +docs = ["mock", "python-utils", "sphinx"] +loguru = ["loguru"] +tests = ["flake8", "loguru", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mypy", "sphinx", "types-setuptools"] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -1569,6 +2334,21 @@ six = "*" [package.extras] httpx = ["httpx"] +[[package]] +name = "requests-file" +version = "1.5.1" +description = "File transport adapter for Requests" +optional = false +python-versions = "*" +files = [ + {file = "requests-file-1.5.1.tar.gz", hash = "sha256:07d74208d3389d01c38ab89ef403af0cfec63957d53a0081d8eca738d0247d8e"}, + {file = "requests_file-1.5.1-py2.py3-none-any.whl", hash = "sha256:dfe5dae75c12481f68ba353183c53a65e6044c923e64c24b2209f6c7570ca953"}, +] + +[package.dependencies] +requests = ">=1.0.0" +six = "*" + [[package]] name = "requests-mock" version = "1.11.0" @@ -1710,6 +2490,83 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "ruamel-yaml" +version = "0.18.5" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, + {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.6" +files = [ + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, + {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, + {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, +] + [[package]] name = "s3transfer" version = "0.7.0" @@ -1727,6 +2584,51 @@ botocore = ">=1.12.36,<2.0a.0" [package.extras] crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] +[[package]] +name = "sentry-sdk" +version = "1.39.1" +description = "Python client for Sentry (https://sentry.io)" +optional = false +python-versions = "*" +files = [ + {file = "sentry-sdk-1.39.1.tar.gz", hash = "sha256:320a55cdf9da9097a0bead239c35b7e61f53660ef9878861824fd6d9b2eaf3b5"}, + {file = "sentry_sdk-1.39.1-py2.py3-none-any.whl", hash = "sha256:81b5b9ffdd1a374e9eb0c053b5d2012155db9cbe76393a8585677b753bd5fdc1"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] +grpcio = ["grpcio (>=1.21.1)"] +httpx = ["httpx (>=0.16.0)"] +huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +starlite = ["starlite (>=1.48)"] +tornado = ["tornado (>=5)"] + [[package]] name = "setuptools" version = "66.0.0" @@ -1907,6 +2809,20 @@ files = [ {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"}, ] +[[package]] +name = "termcolor" +version = "2.4.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.8" +files = [ + {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, + {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "toml" version = "0.10.2" @@ -2017,6 +2933,188 @@ files = [ [package.extras] test = ["pytest (>=3.0.0)"] +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "yarl" +version = "1.9.4" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [[package]] name = "zipp" version = "3.17.0" @@ -2034,5 +3132,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = "^3.10.0" -content-hash = "192a10b6c21e0925349c2e4583351c3dfd7fee33ea40806dcfb45bfa9e085143" +python-versions = "^3.10" +content-hash = "5efc740c64f8cf9732e86742152e6576a4231ad85cbdeae30350f8394a25a023" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 4604829b..f7addbb5 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -8,8 +8,11 @@ readme = "README.md" packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] -python = "^3.10.0" +python = "^3.10" openmetadata-ingestion = "~1.2.0.1" +acryl-datahub = {extras = ["datahub-rest"], version = "^0.12.1.3"} +freezegun = "^1.4.0" +deepdiff = "^6.7.1" [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" diff --git a/lib/datahub-client/tests/conftest.py b/lib/datahub-client/tests/conftest.py new file mode 100644 index 00000000..7752d91a --- /dev/null +++ b/lib/datahub-client/tests/conftest.py @@ -0,0 +1,50 @@ +from pathlib import Path +from typing import Any, Dict + +import pytest +from datahub.metadata.schema_classes import DomainPropertiesClass +from tests.test_helpers.graph_helpers import MockDataHubGraph +from tests.test_helpers.mce_helpers import check_golden_file + +FROZEN_TIME = "2023-04-14 07:00:00" + + +@pytest.fixture +def base_entity_metadata(): + return { + "urn:li:domain:12345": { + "domainProperties": DomainPropertiesClass( + name="Marketing", description="Marketing Domain" + ) + } + } + + +@pytest.fixture +def base_mock_graph( + base_entity_metadata: Dict[str, Dict[str, Any]] +) -> MockDataHubGraph: + return MockDataHubGraph(entity_graph=base_entity_metadata) + + +@pytest.fixture +def test_snapshots_dir(pytestconfig: pytest.Config) -> Path: + return pytestconfig.rootpath / "tests/snapshots" + + +@pytest.fixture +def check_snapshot(test_snapshots_dir, pytestconfig): + def _check_snapshot(name: str, output_file: Path): + last_snapshot = Path(test_snapshots_dir / name) + check_golden_file(pytestconfig, output_file, last_snapshot) + + return _check_snapshot + + +def pytest_addoption(parser): + parser.addoption( + "--update-golden-files", + action="store_true", + default=False, + ) + parser.addoption("--copy-output-files", action="store_true", default=False) diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json new file mode 100644 index 00000000..08236691 --- /dev/null +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -0,0 +1,70 @@ +[ +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": {}, + "description": "bla bla", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table", + "platform": "urn:li:dataPlatform:glue", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "foo", + "nullable": false, + "description": "a", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "bar", + "nullable": false, + "description": "b", + "type": { + "type": { + "com.linkedin.schema.NumberType": {} + } + }, + "nativeDataType": "int", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +} +] diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json new file mode 100644 index 00000000..d6d2bd5f --- /dev/null +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -0,0 +1,129 @@ +[ +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": {}, + "description": "bla bla", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table", + "platform": "urn:li:dataPlatform:glue", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "foo", + "nullable": false, + "description": "a", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "bar", + "nullable": false, + "description": "b", + "type": { + "type": { + "com.linkedin.schema.NumberType": {} + } + }, + "nativeDataType": "int", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +}, +{ + "entityType": "domain", + "entityUrn": "urn:li:domain:legal-aid", + "changeType": "UPSERT", + "aspectName": "domainProperties", + "aspect": { + "json": { + "name": "legal-aid", + "description": "" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:legal-aid" + ] + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": { + "email": "justice@justice.gov.uk", + "retention_period_in_days": "365", + "dpia_required": "False" + }, + "name": "my_data_product", + "description": "bla bla" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": {}, + "assets": [ + { + "sourceUrn": "urn:li:dataProduct:my_data_product", + "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + } + ] + } + } +} +] diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py new file mode 100644 index 00000000..583ac556 --- /dev/null +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -0,0 +1,132 @@ +from pathlib import Path + +import pytest +from data_platform_catalogue.client import DataHubCatalogueClient +from data_platform_catalogue.entities import ( + CatalogueMetadata, + DataLocation, + DataProductMetadata, + TableMetadata, +) + + +class TestCatalogueClientWithDatahub: + """ + Test that the contract with DataHubGraph has not changed, using a mock. + + If this is the case, then the final metadata graph should match a snapshot we took earlier. + """ + + @pytest.fixture + def catalogue(self): + return CatalogueMetadata( + name="data_platform", + description="All data products hosted on the data platform", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + ) + + @pytest.fixture + def data_product(self): + return DataProductMetadata( + name="my_data_product", + description="bla bla", + version="v1.0.0", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="legal-aid", + dpia_required=False, + tags=["test"], + ) + + @pytest.fixture + def table(self): + return TableMetadata( + name="my_table", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=365, + ) + + @pytest.fixture + def datahub_client(self, base_mock_graph) -> DataHubCatalogueClient: + return DataHubCatalogueClient( + jwt_token="abc", api_url="http://example.com/api/gms", graph=base_mock_graph + ) + + def test_create_table_datahub( + self, datahub_client, base_mock_graph, table, tmp_path, check_snapshot + ): + """ + Case where we just create a dataset (no data product) + """ + fqn = datahub_client.upsert_table( + metadata=table, + location=DataLocation(fully_qualified_name="my_database"), + ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + + assert fqn == fqn_out + + output_file = Path(tmp_path / "datahub_create_table.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_table.json", output_file) + + def test_create_table_with_metadata_datahub( + self, + datahub_client, + table, + data_product, + base_mock_graph, + tmp_path, + check_snapshot, + ): + """ + Case where we create a dataset, data product and domain + """ + fqn = datahub_client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("my_database"), + ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + + assert fqn == fqn_out + + output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_table_with_metadata.json", output_file) + + def test_create_table_and_metadata_idempotent_datahub( + self, + datahub_client, + table, + data_product, + base_mock_graph, + tmp_path, + check_snapshot, + ): + """ + `create_table` should work even if the entities already exist in the metadata graph. + """ + datahub_client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("my_database"), + ) + + fqn = datahub_client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("my_database"), + ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + + assert fqn == fqn_out + + output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_table_with_metadata.json", output_file) diff --git a/lib/datahub-client/tests/test_client.py b/lib/datahub-client/tests/test_client_openmetadata.py similarity index 84% rename from lib/datahub-client/tests/test_client.py rename to lib/datahub-client/tests/test_client_openmetadata.py index f0fd2a6b..e4d03003 100644 --- a/lib/datahub-client/tests/test_client.py +++ b/lib/datahub-client/tests/test_client_openmetadata.py @@ -1,13 +1,17 @@ import pytest -from data_platform_catalogue.client import CatalogueClient, ReferencedEntityMissing +from data_platform_catalogue.client import ( + OpenMetadataCatalogueClient, + ReferencedEntityMissing, +) from data_platform_catalogue.entities import ( CatalogueMetadata, + DataLocation, DataProductMetadata, TableMetadata, ) -class TestCatalogueClient: +class TestCatalogueClientWithOMD: def mock_service_response(self, fqn): return { "fullyQualifiedName": fqn, @@ -42,9 +46,9 @@ def mock_schema_response(self, fqn): }, } - def mock_table_response(self, fqn): + def mock_table_response_omd(self): return { - "fullyQualifiedName": "some-table", + "fullyQualifiedName": "my_table", "id": "39b855e3-84a5-491e-b9a5-c411e626e340", "name": "foo", "service": { @@ -103,21 +107,23 @@ def table(self): ) @pytest.fixture - def client(self, requests_mock): + def omd_client(self, requests_mock) -> OpenMetadataCatalogueClient: requests_mock.get( "http://example.com/api/v1/system/version", json={"version": "1.2.0.1", "revision": "1", "timestamp": 0}, ) - return CatalogueClient(jwt_token="abc", api_uri="http://example.com/api") + return OpenMetadataCatalogueClient( + jwt_token="abc", api_url="http://example.com/api" + ) - def test_create_service(self, client, requests_mock): + def test_create_service_omd(self, omd_client, requests_mock): requests_mock.put( "http://example.com/api/v1/services/databaseServices", json=self.mock_service_response("some-service"), ) - fqn = client.create_or_update_database_service() + fqn = omd_client.upsert_database_service() assert requests_mock.last_request.json() == { "name": "data-platform", @@ -127,14 +133,14 @@ def test_create_service(self, client, requests_mock): } assert fqn == "some-service" - def test_create_database(self, client, requests_mock, catalogue): + def test_create_database_omd(self, request, omd_client, requests_mock, catalogue): requests_mock.put( "http://example.com/api/v1/databases", json=self.mock_database_response("some-db"), ) - fqn = client.create_or_update_database( - metadata=catalogue, service_fqn="data-platform" + fqn: str = omd_client.upsert_database( + metadata=catalogue, location=DataLocation("data-platform") ) assert requests_mock.last_request.json() == { "name": "data_platform", @@ -161,14 +167,14 @@ def test_create_database(self, client, requests_mock, catalogue): } assert fqn == "some-db" - def test_create_schema(self, client, requests_mock, data_product): + def test_create_schema(self, request, omd_client, requests_mock, data_product): requests_mock.put( "http://example.com/api/v1/databaseSchemas", json=self.mock_schema_response("some-schema"), ) - fqn = client.create_or_update_schema( - metadata=data_product, database_fqn="data-product" + fqn = omd_client.upsert_schema( + metadata=data_product, location=DataLocation("data-product") ) assert requests_mock.last_request.json() == { "name": "my_data_product", @@ -206,15 +212,16 @@ def test_create_schema(self, client, requests_mock, data_product): } assert fqn == "some-schema" - def test_create_table(self, client, requests_mock, table): + def test_create_table_omd(self, request, omd_client, requests_mock, table): requests_mock.put( "http://example.com/api/v1/tables", - json=self.mock_table_response("some-table"), + json=self.mock_table_response_omd(), ) - fqn = client.create_or_update_table( - metadata=table, schema_fqn="data-platform.data-product.schema" + fqn = omd_client.upsert_table( + metadata=table, location=DataLocation("data-platform.data-product.schema") ) + assert requests_mock.called assert requests_mock.last_request.json() == { "name": "my_table", "displayName": None, @@ -275,9 +282,9 @@ def test_create_table(self, client, requests_mock, table): "sourceUrl": None, "fileFormat": None, } - assert fqn == "some-table" + assert fqn == "my_table" - def test_404_handling(self, client, requests_mock, table): + def test_404_handling_omd(self, request, omd_client, requests_mock, table): requests_mock.put( "http://example.com/api/v1/tables", status_code=404, @@ -285,11 +292,12 @@ def test_404_handling(self, client, requests_mock, table): ) with pytest.raises(ReferencedEntityMissing): - client.create_or_update_table( - metadata=table, schema_fqn="data-platform.data-product.schema" + omd_client.upsert_table( + metadata=table, + location=DataLocation("data-platform.data-product.schema"), ) - def test_get_user_id(self, requests_mock, client): + def test_get_user_id(self, request, requests_mock, omd_client): requests_mock.get( "http://example.com/api/v1/users/name/justice", json={ @@ -298,6 +306,7 @@ def test_get_user_id(self, requests_mock, client): "id": "39b855e3-84a5-491e-b9a5-c411e626e340", }, ) - user_id = client.get_user_id("justice@justice.gov.uk") + + user_id = omd_client.get_user_id("justice@justice.gov.uk") assert "39b855e3-84a5-491e-b9a5-c411e626e340" in str(user_id) diff --git a/lib/datahub-client/tests/test_helpers/graph_helpers.py b/lib/datahub-client/tests/test_helpers/graph_helpers.py new file mode 100644 index 00000000..ffa2cc64 --- /dev/null +++ b/lib/datahub-client/tests/test_helpers/graph_helpers.py @@ -0,0 +1,137 @@ +# from https://github.com/datahub-project/datahub/blob/master/metadata-ingestion/tests/test_helpers/graph_helpers.py +# outputs MockDataHubGraph +from pathlib import Path +from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union + +from datahub.emitter.mce_builder import Aspect +from datahub.emitter.mcp import MetadataChangeProposalWrapper +from datahub.emitter.mcp_builder import mcps_from_mce +from datahub.ingestion.api.common import PipelineContext +from datahub.ingestion.api.workunit import MetadataWorkUnit +from datahub.ingestion.graph.client import DataHubGraph +from datahub.ingestion.sink.file import write_metadata_file +from datahub.ingestion.source.file import FileSourceConfig, GenericFileSource +from datahub.metadata.com.linkedin.pegasus2avro.mxe import ( + MetadataChangeEvent, + MetadataChangeProposal, +) +from datahub.metadata.schema_classes import ( + ASPECT_NAME_MAP, + DomainPropertiesClass, + UsageAggregationClass, +) + + +class MockDataHubGraph(DataHubGraph): + def __init__( + self, entity_graph: Optional[Dict[str, Dict[str, Any]]] = None + ) -> None: + self.emitted: List[ + Union[ + MetadataChangeEvent, + MetadataChangeProposal, + MetadataChangeProposalWrapper, + ] + ] = [] + self.entity_graph = entity_graph or {} + + def import_file(self, file: Path) -> None: + """Imports metadata from any MCE/MCP file. Does not clear prior loaded data. + This function can be called repeatedly on the same + Mock instance to load up metadata from multiple files.""" + file_source: GenericFileSource = GenericFileSource( + ctx=PipelineContext(run_id="test"), config=FileSourceConfig(path=str(file)) + ) + for wu in file_source.get_workunits(): + if isinstance(wu, MetadataWorkUnit): + metadata = wu.get_metadata().get("metadata") + mcps: Iterable[ + Union[ + MetadataChangeProposal, + MetadataChangeProposalWrapper, + ] + ] + if isinstance(metadata, MetadataChangeEvent): + mcps = mcps_from_mce(metadata) + elif isinstance( + metadata, (MetadataChangeProposal, MetadataChangeProposalWrapper) + ): + mcps = [metadata] + else: + raise Exception( + f"Unexpected metadata type {type(metadata)}. Was expecting MCE, MCP or MCPW" + ) + + for mcp in mcps: + assert mcp.entityUrn + assert mcp.aspectName + assert mcp.aspect + if mcp.entityUrn not in self.entity_graph: + self.entity_graph[mcp.entityUrn] = {} + self.entity_graph[mcp.entityUrn][mcp.aspectName] = mcp.aspect + + def get_aspect( + self, entity_urn: str, aspect_type: Type[Aspect], version: int = 0 + ) -> Optional[Aspect]: + aspect_name = [v for v in ASPECT_NAME_MAP if ASPECT_NAME_MAP[v] == aspect_type][ + 0 + ] + result = self.entity_graph.get(entity_urn, {}).get(aspect_name, None) + if result is not None and isinstance(result, dict): + return aspect_type.from_obj(result) + else: + return result + + def get_domain_urn_by_name(self, domain_name: str) -> Optional[str]: + domain_metadata = { + urn: metadata + for urn, metadata in self.entity_graph.items() + if urn.startswith("urn:li:domain:") + } + domain_properties_metadata = { + urn: metadata["domainProperties"].name + for urn, metadata in domain_metadata.items() + if "domainProperties" in metadata + and isinstance(metadata["domainProperties"], DomainPropertiesClass) + } + urn_match = [ + urn + for urn, name in domain_properties_metadata.items() + if name == domain_name + ] + if urn_match: + return urn_match[0] + else: + return None + + def emit( + self, + item: Union[ + MetadataChangeEvent, + MetadataChangeProposal, + MetadataChangeProposalWrapper, + UsageAggregationClass, + ], + callback: Union[Callable[[Exception, str], None], None] = None, + ) -> None: + self.emitted.append(item) # type: ignore + + def emit_mce(self, mce: MetadataChangeEvent) -> None: + self.emitted.append(mce) + + def emit_mcp( + self, mcp: Union[MetadataChangeProposal, MetadataChangeProposalWrapper] + ) -> None: + self.emitted.append(mcp) + + def get_emitted( + self, + ) -> List[ + Union[ + MetadataChangeEvent, MetadataChangeProposal, MetadataChangeProposalWrapper + ] + ]: + return self.emitted + + def sink_to_file(self, file: Path) -> None: + write_metadata_file(file, self.emitted) diff --git a/lib/datahub-client/tests/test_helpers/mce_helpers.py b/lib/datahub-client/tests/test_helpers/mce_helpers.py new file mode 100644 index 00000000..dc0169e3 --- /dev/null +++ b/lib/datahub-client/tests/test_helpers/mce_helpers.py @@ -0,0 +1,417 @@ +# from https://github.com/datahub-project/datahub/blob/master/metadata-ingestion/tests/test_helpers/mce_helpers.py + +import json +import logging +import os +import re +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, +) + +from datahub.emitter.mcp import MetadataChangeProposalWrapper +from datahub.metadata.schema_classes import MetadataChangeEventClass +from datahub.testing.compare_metadata_json import ( + assert_metadata_files_equal, + load_json_file, +) +from datahub.utilities.urns.urn import Urn +from tests.test_helpers.type_helpers import PytestConfig + +logger = logging.getLogger(__name__) + +IGNORE_PATH_TIMESTAMPS = [ + # Ignore timestamps from the ETL pipeline. A couple examples: + r"root\[\d+\]\['proposedSnapshot'\].+\['aspects'\].+\['created'\]\['time'\]", + r"root\[\d+\]\['proposedSnapshot'\].+\['aspects'\].+\['lastModified'\]\['time'\]", + r"root\[\d+\]\['proposedSnapshot'\].+\['aspects'\].+\['createStamp'\]\['time'\]", + r"root\[\d+\]\['proposedSnapshot'\].+\['aspects'\].+\['auditStamp'\]\['time'\]", +] + + +class MCEConstants: + PROPOSED_SNAPSHOT = "proposedSnapshot" + DATASET_SNAPSHOT_CLASS = ( + "com.linkedin.pegasus2avro.metadata.snapshot.DatasetSnapshot" + ) + + +class MCPConstants: + CHANGE_TYPE = "changeType" + ENTITY_URN = "entityUrn" + ENTITY_TYPE = "entityType" + ASPECT_NAME = "aspectName" + ASPECT_VALUE = "aspect" + + +class EntityType: + DATASET = "dataset" + PIPELINE = "dataFlow" + FLOW = "dataFlow" + TASK = "dataJob" + JOB = "dataJob" + USER = "corpuser" + GROUP = "corpGroup" + + +def clean_nones(value): + """ + Recursively remove all None values from dictionaries and lists, and returns + the result as a new dictionary or list. + """ + if isinstance(value, list): + return [clean_nones(x) for x in value if x is not None] + elif isinstance(value, dict): + return {key: clean_nones(val) for key, val in value.items() if val is not None} + else: + return value + + +def check_golden_file( + pytestconfig: PytestConfig, + output_path: Union[str, os.PathLike], + golden_path: Union[str, os.PathLike], + ignore_paths: Sequence[str] = (), +) -> None: + update_golden = pytestconfig.getoption("--update-golden-files") + copy_output = pytestconfig.getoption("--copy-output-files") + assert_metadata_files_equal( + output_path=output_path, + golden_path=golden_path, + update_golden=update_golden, + copy_output=copy_output, + ignore_paths=ignore_paths, + ) + + +def _get_field_for_entity_type_in_mce(entity_type: str) -> str: + """Returns the field to look for depending on the type of entity in the MCE""" + if entity_type == EntityType.DATASET: + return MCEConstants.DATASET_SNAPSHOT_CLASS + raise Exception(f"Not implemented for entity_type {entity_type}") + + +def _get_filter( + mce: bool = False, mcp: bool = False, entity_type: Optional[str] = None +) -> Callable[[Dict], bool]: + if mce: + # cheap way to determine if we are working with an MCE for the appropriate entity_type + if entity_type: + return ( + lambda x: MCEConstants.PROPOSED_SNAPSHOT in x + and _get_field_for_entity_type_in_mce(str(entity_type)) + in x[MCEConstants.PROPOSED_SNAPSHOT] + ) + else: + return lambda x: MCEConstants.PROPOSED_SNAPSHOT in x + if mcp: + # cheap way to determine if we are working with an MCP + return lambda x: MCPConstants.CHANGE_TYPE in x and ( + x[MCPConstants.ENTITY_TYPE] == entity_type if entity_type else True + ) + return lambda _: False + + +def _get_element(event: Dict[str, Any], path_spec: List[str]) -> Any: + try: + for p in path_spec: + if p not in event: + return None + else: + event = event.get(p, {}) + return event + except Exception as e: + print(event) + raise e + + +def _element_matches_pattern( + event: Dict[str, Any], path_spec: List[str], pattern: str +) -> Tuple[bool, bool]: + import re + + element = _get_element(event, path_spec) + if element is None: + return (False, False) + else: + return (True, re.search(pattern, str(element)) is not None) + + +def get_entity_urns(events_file: str) -> Set[str]: + events = load_json_file(events_file) + assert isinstance(events, list) + return _get_entity_urns(events) + + +def _get_entity_urns(events_list: List[Dict]) -> Set[str]: + entity_type = "dataset" + # mce urns + mce_urns = set( + [ + _get_element(x, _get_mce_urn_path_spec(entity_type)) + for x in events_list + if _get_filter(mce=True, entity_type=entity_type)(x) + ] + ) + mcp_urns = set( + [ + _get_element(x, _get_mcp_urn_path_spec()) + for x in events_list + if _get_filter(mcp=True, entity_type=entity_type)(x) + ] + ) + all_urns = mce_urns.union(mcp_urns) + return all_urns + + +def assert_mcp_entity_urn( + filter: str, entity_type: str, regex_pattern: str, file: str +) -> int: + def get_path_spec_for_urn() -> List[str]: + return [MCPConstants.ENTITY_URN] + + test_output = load_json_file(file) + if isinstance(test_output, list): + path_spec = get_path_spec_for_urn() + filter_operator = _get_filter(mcp=True, entity_type=entity_type) + filtered_events = [ + (x, _element_matches_pattern(x, path_spec, regex_pattern)) + for x in test_output + if filter_operator(x) + ] + failed_events = [y for y in filtered_events if not y[1][0] or not y[1][1]] + if failed_events: + raise Exception("Failed to match events", failed_events) + return len(filtered_events) + else: + raise Exception( + f"Did not expect the file {file} to not contain a list of items" + ) + + +def _get_mce_urn_path_spec(entity_type: str) -> List[str]: + if entity_type == EntityType.DATASET: + return [ + MCEConstants.PROPOSED_SNAPSHOT, + MCEConstants.DATASET_SNAPSHOT_CLASS, + "urn", + ] + raise Exception(f"Not implemented for entity_type: {entity_type}") + + +def _get_mcp_urn_path_spec() -> List[str]: + return [MCPConstants.ENTITY_URN] + + +def assert_mce_entity_urn( + filter: str, entity_type: str, regex_pattern: str, file: str +) -> int: + """Assert that all mce entity urns must match the regex pattern passed in. + + Return the number of events matched""" + + test_output = load_json_file(file) + if isinstance(test_output, list): + path_spec = _get_mce_urn_path_spec(entity_type) + filter_operator = _get_filter(mce=True) + filtered_events = [ + (x, _element_matches_pattern(x, path_spec, regex_pattern)) + for x in test_output + if filter_operator(x) + ] + failed_events = [y for y in filtered_events if not y[1][0] or not y[1][1]] + if failed_events: + raise Exception( + "Failed to match events: {json.dumps(failed_events, indent=2)}" + ) + return len(filtered_events) + else: + raise Exception( + f"Did not expect the file {file} to not contain a list of items" + ) + + +def assert_for_each_entity( + entity_type: str, + aspect_name: str, + aspect_field_matcher: Dict[str, Any], + file: str, + exception_urns: List[str] = [], +) -> int: + """Assert that an aspect name with the desired fields exists for each entity urn""" + test_output = load_json_file(file) + assert isinstance(test_output, list) + # mce urns + mce_urns = set( + [ + _get_element(x, _get_mce_urn_path_spec(entity_type)) + for x in test_output + if _get_filter(mce=True, entity_type=entity_type)(x) + ] + ) + mcp_urns = set( + [ + _get_element(x, _get_mcp_urn_path_spec()) + for x in test_output + if _get_filter(mcp=True, entity_type=entity_type)(x) + ] + ) + all_urns = mce_urns.union(mcp_urns) + # there should not be any None urns + assert None not in all_urns + aspect_map = {urn: None for urn in all_urns} + # iterate over all mcps + for o in [ + mcp + for mcp in test_output + if _get_filter(mcp=True, entity_type=entity_type)(mcp) + ]: + if o.get(MCPConstants.ASPECT_NAME) == aspect_name: + # load the inner aspect payload and assign to this urn + aspect_map[o[MCPConstants.ENTITY_URN]] = o.get( + MCPConstants.ASPECT_VALUE, {} + ).get("json") + + success: List[str] = [] + failures: List[str] = [] + for urn, aspect_val in aspect_map.items(): + if aspect_val is not None: + for f in aspect_field_matcher: + assert aspect_field_matcher[f] == _get_element( + aspect_val, [f] + ), f"urn: {urn} -> Field {f} must match value {aspect_field_matcher[f]}, found {_get_element(aspect_val, [f])}" # noqa: E501 + success.append(urn) + elif urn not in exception_urns: + print(f"Adding {urn} to failures") + failures.append(urn) + + if success: + print(f"Succeeded on assertion for urns {success}") + if failures: + raise AssertionError( + f"Failed to find aspect_name {aspect_name} for urns {json.dumps(failures, indent=2)}" + ) + + return len(success) + + +def assert_entity_mce_aspect( + entity_urn: str, aspect: Any, aspect_type: Type, file: str +) -> int: + # TODO: Replace with read_metadata_file() + test_output = load_json_file(file) + entity_type = Urn.create_from_string(entity_urn).get_type() + assert isinstance(test_output, list) + # mce urns + mces: List[MetadataChangeEventClass] = [ + MetadataChangeEventClass.from_obj(x) + for x in test_output + if _get_filter(mce=True, entity_type=entity_type)(x) + and _get_element(x, _get_mce_urn_path_spec(entity_type)) == entity_urn + ] + matches = 0 + for mce in mces: + for a in mce.proposedSnapshot.aspects: + if isinstance(a, aspect_type): + assert a == aspect + matches = matches + 1 + return matches + + +def assert_entity_mcp_aspect( + entity_urn: str, aspect_field_matcher: Dict[str, Any], aspect_name: str, file: str +) -> int: + # TODO: Replace with read_metadata_file() + test_output = load_json_file(file) + entity_type = Urn.create_from_string(entity_urn).get_type() + assert isinstance(test_output, list) + # mcps that match entity_urn + mcps: List[MetadataChangeProposalWrapper] = [ + MetadataChangeProposalWrapper.from_obj_require_wrapper(x) + for x in test_output + if _get_filter(mcp=True, entity_type=entity_type)(x) + and _get_element(x, _get_mcp_urn_path_spec()) == entity_urn + ] + matches = 0 + for mcp in mcps: + if mcp.aspectName == aspect_name: + assert mcp.aspect + aspect_val = mcp.aspect.to_obj() + for f in aspect_field_matcher: + assert aspect_field_matcher[f] == _get_element( + aspect_val, [f] + ), f"urn: {mcp.entityUrn} -> Field {f} must match value {aspect_field_matcher[f]}, found {_get_element(aspect_val, [f])}" # noqa: E501 + matches = matches + 1 + return matches + + +def assert_entity_urn_not_like(entity_type: str, regex_pattern: str, file: str) -> int: + """Assert that there are no entity urns that match the regex pattern passed in. + + Returns the total number of events in the file""" + + # TODO: Refactor common code with assert_entity_urn_like. + test_output = load_json_file(file) + assert isinstance(test_output, list) + # mce urns + mce_urns = set( + [ + _get_element(x, _get_mce_urn_path_spec(entity_type)) + for x in test_output + if _get_filter(mce=True, entity_type=entity_type)(x) + ] + ) + mcp_urns = set( + [ + _get_element(x, _get_mcp_urn_path_spec()) + for x in test_output + if _get_filter(mcp=True, entity_type=entity_type)(x) + ] + ) + all_urns = mce_urns.union(mcp_urns) + print(all_urns) + matched_urns = [u for u in all_urns if re.match(regex_pattern, u)] + if matched_urns: + raise AssertionError(f"urns found that match the deny list {matched_urns}") + return len(test_output) + + +def assert_entity_urn_like(entity_type: str, regex_pattern: str, file: str) -> int: + """Assert that there exist entity urns that match the regex pattern passed in. + + Returns the total number of events in the file""" + + test_output = load_json_file(file) + assert isinstance(test_output, list) + # mce urns + mce_urns = set( + [ + _get_element(x, _get_mce_urn_path_spec(entity_type)) + for x in test_output + if _get_filter(mce=True, entity_type=entity_type)(x) + ] + ) + mcp_urns = set( + [ + _get_element(x, _get_mcp_urn_path_spec()) + for x in test_output + if _get_filter(mcp=True, entity_type=entity_type)(x) + ] + ) + all_urns = mce_urns.union(mcp_urns) + print(all_urns) + matched_urns = [u for u in all_urns if re.match(regex_pattern, u)] + if matched_urns: + return len(matched_urns) + else: + raise AssertionError( + f"No urns found that match the pattern {regex_pattern}. Full list is {all_urns}" + ) diff --git a/lib/datahub-client/tests/test_helpers/type_helpers.py b/lib/datahub-client/tests/test_helpers/type_helpers.py new file mode 100644 index 00000000..d407a320 --- /dev/null +++ b/lib/datahub-client/tests/test_helpers/type_helpers.py @@ -0,0 +1,17 @@ +# from https://github.com/datahub-project/datahub/blob/master/metadata-ingestion/tests/test_helpers/type_helpers.py + +from typing import Optional, TypeVar + +# The current PytestConfig solution is somewhat ugly and not ideal. +# However, it is currently the best solution available, as the type itself is not +# exported: https://docs.pytest.org/en/stable/reference.html#config. +# As pytest's type support improves, this will likely change. +# TODO: revisit pytestconfig as https://github.com/pytest-dev/pytest/issues/7469 progresses. +from _pytest.config import Config as PytestConfig # noqa: F401 + +_T = TypeVar("_T") + + +def assert_not_null(value: Optional[_T]) -> _T: + assert value is not None, "value is unexpectedly None" + return value diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py new file mode 100644 index 00000000..1dc89e74 --- /dev/null +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -0,0 +1,61 @@ +""" +Integration test that runs against a DataHub server + +Run with: +export API_URL='https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api' +export JWT_TOKEN=****** +poetry run pytest tests/test_integration_with_server.py +""" + +import os + +import pytest +from data_platform_catalogue import DataProductMetadata, TableMetadata +from data_platform_catalogue.client import DataHubCatalogueClient +from data_platform_catalogue.entities import DataLocation +from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass + +jwt_token = os.environ.get("JWT_TOKEN") +api_url = os.environ.get("API_URL", "") +runs_on_development_server = pytest.mark.skipif("not jwt_token or not api_url") + + +@runs_on_development_server +def test_upsert_test_hierarchy(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + data_product = DataProductMetadata( + name="test_data_product", + description="bla bla", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="Sample", + dpia_required=False, + ) + + table = TableMetadata( + name="test_table", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=365, + tags=["test"], + ) + + table_fqn = client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("test_data_product_v2"), + ) + assert ( + table_fqn + == "urn:li:dataset:(urn:li:dataPlatform:glue,test_data_product_v2.test_table,PROD)" + ) + + # Ensure data went through + assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) + assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) diff --git a/lib/datahub-client/tests/test_integration_with_server.py b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py similarity index 70% rename from lib/datahub-client/tests/test_integration_with_server.py rename to lib/datahub-client/tests/test_integration_with_openmetadata_server.py index 06361d4f..11243a98 100644 --- a/lib/datahub-client/tests/test_integration_with_server.py +++ b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py @@ -10,16 +10,18 @@ import os import pytest -from data_platform_catalogue import CatalogueClient, DataProductMetadata, TableMetadata +from data_platform_catalogue import DataProductMetadata, TableMetadata +from data_platform_catalogue.client import OpenMetadataCatalogueClient +from data_platform_catalogue.entities import DataLocation jwt_token = os.environ.get("JWT_TOKEN") -api_url = os.environ.get("API_URL") +api_url = os.environ.get("API_URL", "") runs_on_development_server = pytest.mark.skipif("not jwt_token or not api_url") @runs_on_development_server -def test_create_or_update_test_hierarchy(): - client = CatalogueClient(jwt_token=jwt_token, api_uri=api_url) +def test_upsert_test_hierarchy(): + client = OpenMetadataCatalogueClient(jwt_token=jwt_token, api_url=api_url) assert client.is_healthy() @@ -55,18 +57,18 @@ def test_create_or_update_test_hierarchy(): retention_period_in_days=365, ) - service_fqn = client.create_or_update_database_service(name="data_platform") + service_fqn = client.upsert_database_service(name="data_platform") assert service_fqn == "data_platform" - database_fqn = client.create_or_update_database( - metadata=data_product, service_fqn=service_fqn + database_fqn = client.upsert_database( + metadata=data_product, location=DataLocation(service_fqn) ) assert database_fqn == "data_platform.my_data_product" - schema_fqn = client.create_or_update_schema( - metadata=data_product_schema, database_fqn=database_fqn + schema_fqn = client.upsert_schema( + metadata=data_product_schema, location=DataLocation(database_fqn) ) assert schema_fqn == "data_platform.my_data_product.Tables" - table_fqn = client.create_or_update_table(metadata=table, schema_fqn=schema_fqn) + table_fqn = client.upsert_table(metadata=table, location=DataLocation(schema_fqn)) assert table_fqn == "data_platform.my_data_product.Tables.my_table" From 2cc14f04755e57d2d6c0e31737ca90f6fff4f35b Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 23 Jan 2024 16:06:05 +0000 Subject: [PATCH 12/64] [DP-2782] Fix data product assets issue and bump the version (#3040) * Don't import openmetadata by default It's incompatable with python 3.11 which is really annoying. * Fix bug appending assets to a data product The list append() method returns None, so this was clearing the asset list every other time an asset was added. * Bump version --- lib/datahub-client/CHANGELOG.md | 4 +- .../data_platform_catalogue/__init__.py | 1 - .../client/__init__.py | 1 - .../data_platform_catalogue/client/datahub.py | 6 +- lib/datahub-client/pyproject.toml | 4 +- ...tahub_create_two_tables_with_metadata.json | 256 ++++++++++++++++++ .../tests/test_client_datahub.py | 47 ++++ .../tests/test_client_openmetadata.py | 6 +- ...st_integration_with_openmetadata_server.py | 2 +- 9 files changed, 314 insertions(+), 13 deletions(-) create mode 100644 lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 750d886e..d4b70d8b 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.4.0] 2024-01-19 ### Breaking changes @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 about where a node in the metadata graph should be located, and what kind of database it comes from. +- Renamed `create_or_update_*` methods to `upsert_*`. + - Extracted `BaseCatalogueClient` base class from `CatalogueClient`. Use this as a type annotation to avoid coupling to the OpenMetadata implementation. diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py index 1489b0f3..6e427eb8 100644 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -1,5 +1,4 @@ from .client import DataHubCatalogueClient # noqa: F401 -from .client import OpenMetadataCatalogueClient # noqa: F401 from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 from .entities import DataProductMetadata # noqa: F401 from .entities import CatalogueMetadata, DataLocation, TableMetadata # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/__init__.py b/lib/datahub-client/data_platform_catalogue/client/__init__.py index e5becdb2..f19de51a 100644 --- a/lib/datahub-client/data_platform_catalogue/client/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/client/__init__.py @@ -2,4 +2,3 @@ from .base import CatalogueError # noqa: F401 from .base import ReferencedEntityMissing # noqa: F401 from .datahub import DataHubCatalogueClient # noqa: F401 -from .openmetadata import OpenMetadataCatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub.py b/lib/datahub-client/data_platform_catalogue/client/datahub.py index d7afa9ef..87966bd6 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub.py @@ -294,11 +294,11 @@ def upsert_table( data_product_existing_properties is not None and data_product_existing_properties.assets is not None ): - assets = data_product_existing_properties.assets.append( - data_product_association - ) + assets = data_product_existing_properties.assets[::] + assets.append(data_product_association) else: assets = [data_product_association] + data_product_properties = DataProductPropertiesClass(assets=assets) metadata_event = MetadataChangeProposalWrapper( diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index f7addbb5..c22ad548 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.3.1" +version = "0.4.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" @@ -10,7 +10,7 @@ packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] python = "^3.10" openmetadata-ingestion = "~1.2.0.1" -acryl-datahub = {extras = ["datahub-rest"], version = "^0.12.1.3"} +acryl-datahub = { extras = ["datahub-rest"], version = "^0.12.1.3" } freezegun = "^1.4.0" deepdiff = "^6.7.1" diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json new file mode 100644 index 00000000..4d424eef --- /dev/null +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -0,0 +1,256 @@ +[ +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": {}, + "description": "bla bla", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table", + "platform": "urn:li:dataPlatform:glue", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "foo", + "nullable": false, + "description": "a", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "bar", + "nullable": false, + "description": "b", + "type": { + "type": { + "com.linkedin.schema.NumberType": {} + } + }, + "nativeDataType": "int", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +}, +{ + "entityType": "domain", + "entityUrn": "urn:li:domain:legal-aid", + "changeType": "UPSERT", + "aspectName": "domainProperties", + "aspect": { + "json": { + "name": "legal-aid", + "description": "" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:legal-aid" + ] + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": { + "email": "justice@justice.gov.uk", + "retention_period_in_days": "365", + "dpia_required": "False" + }, + "name": "my_data_product", + "description": "bla bla" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": {}, + "assets": [ + { + "sourceUrn": "urn:li:dataProduct:my_data_product", + "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + } + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": {}, + "description": "this is a different table", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table2", + "platform": "urn:li:dataPlatform:glue", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "boo", + "nullable": false, + "description": "spooky", + "type": { + "type": { + "com.linkedin.schema.BooleanType": {} + } + }, + "nativeDataType": "boolean", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "yar", + "nullable": false, + "description": "shiver my timbers", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +}, +{ + "entityType": "domain", + "entityUrn": "urn:li:domain:legal-aid", + "changeType": "UPSERT", + "aspectName": "domainProperties", + "aspect": { + "json": { + "name": "legal-aid", + "description": "" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:legal-aid" + ] + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": { + "email": "justice@justice.gov.uk", + "retention_period_in_days": "365", + "dpia_required": "False" + }, + "name": "my_data_product", + "description": "bla bla" + } + } +}, +{ + "entityType": "dataproduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": {}, + "assets": [ + { + "sourceUrn": "urn:li:dataProduct:my_data_product", + "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)" + } + ] + } + } +} +] diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py index 583ac556..27992b4e 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -51,6 +51,18 @@ def table(self): retention_period_in_days=365, ) + @pytest.fixture + def table2(self): + return TableMetadata( + name="my_table2", + description="this is a different table", + column_details=[ + {"name": "boo", "type": "boolean", "description": "spooky"}, + {"name": "yar", "type": "string", "description": "shiver my timbers"}, + ], + retention_period_in_days=1, + ) + @pytest.fixture def datahub_client(self, base_mock_graph) -> DataHubCatalogueClient: return DataHubCatalogueClient( @@ -100,6 +112,41 @@ def test_create_table_with_metadata_datahub( base_mock_graph.sink_to_file(output_file) check_snapshot("datahub_create_table_with_metadata.json", output_file) + def test_create_two_tables_with_metadata( + self, + datahub_client, + table, + table2, + data_product, + base_mock_graph, + tmp_path, + check_snapshot, + ): + """ + Case where we create a dataset, data product and domain + """ + fqn = datahub_client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("my_database"), + ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" + + assert fqn == fqn_out + + fqn = datahub_client.upsert_table( + metadata=table2, + data_product_metadata=data_product, + location=DataLocation("my_database"), + ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)" + + assert fqn == fqn_out + + output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_two_tables_with_metadata.json", output_file) + def test_create_table_and_metadata_idempotent_datahub( self, datahub_client, diff --git a/lib/datahub-client/tests/test_client_openmetadata.py b/lib/datahub-client/tests/test_client_openmetadata.py index e4d03003..d386a8d0 100644 --- a/lib/datahub-client/tests/test_client_openmetadata.py +++ b/lib/datahub-client/tests/test_client_openmetadata.py @@ -1,8 +1,6 @@ import pytest -from data_platform_catalogue.client import ( - OpenMetadataCatalogueClient, - ReferencedEntityMissing, -) +from data_platform_catalogue.client import ReferencedEntityMissing +from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient from data_platform_catalogue.entities import ( CatalogueMetadata, DataLocation, diff --git a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py index 11243a98..0337d631 100644 --- a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py +++ b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py @@ -11,7 +11,7 @@ import pytest from data_platform_catalogue import DataProductMetadata, TableMetadata -from data_platform_catalogue.client import OpenMetadataCatalogueClient +from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient from data_platform_catalogue.entities import DataLocation jwt_token = os.environ.get("JWT_TOKEN") From e2f98d6efdb6cac5d0a80d0f13439d7c94327a08 Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 24 Jan 2024 13:06:38 +0000 Subject: [PATCH 13/64] [DP-2983] Add a method for search (#3051) * Add a method for search This method accepts a query and pagination variables, and returns the total number of results plus the results to display on the current page. For each search result, return: - the result type, DataSet or DataProduct - ID, name and description - tags - a dictionary of additional metadata fields (exactly what this will contain will depend on what tests well with users) - information about the properties which matched the search query - the time the metadata was last updated The raw responses from Datahub are logged at DEBUG level. For now I've excluded filters, facets, and sorting but these are supported by the underlying API. See - https://datahubproject.io/docs/graphql/inputObjects#facetfilterinput - https://datahubproject.io/docs/graphql/objects#facetmetadata - https://datahubproject.io/docs/graphql/inputObjects#searchsortinput * Parameterise result types in search queries --- .../data_platform_catalogue/__init__.py | 1 - .../client/__init__.py | 1 - .../data_platform_catalogue/client/base.py | 17 + .../client/datahub/__init__.py | 1 + .../{datahub.py => datahub/datahub_client.py} | 24 +- .../client/datahub/graphql/__init__.py | 0 .../client/datahub/graphql/search.graphql | 144 ++++++++ .../client/datahub/search.py | 182 +++++++++++ .../data_platform_catalogue/search_types.py | 27 ++ .../tests/test_client_datahub.py | 2 +- .../tests/test_datahub_search.py | 309 ++++++++++++++++++ .../test_integration_with_datahub_server.py | 34 +- 12 files changed, 737 insertions(+), 5 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py rename lib/datahub-client/data_platform_catalogue/client/{datahub.py => datahub/datahub_client.py} (94%) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/__init__.py create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/search.py create mode 100644 lib/datahub-client/data_platform_catalogue/search_types.py create mode 100644 lib/datahub-client/tests/test_datahub_search.py diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py index 6e427eb8..d0a34b65 100644 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -1,4 +1,3 @@ -from .client import DataHubCatalogueClient # noqa: F401 from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 from .entities import DataProductMetadata # noqa: F401 from .entities import CatalogueMetadata, DataLocation, TableMetadata # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/__init__.py b/lib/datahub-client/data_platform_catalogue/client/__init__.py index f19de51a..44d4e8c7 100644 --- a/lib/datahub-client/data_platform_catalogue/client/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/client/__init__.py @@ -1,4 +1,3 @@ from .base import BaseCatalogueClient # noqa: F401 from .base import CatalogueError # noqa: F401 from .base import ReferencedEntityMissing # noqa: F401 -from .datahub import DataHubCatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 31be915c..5ab4f2e1 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -1,5 +1,6 @@ import logging from abc import ABC, abstractmethod +from typing import Sequence from ..entities import ( CatalogueMetadata, @@ -7,6 +8,7 @@ DataProductMetadata, TableMetadata, ) +from ..search_types import ResultType, SearchResponse logger = logging.getLogger(__name__) @@ -53,3 +55,18 @@ def upsert_table( data_product_metadata: DataProductMetadata | None = None, ) -> str: pass + + def search( + self, + query: str = "*", + count: int = 20, + page: str | None = None, + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + ) -> SearchResponse: + """ + Wraps the catalogue's search function. + """ + raise NotImplementedError diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py b/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py new file mode 100644 index 00000000..8b525dc1 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py @@ -0,0 +1 @@ +from .datahub_client import DataHubCatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py similarity index 94% rename from lib/datahub-client/data_platform_catalogue/client/datahub.py rename to lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 87966bd6..78b16204 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -1,3 +1,5 @@ +from typing import Sequence + import datahub.emitter.mce_builder as mce_builder import datahub.metadata.schema_classes as schema_classes from data_platform_catalogue.client.base import ( @@ -6,6 +8,7 @@ ReferencedEntityMissing, logger, ) +from data_platform_catalogue.search_types import ResultType, SearchResponse from datahub.emitter.mce_builder import make_data_platform_urn from datahub.emitter.mcp import MetadataChangeProposalWrapper from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph @@ -22,12 +25,13 @@ SchemaMetadataClass, ) -from ..entities import ( +from ...entities import ( CatalogueMetadata, DataLocation, DataProductMetadata, TableMetadata, ) +from .search import SearchClient DATAHUB_DATA_TYPE_MAPPING = { "boolean": schema_classes.BooleanTypeClass(), @@ -81,6 +85,7 @@ def __init__(self, jwt_token, api_url: str, graph=None): server=self.gms_endpoint, token=jwt_token ) self.graph = graph or DataHubGraph(self.server_config) + self.search_client = SearchClient(self.graph) def upsert_database_service(self, platform: str = "glue", *args, **kwargs) -> str: """ @@ -310,3 +315,20 @@ def upsert_table( self.graph.emit(metadata_event) return dataset_urn + + def search( + self, + query: str = "*", + count: int = 20, + page: str | None = None, + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + ) -> SearchResponse: + """ + Wraps the catalogue's search function. + """ + return self.search_client.search( + query=query, count=count, page=page, result_types=result_types + ) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/__init__.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql new file mode 100644 index 00000000..506ba0fb --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -0,0 +1,144 @@ +query Search( + $query: String! + $count: Int! + $start: Int! + $types: [EntityType!] +) { + searchAcrossEntities( + input: { types: $types, query: $query, start: $start, count: $count } + ) { + start + count + total + searchResults { + insights { + text + } + matchedFields { + name + value + } + entity { + type + ... on Dataset { + urn + type + platform { + name + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + name + properties { + name + qualifiedName + description + customProperties { + key + value + } + created + lastModified + } + editableProperties { + description + } + tags { + tags { + tag { + urn + properties { + name + description + } + } + } + } + lastIngested + domain { + domain { + urn + id + properties { + name + description + } + } + } + } + ... on DataProduct { + urn + type + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + properties { + name + description + customProperties { + key + value + } + numAssets + } + domain { + domain { + urn + id + properties { + name + description + } + } + } + tags { + tags { + tag { + urn + properties { + name + description + } + } + } + } + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py new file mode 100644 index 00000000..fdbe4894 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -0,0 +1,182 @@ +import json +import logging +from datetime import datetime +from importlib.resources import files +from typing import Any, Sequence + +from data_platform_catalogue.search_types import ( + ResultType, + SearchResponse, + SearchResult, +) +from datahub.configuration.common import GraphError +from datahub.ingestion.graph.client import DataHubGraph + +logger = logging.getLogger(__name__) + + +class SearchClient: + def __init__(self, graph: DataHubGraph): + self.graph = graph + self.search_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("search.graphql") + .read_text() + ) + + def search( + self, + query: str = "*", + count: int = 20, + page: str | None = None, + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + ) -> SearchResponse: + """ + Wraps the catalogue's search function. + """ + if page is None: + start = 0 + else: + start = int(page) + + types = self._map_result_types(result_types) + + variables = {"count": count, "query": query, "start": start, "types": types} + + try: + response = self.graph.execute_graphql(self.search_query, variables) + except GraphError as e: + raise Exception("Unable to execute search") from e + + page_results = [] + response = response["searchAcrossEntities"] + + logger.debug(json.dumps(response, indent=2)) + + for result in response["searchResults"]: + entity = result["entity"] + entity_type = entity["type"] + matched_fields = { + i["name"]: i["value"] for i in result.get("matchedFields", []) + } + + if entity_type == "DATA_PRODUCT": + page_results.append(self._parse_data_product(entity, matched_fields)) + elif entity_type == "DATASET": + page_results.append(self._parse_dataset(entity, matched_fields)) + else: + raise ValueError(f"Unexpected entity type: {entity_type}") + + return SearchResponse( + total_results=response["total"], page_results=page_results + ) + + def _map_result_types(self, result_types: Sequence[ResultType]): + """ + Map result types to Datahub EntityTypes + """ + types = [] + if ResultType.DATA_PRODUCT in result_types: + types.append("DATA_PRODUCT") + if ResultType.TABLE in result_types: + types.append("DATASET") + return types + + def _parse_owner(self, entity: dict[str, Any]): + """ + Parse ownership information, if it is set. + """ + ownership = entity.get("ownership") or {} + owners = [i["owner"] for i in ownership.get("owners", [])] + if owners: + properties = owners[0].get("properties") or {} + owner_email = properties.get("email", "") + owner_name = properties.get("fullName", properties.get("displayName", "")) + else: + owner_email = "" + owner_name = "" + + return owner_email, owner_name + + def _parse_last_updated(self, entity: dict[str, Any]) -> datetime | None: + """ + Parse the last updated timestamp, if available + """ + timestamp = entity.get("lastIngested") + if timestamp is None: + return None + return datetime.utcfromtimestamp(timestamp / 1000) + + def _parse_tags(self, entity: dict[str, Any]) -> list[str]: + """ + Parse tag information into a flat list of strings for displaying + as part of the search result. + """ + outer_tags = entity.get("tags") or {} + tags = [] + for tag in outer_tags.get("tags", []): + properties = tag["tag"]["properties"] + if properties: + tags.append(properties["name"]) + return tags + + def _parse_properties(self, entity: dict[str, Any]) -> dict[str, Any]: + """ + Parse properties and editableProperties into a single dictionary. + """ + properties = entity["properties"] or {} + editable_properties = entity.get("editableProperties") or {} + properties.update(editable_properties) + return properties + + def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: + """ + Map a dataset entity to a SearchResult + """ + owner_email, owner_name = self._parse_owner(entity) + properties = self._parse_properties(entity) + tags = self._parse_tags(entity) + last_updated = self._parse_last_updated(entity) + name = entity["name"] + + return SearchResult( + id=entity["urn"], + result_type=ResultType.TABLE, + matches=matches, + name=properties.get("name", name), + description=properties.get("description", ""), + metadata={ + "owner": owner_name, + "owner_email": owner_email, + }, + tags=tags, + last_updated=last_updated, + ) + + def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: + """ + Map a data product entity to a SearchResult + """ + domain = entity["domain"]["domain"] + owner_email, owner_name = self._parse_owner(entity) + properties = self._parse_properties(entity) + tags = self._parse_tags(entity) + last_updated = self._parse_last_updated(entity) + + return SearchResult( + id=entity["urn"], + result_type=ResultType.DATA_PRODUCT, + matches=matches, + name=properties["name"], + description=properties.get("description", ""), + metadata={ + "owner": owner_name, + "owner_email": owner_email, + "domain": domain, + }, + tags=tags, + last_updated=last_updated, + ) diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py new file mode 100644 index 00000000..3baf4b70 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -0,0 +1,27 @@ +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum, auto +from typing import Any + + +class ResultType(Enum): + DATA_PRODUCT = auto() + TABLE = auto() + + +@dataclass +class SearchResult: + id: str + result_type: ResultType + name: str + description: str = "" + matches: dict[str, str] = field(default_factory=dict) + metadata: dict[str, Any] = field(default_factory=dict) + tags: list[str] = field(default_factory=list) + last_updated: datetime | None = None + + +@dataclass +class SearchResponse: + total_results: int + page_results: list[SearchResult] diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py index 27992b4e..c56fe4be 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -1,7 +1,7 @@ from pathlib import Path import pytest -from data_platform_catalogue.client import DataHubCatalogueClient +from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient from data_platform_catalogue.entities import ( CatalogueMetadata, DataLocation, diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py new file mode 100644 index 00000000..d3c36dbd --- /dev/null +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -0,0 +1,309 @@ +from datetime import datetime +from unittest.mock import MagicMock + +import pytest +from data_platform_catalogue.client.datahub.search import SearchClient +from data_platform_catalogue.search_types import ( + ResultType, + SearchResponse, + SearchResult, +) + + +@pytest.fixture +def mock_graph(): + return MagicMock() + + +@pytest.fixture +def searcher(mock_graph): + return SearchClient(mock_graph) + + +def test_empty_search_results(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 0, + "total": 0, + "searchResults": [], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse(total_results=0, page_results=[]) + + +def test_one_search_result(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "entity": { + "type": "DATA_PRODUCT", + "urn": "urn:li:dataProduct:6cc5cbc4-c002-42c3-b80b-ed55df17d39f", + "ownership": None, + "properties": { + "name": "Use of force", + "description": "Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 + "customProperties": [], + "numAssets": 7, + }, + "domain": { + "domain": { + "urn": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + "id": "3dc18e48-c062-4407-84a9-73e23f768023", + "properties": { + "name": "HMPPS", + "description": "HMPPS is an executive agency that carries out sentences given by the courts, in custody and the community, and rehabilitates people through education and employment.", # noqa E501 + }, + } + }, + "tags": { + "tags": [ + { + "tag": { + "urn": "urn:li:tag:custody", + "properties": { + "name": "custody", + "description": "Data about prisons and prisoners. Not just NOMIS!", + }, + } + } + ] + }, + } + } + ], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataProduct:6cc5cbc4-c002-42c3-b80b-ed55df17d39f", + matches={}, + result_type=ResultType.DATA_PRODUCT, + name="Use of force", + description="Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 + metadata={ + "domain": { + "id": "3dc18e48-c062-4407-84a9-73e23f768023", + "properties": { + "name": "HMPPS", + "description": "HMPPS is an executive agency that carries out sentences given by the courts, in custody and the community, and rehabilitates people through education and employment.", # noqa E501 + }, + "urn": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + }, + "owner": "", + "owner_email": "", + }, + tags=["custody"], + ) + ], + ) + + +def test_full_page(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 3, + "total": 5, + "searchResults": [ + { + "insights": [], + "matchedFields": [], + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "platform": {"name": "bigquery"}, + "ownership": None, + "name": "calm-pagoda-323403.jaffle_shop.customers", + "properties": { + "name": "customers", + }, + "editableProperties": None, + "tags": None, + "lastIngested": 1705990502353, + }, + }, + { + "insights": [], + "matchedFields": [], + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers2", + "properties": { + "name": "customers2", + }, + }, + }, + { + "insights": [], + "matchedFields": [], + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers3,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers3", + "properties": { + "name": "customers3", + }, + }, + }, + ], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=5, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers", + description="", + metadata={"owner": "", "owner_email": ""}, + tags=[], + last_updated=datetime(2024, 1, 23, 6, 15, 2, 353000), + ), + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers2", + description="", + metadata={"owner": "", "owner_email": ""}, + tags=[], + last_updated=None, + ), + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers3,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers3", + description="", + metadata={"owner": "", "owner_email": ""}, + tags=[], + last_updated=None, + ), + ], + ) + + +def test_query_match(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "insights": [], + "matchedFields": [ + { + "name": "urn", + "value": "urn:li:dataset:(urn:li:dataPlatform:looker,long_tail_companions.view.customer_focused,PROD)", # noqa E501 + }, + {"name": "name", "value": "customer_focused"}, + ], + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers", + "properties": { + "name": "customers", + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={ + "urn": "urn:li:dataset:(urn:li:dataPlatform:looker,long_tail_companions.view.customer_focused,PROD)", # noqa E501 + "name": "customer_focused", + }, + result_type=ResultType.TABLE, + name="customers", + description="", + metadata={"owner": "", "owner_email": ""}, + tags=[], + ) + ], + ) + + +def test_result_with_owner(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers", + "ownership": { + "owners": [ + { + "owner": { + "urn": "urn:li:corpuser:shannon@longtail.com", + "properties": { + "fullName": "Shannon Lovett", + "email": "shannon@longtail.com", + }, + } + } + ] + }, + "properties": { + "name": "customers", + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers", + description="", + metadata={ + "owner": "Shannon Lovett", + "owner_email": "shannon@longtail.com", + }, + tags=[], + ) + ], + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 1dc89e74..3ad1bbab 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -11,8 +11,9 @@ import pytest from data_platform_catalogue import DataProductMetadata, TableMetadata -from data_platform_catalogue.client import DataHubCatalogueClient +from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient from data_platform_catalogue.entities import DataLocation +from data_platform_catalogue.search_types import ResultType from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass jwt_token = os.environ.get("JWT_TOKEN") @@ -59,3 +60,34 @@ def test_upsert_test_hierarchy(): # Ensure data went through assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) + + +@runs_on_development_server +def test_search(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + response = client.search() + assert response.total_results > 20 + assert len(response.page_results) == 20 + + +@runs_on_development_server +def test_search_for_data_product(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + data_product = DataProductMetadata( + name="lfdskjflkjflkjsdflksfjds", + description="lfdskjflkjflkjsdflksfjds", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="Sample", + dpia_required=False, + ) + client.upsert_data_product(data_product) + + response = client.search( + query="lfdskjflkjflkjsdflksfjds", result_types=(ResultType.DATA_PRODUCT,) + ) + assert response.total_results >= 1 + assert response.page_results[0].id == "urn:li:dataProduct:lfdskjflkjflkjsdflksfjds" From 47ef4b1b6eb3beab856456baaebfa8a937f3db76 Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 24 Jan 2024 13:12:50 +0000 Subject: [PATCH 14/64] Bump version (#3059) --- lib/datahub-client/CHANGELOG.md | 6 ++++++ lib/datahub-client/pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index d4b70d8b..2f17e26a 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] 2024-01-24 + +### Added + +- Search function + ## [0.4.0] 2024-01-19 ### Breaking changes diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index c22ad548..6e59cfc3 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.4.0" +version = "0.5.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From 9d848ab12ce3636612456b6e48f151b086c8914f Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:53:48 +0000 Subject: [PATCH 15/64] Added list data product method (#3068) * Added list data product method --- lib/datahub-client/CHANGELOG.md | 6 ++++++ lib/datahub-client/data_platform_catalogue/client/base.py | 3 +++ lib/datahub-client/pyproject.toml | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 2f17e26a..91bf7ffb 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.0] 2024-01-24 + +### Added + +- BaseCatalogueClient.list_data_products() + ## [0.5.0] 2024-01-24 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 5ab4f2e1..0d534d67 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -70,3 +70,6 @@ def search( Wraps the catalogue's search function. """ raise NotImplementedError + + def list_data_products(self) -> SearchResponse: + return self.search(count=500, result_types=[ResultType.DATA_PRODUCT]) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 6e59cfc3..fb50e4b4 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.5.0" +version = "0.6.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From 99c752b04e2319a23f5d61483762ec5e40ad7381 Mon Sep 17 00:00:00 2001 From: Mat Date: Fri, 26 Jan 2024 14:21:19 +0000 Subject: [PATCH 16/64] [DP-3074] Add filtering to search function (#3075) * Add parameter for search filters * Allow the search response to contain facet information Facets are the dynamic search filters that show how the search results break down across different dimensions. Returning these allows us to display only options that are relevant to the current result set and indicate the number of results matching each option. For most filters, we are expected to pass a URN as the value, so this is also the easiest way for the frontend to figure out what the possible values are. * Use relative imports throughout --- lib/datahub-client/CHANGELOG.md | 9 ++ lib/datahub-client/README.md | 20 ++++ .../data_platform_catalogue/client/base.py | 3 +- .../client/datahub/datahub_client.py | 16 +-- .../client/datahub/graphql/search.graphql | 34 +++++- .../client/datahub/search.py | 60 +++++++++- .../client/openmetadata.py | 7 +- .../data_platform_catalogue/search_types.py | 27 ++++- lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 108 ++++++++++++++++++ .../test_integration_with_datahub_server.py | 35 +++++- 11 files changed, 296 insertions(+), 25 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 91bf7ffb..a68d1609 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.0] 2024-01-25 + +### Added + +- Added filters param to the search function +- Return facets attribute to the search response. This is a dictionary mapping + fieldnames to `FacetOptions`, which expose values, display names and the + count of results with that value. + ## [0.6.0] 2024-01-24 ### Added diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 3569fdcb..4aeca66a 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -70,6 +70,26 @@ except CatalogueError: print("oh no") ``` +## Search example + +```python +response = client.search() + +# Total results across all pages +print(response.total_results) + +# Iterate over search results +for item in response.page_results: + print(item) + +# Iterate over facet options +for option in response.facets['domains']: + print(option) + +# Filter by domain +client.search(filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])]) +``` + ## Catalogue Implementations ### DataHub diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 0d534d67..8f02d228 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -8,7 +8,7 @@ DataProductMetadata, TableMetadata, ) -from ..search_types import ResultType, SearchResponse +from ..search_types import MultiSelectFilter, ResultType, SearchResponse logger = logging.getLogger(__name__) @@ -65,6 +65,7 @@ def search( ResultType.DATA_PRODUCT, ResultType.TABLE, ), + filters: Sequence[MultiSelectFilter] = (), ) -> SearchResponse: """ Wraps the catalogue's search function. diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 78b16204..e7402e92 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -2,13 +2,6 @@ import datahub.emitter.mce_builder as mce_builder import datahub.metadata.schema_classes as schema_classes -from data_platform_catalogue.client.base import ( - BaseCatalogueClient, - CatalogueError, - ReferencedEntityMissing, - logger, -) -from data_platform_catalogue.search_types import ResultType, SearchResponse from datahub.emitter.mce_builder import make_data_platform_urn from datahub.emitter.mcp import MetadataChangeProposalWrapper from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph @@ -31,6 +24,8 @@ DataProductMetadata, TableMetadata, ) +from ...search_types import MultiSelectFilter, ResultType, SearchResponse +from ..base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger from .search import SearchClient DATAHUB_DATA_TYPE_MAPPING = { @@ -325,10 +320,15 @@ def search( ResultType.DATA_PRODUCT, ResultType.TABLE, ), + filters: Sequence[MultiSelectFilter] = (), ) -> SearchResponse: """ Wraps the catalogue's search function. """ return self.search_client.search( - query=query, count=count, page=page, result_types=result_types + query=query, + count=count, + page=page, + result_types=result_types, + filters=filters, ) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index 506ba0fb..18835f97 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -3,13 +3,45 @@ query Search( $count: Int! $start: Int! $types: [EntityType!] + $filters: [FacetFilterInput!] ) { searchAcrossEntities( - input: { types: $types, query: $query, start: $start, count: $count } + input: { + types: $types + query: $query + start: $start + count: $count + filters: $filters + } ) { start count total + facets { + field + displayName + aggregations { + value + count + entity { + ... on Domain { + properties { + name + } + } + ... on Tag { + properties { + name + } + } + ... on GlossaryTerm { + properties { + name + } + } + } + } + } searchResults { insights { text diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index fdbe4894..1c0ae129 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -2,15 +2,18 @@ import logging from datetime import datetime from importlib.resources import files -from typing import Any, Sequence +from typing import Any, Literal, Sequence -from data_platform_catalogue.search_types import ( +from datahub.configuration.common import GraphError +from datahub.ingestion.graph.client import DataHubGraph + +from ...search_types import ( + FacetOption, + MultiSelectFilter, ResultType, SearchResponse, SearchResult, ) -from datahub.configuration.common import GraphError -from datahub.ingestion.graph.client import DataHubGraph logger = logging.getLogger(__name__) @@ -33,6 +36,7 @@ def search( ResultType.DATA_PRODUCT, ResultType.TABLE, ), + filters: Sequence[MultiSelectFilter] = (), ) -> SearchResponse: """ Wraps the catalogue's search function. @@ -43,8 +47,15 @@ def search( start = int(page) types = self._map_result_types(result_types) + formatted_filters = self._map_filters(filters) - variables = {"count": count, "query": query, "start": start, "types": types} + variables = { + "count": count, + "query": query, + "start": start, + "types": types, + "filters": formatted_filters, + } try: response = self.graph.execute_graphql(self.search_query, variables) @@ -53,6 +64,7 @@ def search( page_results = [] response = response["searchAcrossEntities"] + facets = self._parse_facets(response.get("facets", [])) logger.debug(json.dumps(response, indent=2)) @@ -71,7 +83,7 @@ def search( raise ValueError(f"Unexpected entity type: {entity_type}") return SearchResponse( - total_results=response["total"], page_results=page_results + total_results=response["total"], page_results=page_results, facets=facets ) def _map_result_types(self, result_types: Sequence[ResultType]): @@ -85,6 +97,14 @@ def _map_result_types(self, result_types: Sequence[ResultType]): types.append("DATASET") return types + def _map_filters(self, filters: Sequence[MultiSelectFilter]): + result = [] + for filter in filters: + result.append( + {"field": filter.filter_name, "values": filter.included_values} + ) + return result + def _parse_owner(self, entity: dict[str, Any]): """ Parse ownership information, if it is set. @@ -180,3 +200,31 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: tags=tags, last_updated=last_updated, ) + + def _parse_facets( + self, facets: list[dict[str, Any]] + ) -> dict[ + Literal["domains", "tags", "customProperties", "glossaryTerms"], + list[FacetOption], + ]: + """ + Parse the facets and aggregate information from the query results + """ + results = {} + for facet in facets: + field = facet["field"] + if field not in ("domains", "tags", "customProperties", "glossaryTerms"): + continue + + options = [] + for aggregate in facet["aggregations"]: + value = aggregate["value"] + count = aggregate["count"] + entity = aggregate.get("entity") or {} + properties = entity.get("properties") or {} + label = properties.get("name", value) + options.append(FacetOption(value=value, label=label, count=count)) + + results[field] = options + + return results diff --git a/lib/datahub-client/data_platform_catalogue/client/openmetadata.py b/lib/datahub-client/data_platform_catalogue/client/openmetadata.py index 91ad4d46..989072dd 100644 --- a/lib/datahub-client/data_platform_catalogue/client/openmetadata.py +++ b/lib/datahub-client/data_platform_catalogue/client/openmetadata.py @@ -1,12 +1,6 @@ import json from http import HTTPStatus -from data_platform_catalogue.client.base import ( - BaseCatalogueClient, - CatalogueError, - ReferencedEntityMissing, - logger, -) from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest from metadata.generated.schema.api.data.createDatabaseSchema import ( CreateDatabaseSchemaRequest, @@ -40,6 +34,7 @@ DataProductMetadata, TableMetadata, ) +from .base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger OMD_DATA_TYPE_MAPPING = { "boolean": OpenMetadataDataType.BOOLEAN, diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 3baf4b70..da9ca3ad 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from datetime import datetime from enum import Enum, auto -from typing import Any +from typing import Any, Literal class ResultType(Enum): @@ -9,6 +9,27 @@ class ResultType(Enum): TABLE = auto() +@dataclass +class MultiSelectFilter: + """ + Values to filter the result set by + """ + + filter_name: Literal["domains", "tags", "customProperties", "glossaryTerms"] + included_values: list[Any] + + +@dataclass +class FacetOption: + """ + A specific value that may be used to filter the search + """ + + value: str + label: str + count: int + + @dataclass class SearchResult: id: str @@ -25,3 +46,7 @@ class SearchResult: class SearchResponse: total_results: int page_results: list[SearchResult] + facets: dict[ + Literal["domains", "tags", "customProperties", "glossaryTerms"], + list[FacetOption], + ] = field(default_factory=dict) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index fb50e4b4..d7aa6a5a 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.6.0" +version = "0.7.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index d3c36dbd..acf00bdc 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -4,6 +4,8 @@ import pytest from data_platform_catalogue.client.datahub.search import SearchClient from data_platform_catalogue.search_types import ( + FacetOption, + MultiSelectFilter, ResultType, SearchResponse, SearchResult, @@ -307,3 +309,109 @@ def test_result_with_owner(mock_graph, searcher): ) ], ) + + +def test_filter(searcher, mock_graph): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 0, + "total": 0, + "searchResults": [], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search(filters=[MultiSelectFilter("domains", ["Abc", "Def"])]) + + assert response == SearchResponse( + total_results=0, + page_results=[], + ) + + +def test_facets(searcher, mock_graph): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 10, + "total": 10, + "searchResults": [], + "facets": [ + { + "field": "_entityType", + "displayName": "Type", + "aggregations": [ + {"value": "DATASET", "count": 1505, "entity": None} + ], + }, + { + "field": "glossaryTerms", + "displayName": "Glossary Term", + "aggregations": [ + { + "value": "urn:li:glossaryTerm:Classification.Sensitive", + "count": 1, + "entity": {"properties": {"name": "Sensitive"}}, + }, + { + "value": "urn:li:glossaryTerm:Silver", + "count": 1, + "entity": {"properties": None}, + }, + ], + }, + { + "field": "domains", + "displayName": "Domain", + "aggregations": [ + { + "value": "urn:li:domain:094dc54b-0ebc-40a6-a4cf-e1b75e8b8089", + "count": 7, + "entity": {"properties": {"name": "Pet Adoptions"}}, + }, + { + "value": "urn:li:domain:7186eeff-a860-4b0a-989f-69473a0c9c67", + "count": 4, + "entity": {"properties": {"name": "E-Commerce"}}, + }, + ], + }, + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + + assert response == SearchResponse( + total_results=10, + page_results=[], + facets={ + "glossaryTerms": [ + FacetOption( + value="urn:li:glossaryTerm:Classification.Sensitive", + label="Sensitive", + count=1, + ), + FacetOption( + value="urn:li:glossaryTerm:Silver", + label="urn:li:glossaryTerm:Silver", + count=1, + ), + ], + "domains": [ + FacetOption( + value="urn:li:domain:094dc54b-0ebc-40a6-a4cf-e1b75e8b8089", + label="Pet Adoptions", + count=7, + ), + FacetOption( + value="urn:li:domain:7186eeff-a860-4b0a-989f-69473a0c9c67", + label="E-Commerce", + count=4, + ), + ], + }, + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 3ad1bbab..aa54791c 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -13,7 +13,7 @@ from data_platform_catalogue import DataProductMetadata, TableMetadata from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient from data_platform_catalogue.entities import DataLocation -from data_platform_catalogue.search_types import ResultType +from data_platform_catalogue.search_types import MultiSelectFilter, ResultType from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass jwt_token = os.environ.get("JWT_TOKEN") @@ -91,3 +91,36 @@ def test_search_for_data_product(): ) assert response.total_results >= 1 assert response.page_results[0].id == "urn:li:dataProduct:lfdskjflkjflkjsdflksfjds" + + +@runs_on_development_server +def test_search_by_domain(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + response = client.search( + filters=[MultiSelectFilter("domains", ["does-not-exist"])], + result_types=(ResultType.DATA_PRODUCT,), + ) + assert response.total_results == 0 + + +@runs_on_development_server +def test_domain_facets_are_returned(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + data_product = DataProductMetadata( + name="lfdskjflkjflkjsdflksfjds", + description="lfdskjflkjflkjsdflksfjds", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="Sample", + dpia_required=False, + ) + client.upsert_data_product(data_product) + + response = client.search() + assert response.facets["domains"] From 80ca8880aeeb1df39134c9e7d0c9736c3279030e Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 30 Jan 2024 12:09:53 +0000 Subject: [PATCH 17/64] [DP-3106] Enhance metadata returned with search results (#3124) * Remove restrictive filter type, and test urn example * Switch to non-deprecated syntax * Document possible filters * Add number_of_assets to data products metadata * Add data product to metadata of datasets --- lib/datahub-client/CHANGELOG.md | 13 +++ lib/datahub-client/README.md | 34 +++++++- .../client/datahub/graphql/search.graphql | 21 ++++- .../client/datahub/search.py | 17 ++-- .../data_platform_catalogue/search_types.py | 6 +- .../tests/test_datahub_search.py | 87 ++++++++++++++++++- .../test_integration_with_datahub_server.py | 70 ++++++++++++++- 7 files changed, 232 insertions(+), 16 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index a68d1609..8e20af5e 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] 2024-01-29 + +### Added + +Enhanced the metadata returned with search results: + +- Added `number_of_assets` to data product metadata +- Added `data_products` and `total_data_products` to dataset metadata + +### Changed + +- Replaced deprecated Datahub `filters` parameter with `orFilters` + ## [0.7.0] 2024-01-25 ### Added diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 4aeca66a..3f3c9d6b 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -86,10 +86,42 @@ for item in response.page_results: for option in response.facets['domains']: print(option) -# Filter by domain +# Include a filter client.search(filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])]) ``` +## Search filters + +### Datahub + +Basic filters: + +- urn +- customProperties +- browsePaths / browsePathsV2 +- deprecated (boolean) +- removed (boolean) +- typeNames +- name, qualifiedName +- description, hasDescription + +Timestamps: + +- lastOperationTime (datetime) +- createdAt (timestamp) +- lastModifiedAt (timestamp) + +URNs: + +- platform / platformInstance +- tags, hasTags +- glossaryTerms, hasGlossaryTerms +- domains, hasDomain +- siblings +- owners, hasOwners +- roles, hasRoles +- container + ## Catalogue Implementations ### DataHub diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index 18835f97..c0c75dbd 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -11,7 +11,7 @@ query Search( query: $query start: $start count: $count - filters: $filters + orFilters: [{ and: $filters }] } ) { start @@ -58,6 +58,25 @@ query Search( platform { name } + relationships( + input: { + types: ["DataProductContains"] + direction: INCOMING + count: 10 + } + ) { + total + relationships { + entity { + urn + ... on DataProduct { + properties { + name + } + } + } + } + } ownership { owners { owner { diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 1c0ae129..d6cc66cd 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -2,7 +2,7 @@ import logging from datetime import datetime from importlib.resources import files -from typing import Any, Literal, Sequence +from typing import Any, Sequence from datahub.configuration.common import GraphError from datahub.ingestion.graph.client import DataHubGraph @@ -161,6 +161,13 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: tags = self._parse_tags(entity) last_updated = self._parse_last_updated(entity) name = entity["name"] + relationships = entity.get("relationships", {}) + total_data_products = relationships.get("total", 0) + data_products = relationships.get("relationships", []) + data_products = [ + {"id": i["entity"]["urn"], "name": i["entity"]["properties"]["name"]} + for i in data_products + ] return SearchResult( id=entity["urn"], @@ -171,6 +178,8 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: metadata={ "owner": owner_name, "owner_email": owner_email, + "total_data_products": total_data_products, + "data_products": data_products, }, tags=tags, last_updated=last_updated, @@ -196,6 +205,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: "owner": owner_name, "owner_email": owner_email, "domain": domain, + "number_of_assets": properties["numAssets"], }, tags=tags, last_updated=last_updated, @@ -203,10 +213,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: def _parse_facets( self, facets: list[dict[str, Any]] - ) -> dict[ - Literal["domains", "tags", "customProperties", "glossaryTerms"], - list[FacetOption], - ]: + ) -> dict[str, list[FacetOption],]: """ Parse the facets and aggregate information from the query results """ diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index da9ca3ad..1cf9fdef 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from datetime import datetime from enum import Enum, auto -from typing import Any, Literal +from typing import Any class ResultType(Enum): @@ -15,7 +15,7 @@ class MultiSelectFilter: Values to filter the result set by """ - filter_name: Literal["domains", "tags", "customProperties", "glossaryTerms"] + filter_name: str included_values: list[Any] @@ -47,6 +47,6 @@ class SearchResponse: total_results: int page_results: list[SearchResult] facets: dict[ - Literal["domains", "tags", "customProperties", "glossaryTerms"], + str, list[FacetOption], ] = field(default_factory=dict) diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index acf00bdc..fb602748 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -106,6 +106,7 @@ def test_one_search_result(mock_graph, searcher): }, "owner": "", "owner_email": "", + "number_of_assets": 7, }, tags=["custody"], ) @@ -176,7 +177,12 @@ def test_full_page(mock_graph, searcher): result_type=ResultType.TABLE, name="customers", description="", - metadata={"owner": "", "owner_email": ""}, + metadata={ + "owner": "", + "owner_email": "", + "data_products": [], + "total_data_products": 0, + }, tags=[], last_updated=datetime(2024, 1, 23, 6, 15, 2, 353000), ), @@ -186,7 +192,12 @@ def test_full_page(mock_graph, searcher): result_type=ResultType.TABLE, name="customers2", description="", - metadata={"owner": "", "owner_email": ""}, + metadata={ + "owner": "", + "owner_email": "", + "data_products": [], + "total_data_products": 0, + }, tags=[], last_updated=None, ), @@ -196,7 +207,12 @@ def test_full_page(mock_graph, searcher): result_type=ResultType.TABLE, name="customers3", description="", - metadata={"owner": "", "owner_email": ""}, + metadata={ + "owner": "", + "owner_email": "", + "data_products": [], + "total_data_products": 0, + }, tags=[], last_updated=None, ), @@ -248,7 +264,12 @@ def test_query_match(mock_graph, searcher): result_type=ResultType.TABLE, name="customers", description="", - metadata={"owner": "", "owner_email": ""}, + metadata={ + "owner": "", + "owner_email": "", + "data_products": [], + "total_data_products": 0, + }, tags=[], ) ], @@ -304,6 +325,8 @@ def test_result_with_owner(mock_graph, searcher): metadata={ "owner": "Shannon Lovett", "owner_email": "shannon@longtail.com", + "data_products": [], + "total_data_products": 0, }, tags=[], ) @@ -415,3 +438,59 @@ def test_facets(searcher, mock_graph): ], }, ) + + +def test_result_with_data_product(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers", + "relationships": { + "total": 1, + "relationships": [ + { + "entity": { + "urn": "urn:abc", + "properties": {"name": "abc"}, + } + } + ], + }, + "properties": { + "name": "customers", + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers", + description="", + metadata={ + "owner": "", + "owner_email": "", + "data_products": [{"id": "urn:abc", "name": "abc"}], + "total_data_products": 1, + }, + tags=[], + ) + ], + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index aa54791c..f6fda7d1 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -83,6 +83,7 @@ def test_search_for_data_product(): retention_period_in_days=365, domain="Sample", dpia_required=False, + tags=["test"], ) client.upsert_data_product(data_product) @@ -108,8 +109,6 @@ def test_search_by_domain(): def test_domain_facets_are_returned(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - data_product = DataProductMetadata( name="lfdskjflkjflkjsdflksfjds", description="lfdskjflkjflkjsdflksfjds", @@ -119,8 +118,75 @@ def test_domain_facets_are_returned(): retention_period_in_days=365, domain="Sample", dpia_required=False, + tags=["test"], ) client.upsert_data_product(data_product) response = client.search() assert response.facets["domains"] + + +@runs_on_development_server +def test_filter_by_urn(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + data_product = DataProductMetadata( + name="lfdskjflkjflkjsdflksfjds", + description="lfdskjflkjflkjsdflksfjds", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="Sample", + dpia_required=False, + tags=["test"], + ) + urn = client.upsert_data_product(data_product) + + response = client.search( + filters=[MultiSelectFilter(filter_name="urn", included_values=[urn])] + ) + assert response.total_results == 1 + + +@runs_on_development_server +def test_fetch_dataset_belonging_to_data_product(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + data_product = DataProductMetadata( + name="test_data_product", + description="bla bla", + version="v1.0.0", + owner="7804c127-d677-4900-82f9-83517e51bb94", + email="justice@justice.gov.uk", + retention_period_in_days=365, + domain="Sample", + dpia_required=False, + ) + + table = TableMetadata( + name="test_table", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=365, + tags=["test"], + ) + + urn = client.upsert_table( + metadata=table, + data_product_metadata=data_product, + location=DataLocation("test_data_product_v2"), + ) + + response = client.search( + filters=[MultiSelectFilter(filter_name="urn", included_values=[urn])] + ) + + assert response.total_results == 1 + + metadata = response.page_results[0].metadata + assert metadata["total_data_products"] == 1 + assert metadata["data_products"][0]["name"] == "test_data_product" From 86a818de7fadf33953030e1bec1ac88c0ee58e54 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 30 Jan 2024 14:02:25 +0000 Subject: [PATCH 18/64] [DP-3106] Add a way to fetch lists of domains and other search facets (#3129) Add functionality for rendering search facets E.g. the domain multi-select --- lib/datahub-client/CHANGELOG.md | 2 + lib/datahub-client/README.md | 6 +- .../data_platform_catalogue/client/base.py | 16 ++- .../client/datahub/datahub_client.py | 18 ++- .../client/datahub/graphql/facets.graphql | 41 +++++++ .../client/datahub/search.py | 39 ++++++- .../data_platform_catalogue/search_types.py | 34 +++++- lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 106 ++++++++++++++++-- .../test_integration_with_datahub_server.py | 2 +- 10 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 8e20af5e..862b6e2c 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -15,6 +15,8 @@ Enhanced the metadata returned with search results: - Added `number_of_assets` to data product metadata - Added `data_products` and `total_data_products` to dataset metadata +- Added separate search_facets method +- Added `SearchFacets`` class to make it easier to present facets ### Changed diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 3f3c9d6b..d8366bd6 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -83,8 +83,10 @@ for item in response.page_results: print(item) # Iterate over facet options -for option in response.facets['domains']: - print(option) +for option in response.facets.options('domains'): + print(option.label) + print(option.value) + print(option.count) # Include a filter client.search(filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])]) diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 8f02d228..e8a56ebd 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -8,7 +8,7 @@ DataProductMetadata, TableMetadata, ) -from ..search_types import MultiSelectFilter, ResultType, SearchResponse +from ..search_types import MultiSelectFilter, ResultType, SearchFacets, SearchResponse logger = logging.getLogger(__name__) @@ -72,5 +72,19 @@ def search( """ raise NotImplementedError + def search_facets( + self, + query: str = "*", + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + filters: Sequence[MultiSelectFilter] = (), + ) -> SearchFacets: + """ + Returns facets that can be used to filter the search results. + """ + raise NotImplementedError + def list_data_products(self) -> SearchResponse: return self.search(count=500, result_types=[ResultType.DATA_PRODUCT]) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index e7402e92..c28e86a9 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -24,7 +24,7 @@ DataProductMetadata, TableMetadata, ) -from ...search_types import MultiSelectFilter, ResultType, SearchResponse +from ...search_types import MultiSelectFilter, ResultType, SearchFacets, SearchResponse from ..base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger from .search import SearchClient @@ -332,3 +332,19 @@ def search( result_types=result_types, filters=filters, ) + + def search_facets( + self, + query: str = "*", + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + filters: Sequence[MultiSelectFilter] = (), + ) -> SearchFacets: + """ + Returns facets that can be used to filter the search results. + """ + return self.search_client.search_facets( + query=query, result_types=result_types, filters=filters + ) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql new file mode 100644 index 00000000..e9fa78eb --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql @@ -0,0 +1,41 @@ +query Facets( + $query: String! + $facets: [String!] + $types: [EntityType!] + $filters: [FacetFilterInput!] +) { + aggregateAcrossEntities( + input: { + types: $types + query: $query + facets: $facets + orFilters: [{ and: $filters }] + } + ) { + facets { + field + displayName + aggregations { + value + count + entity { + ... on Domain { + properties { + name + } + } + ... on Tag { + properties { + name + } + } + ... on GlossaryTerm { + properties { + name + } + } + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index d6cc66cd..9fc6c95d 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -11,6 +11,7 @@ FacetOption, MultiSelectFilter, ResultType, + SearchFacets, SearchResponse, SearchResult, ) @@ -60,7 +61,7 @@ def search( try: response = self.graph.execute_graphql(self.search_query, variables) except GraphError as e: - raise Exception("Unable to execute search") from e + raise Exception("Unable to execute search query") from e page_results = [] response = response["searchAcrossEntities"] @@ -86,6 +87,36 @@ def search( total_results=response["total"], page_results=page_results, facets=facets ) + def search_facets( + self, + query: str = "*", + result_types: Sequence[ResultType] = ( + ResultType.DATA_PRODUCT, + ResultType.TABLE, + ), + filters: Sequence[MultiSelectFilter] = (), + ) -> SearchFacets: + """ + Returns facets that can be used to filter the search results. + """ + types = self._map_result_types(result_types) + formatted_filters = self._map_filters(filters) + + variables = { + "query": query, + "facets": [], + "types": types, + "filters": formatted_filters, + } + + try: + response = self.graph.execute_graphql(self.search_query, variables) + except GraphError as e: + raise Exception("Unable to execute facets query") from e + + response = response["aggregateAcrossEntities"] + return self._parse_facets(response.get("facets", [])) + def _map_result_types(self, result_types: Sequence[ResultType]): """ Map result types to Datahub EntityTypes @@ -211,9 +242,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: last_updated=last_updated, ) - def _parse_facets( - self, facets: list[dict[str, Any]] - ) -> dict[str, list[FacetOption],]: + def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: """ Parse the facets and aggregate information from the query results """ @@ -234,4 +263,4 @@ def _parse_facets( results[field] = options - return results + return SearchFacets(results) diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 1cf9fdef..23c24997 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -42,11 +42,37 @@ class SearchResult: last_updated: datetime | None = None +@dataclass +class SearchFacets: + facets: dict[str, list[FacetOption]] = field(default_factory=dict) + + def options(self, field_name) -> list[FacetOption]: + """ + Return a list of FacetOptions to display in a search facet. + Each option includes label, value and count. + Returns an empty list if there are no options to display. + """ + return self.facets.get(field_name, []) + + def labels(self, field_name) -> list[str]: + """ + Return a list of labels to display in a search facet. + """ + return [f.label for f in self.options(field_name)] + + def lookup_label(self, field_name, label) -> FacetOption | None: + """ + Return the FacetOption matching a particular label. + """ + options = self.options(field_name) + for option in options: + if option.label == label: + return option + return None + + @dataclass class SearchResponse: total_results: int page_results: list[SearchResult] - facets: dict[ - str, - list[FacetOption], - ] = field(default_factory=dict) + facets: SearchFacets = field(default_factory=SearchFacets) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index d7aa6a5a..c8fcdd68 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.7.0" +version = "0.8.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index fb602748..08ba95c1 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -7,6 +7,7 @@ FacetOption, MultiSelectFilter, ResultType, + SearchFacets, SearchResponse, SearchResult, ) @@ -355,11 +356,7 @@ def test_filter(searcher, mock_graph): def test_facets(searcher, mock_graph): datahub_response = { - "searchAcrossEntities": { - "start": 0, - "count": 10, - "total": 10, - "searchResults": [], + "aggregateAcrossEntities": { "facets": [ { "field": "_entityType", @@ -406,12 +403,10 @@ def test_facets(searcher, mock_graph): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) - response = searcher.search() + response = searcher.search_facets() - assert response == SearchResponse( - total_results=10, - page_results=[], - facets={ + assert response == SearchFacets( + { "glossaryTerms": [ FacetOption( value="urn:li:glossaryTerm:Classification.Sensitive", @@ -436,7 +431,96 @@ def test_facets(searcher, mock_graph): count=4, ), ], - }, + } + ) + + +def test_search_results_with_facets(searcher, mock_graph): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 10, + "total": 10, + "searchResults": [], + "facets": [ + { + "field": "_entityType", + "displayName": "Type", + "aggregations": [ + {"value": "DATASET", "count": 1505, "entity": None} + ], + }, + { + "field": "glossaryTerms", + "displayName": "Glossary Term", + "aggregations": [ + { + "value": "urn:li:glossaryTerm:Classification.Sensitive", + "count": 1, + "entity": {"properties": {"name": "Sensitive"}}, + }, + { + "value": "urn:li:glossaryTerm:Silver", + "count": 1, + "entity": {"properties": None}, + }, + ], + }, + { + "field": "domains", + "displayName": "Domain", + "aggregations": [ + { + "value": "urn:li:domain:094dc54b-0ebc-40a6-a4cf-e1b75e8b8089", + "count": 7, + "entity": {"properties": {"name": "Pet Adoptions"}}, + }, + { + "value": "urn:li:domain:7186eeff-a860-4b0a-989f-69473a0c9c67", + "count": 4, + "entity": {"properties": {"name": "E-Commerce"}}, + }, + ], + }, + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + + assert response == SearchResponse( + total_results=10, + page_results=[], + facets=SearchFacets( + { + "glossaryTerms": [ + FacetOption( + value="urn:li:glossaryTerm:Classification.Sensitive", + label="Sensitive", + count=1, + ), + FacetOption( + value="urn:li:glossaryTerm:Silver", + label="urn:li:glossaryTerm:Silver", + count=1, + ), + ], + "domains": [ + FacetOption( + value="urn:li:domain:094dc54b-0ebc-40a6-a4cf-e1b75e8b8089", + label="Pet Adoptions", + count=7, + ), + FacetOption( + value="urn:li:domain:7186eeff-a860-4b0a-989f-69473a0c9c67", + label="E-Commerce", + count=4, + ), + ], + } + ), ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index f6fda7d1..3fe14ee7 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -123,7 +123,7 @@ def test_domain_facets_are_returned(): client.upsert_data_product(data_product) response = client.search() - assert response.facets["domains"] + assert response.facets.options("domains") @runs_on_development_server From 84b72800ed4ee30fe60bb236e6ebb260d7b93185 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 30 Jan 2024 14:25:10 +0000 Subject: [PATCH 19/64] Fix bug with facets query (#3133) --- .../data_platform_catalogue/client/datahub/search.py | 7 ++++++- lib/datahub-client/pyproject.toml | 2 +- .../tests/test_integration_with_datahub_server.py | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 9fc6c95d..815975a1 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -27,6 +27,11 @@ def __init__(self, graph: DataHubGraph): .joinpath("search.graphql") .read_text() ) + self.facets_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("facets.graphql") + .read_text() + ) def search( self, @@ -110,7 +115,7 @@ def search_facets( } try: - response = self.graph.execute_graphql(self.search_query, variables) + response = self.graph.execute_graphql(self.facets_query, variables) except GraphError as e: raise Exception("Unable to execute facets query") from e diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index c8fcdd68..24a6676f 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.8.0" +version = "0.8.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 3fe14ee7..6011fda3 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -124,6 +124,7 @@ def test_domain_facets_are_returned(): response = client.search() assert response.facets.options("domains") + assert client.search_facets().options("domains") @runs_on_development_server From e451a00203cc75a6228bf802812d05eb9b206ee9 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:27:06 +0000 Subject: [PATCH 20/64] Dp 3127 add sorting in search (#3142) * Added the ability to sort graphQL queries * Added sort parameter to base client class * Added tests for sort parameter * Allowed NoneType for sort parameter * Corrections for linter * Satisfied black * Add method SortOption.format() and it's test * Chnaged indentation * Added whitespace for linter --- lib/datahub-client/CHANGELOG.md | 9 +++++++++ lib/datahub-client/README.md | 7 +++++-- .../data_platform_catalogue/client/base.py | 9 ++++++++- .../client/datahub/datahub_client.py | 10 +++++++++- .../client/datahub/graphql/search.graphql | 2 ++ .../client/datahub/search.py | 5 +++++ .../data_platform_catalogue/search_types.py | 16 +++++++++++++++ lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 20 +++++++++++++++++++ lib/datahub-client/tests/test_search_types.py | 13 ++++++++++++ 10 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 lib/datahub-client/tests/test_search_types.py diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 862b6e2c..1b88eb29 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0] 2024-01-31 + +### Added + +Added the ability to sort search results + +- Added class `SortOption` to allow sorting of search results +- Added parameter `sort` to `SearchClient.search()` + ## [0.8.0] 2024-01-29 ### Added diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index d8366bd6..3a2f60dc 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -88,8 +88,11 @@ for option in response.facets.options('domains'): print(option.value) print(option.count) -# Include a filter -client.search(filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])]) +# Include a filter and sort +client.search( + filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])], + sort=SortOption(field="name", ascending=False) +) ``` ## Search filters diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index e8a56ebd..5ec19358 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -8,7 +8,13 @@ DataProductMetadata, TableMetadata, ) -from ..search_types import MultiSelectFilter, ResultType, SearchFacets, SearchResponse +from ..search_types import ( + MultiSelectFilter, + ResultType, + SearchFacets, + SearchResponse, + SortOption, +) logger = logging.getLogger(__name__) @@ -66,6 +72,7 @@ def search( ResultType.TABLE, ), filters: Sequence[MultiSelectFilter] = (), + sort: SortOption | None = None, ) -> SearchResponse: """ Wraps the catalogue's search function. diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index c28e86a9..659fc0b7 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -24,7 +24,13 @@ DataProductMetadata, TableMetadata, ) -from ...search_types import MultiSelectFilter, ResultType, SearchFacets, SearchResponse +from ...search_types import ( + MultiSelectFilter, + ResultType, + SearchFacets, + SearchResponse, + SortOption, +) from ..base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger from .search import SearchClient @@ -321,6 +327,7 @@ def search( ResultType.TABLE, ), filters: Sequence[MultiSelectFilter] = (), + sort: SortOption | None = None, ) -> SearchResponse: """ Wraps the catalogue's search function. @@ -331,6 +338,7 @@ def search( page=page, result_types=result_types, filters=filters, + sort=sort, ) def search_facets( diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index c0c75dbd..737d6695 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -4,6 +4,7 @@ query Search( $start: Int! $types: [EntityType!] $filters: [FacetFilterInput!] + $sort: SearchSortInput ) { searchAcrossEntities( input: { @@ -12,6 +13,7 @@ query Search( start: $start count: $count orFilters: [{ and: $filters }] + sortInput: $sort } ) { start diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 815975a1..6f98f1f5 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -14,6 +14,7 @@ SearchFacets, SearchResponse, SearchResult, + SortOption, ) logger = logging.getLogger(__name__) @@ -43,6 +44,7 @@ def search( ResultType.TABLE, ), filters: Sequence[MultiSelectFilter] = (), + sort: SortOption | None = None, ) -> SearchResponse: """ Wraps the catalogue's search function. @@ -63,6 +65,9 @@ def search( "filters": formatted_filters, } + if sort: + variables.update({"sort": sort.format()}) + try: response = self.graph.execute_graphql(self.search_query, variables) except GraphError as e: diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 23c24997..706262a4 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -19,6 +19,22 @@ class MultiSelectFilter: included_values: list[Any] +@dataclass +class SortOption: + """Set the search result sorting.""" + + field: str + ascending: bool = True + + def format(self): + return { + "sortCriterion": { + "field": self.field, + "sortOrder": "ASCENDING" if self.ascending else "DESCENDING", + } + } + + @dataclass class FacetOption: """ diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 24a6676f..08a4a871 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.8.1" +version = "0.9.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 08ba95c1..214093f5 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -10,6 +10,7 @@ SearchFacets, SearchResponse, SearchResult, + SortOption, ) @@ -354,6 +355,25 @@ def test_filter(searcher, mock_graph): ) +def test_sort(searcher, mock_graph): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 0, + "total": 0, + "searchResults": [], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search(sort=SortOption(field="name", ascending=False)) + + assert response == SearchResponse( + total_results=0, + page_results=[], + ) + + def test_facets(searcher, mock_graph): datahub_response = { "aggregateAcrossEntities": { diff --git a/lib/datahub-client/tests/test_search_types.py b/lib/datahub-client/tests/test_search_types.py new file mode 100644 index 00000000..bc3f5fb8 --- /dev/null +++ b/lib/datahub-client/tests/test_search_types.py @@ -0,0 +1,13 @@ +from data_platform_catalogue.search_types import SortOption + + +def test_format_sort_option(): + expected = { + "sortCriterion": { + "field": "test", + "sortOrder": "DESCENDING", + } + } + result = SortOption(field="test", ascending=False) + + assert result.format() == expected From 1c5af563c52541f4404d83fa2076322141ccf37d Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 1 Feb 2024 13:00:11 +0000 Subject: [PATCH 21/64] Add missing domain metadata to dataset search results, and add all available custom properties (#3134) * Add domain information for datasets. Previously datasets were not returning domain information. This adds that back in and parses out the ID and name. * lint * Add custom properties to search result metadata --- lib/datahub-client/CHANGELOG.md | 8 ++ .../client/datahub/search.py | 58 ++++++++---- lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 94 +++++++++++++++++-- 4 files changed, 135 insertions(+), 27 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 1b88eb29..a3fab307 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.0] 2024-02-01 + +### Changed + +- Custom properties are now added to the metadata of each search result +- Datasets return domain information +- Domain information is now returned as `domain_id` and `domain_name` metadata + ## [0.9.0] 2024-01-31 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 6f98f1f5..4fbfceb5 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -2,7 +2,7 @@ import logging from datetime import datetime from importlib.resources import files -from typing import Any, Sequence +from typing import Any, Sequence, Tuple from datahub.configuration.common import GraphError from datahub.ingestion.graph.client import DataHubGraph @@ -184,21 +184,38 @@ def _parse_tags(self, entity: dict[str, Any]) -> list[str]: tags.append(properties["name"]) return tags - def _parse_properties(self, entity: dict[str, Any]) -> dict[str, Any]: + def _parse_properties( + self, entity: dict[str, Any] + ) -> Tuple[dict[str, Any], dict[str, Any]]: """ Parse properties and editableProperties into a single dictionary. """ properties = entity["properties"] or {} editable_properties = entity.get("editableProperties") or {} properties.update(editable_properties) - return properties + custom_properties = { + i["key"]: i["value"] for i in properties.get("customProperties", []) + } + return properties, custom_properties + + def _parse_domain(self, entity: dict[str, Any]): + metadata = {} + domain = entity.get("domain") or {} + inner_domain = domain.get("domain") or {} + metadata["domain_id"] = inner_domain.get("urn", "") + if inner_domain: + domain_properties, _ = self._parse_properties(inner_domain) + metadata["domain_name"] = domain_properties.get("name", "") + else: + metadata["domain_name"] = "" + return metadata def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: """ Map a dataset entity to a SearchResult """ owner_email, owner_name = self._parse_owner(entity) - properties = self._parse_properties(entity) + properties, custom_properties = self._parse_properties(entity) tags = self._parse_tags(entity) last_updated = self._parse_last_updated(entity) name = entity["name"] @@ -210,18 +227,22 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: for i in data_products ] + metadata = { + "owner": owner_name, + "owner_email": owner_email, + "total_data_products": total_data_products, + "data_products": data_products, + } + metadata.update(self._parse_domain(entity)) + metadata.update(custom_properties) + return SearchResult( id=entity["urn"], result_type=ResultType.TABLE, matches=matches, name=properties.get("name", name), description=properties.get("description", ""), - metadata={ - "owner": owner_name, - "owner_email": owner_email, - "total_data_products": total_data_products, - "data_products": data_products, - }, + metadata=metadata, tags=tags, last_updated=last_updated, ) @@ -230,11 +251,17 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: """ Map a data product entity to a SearchResult """ - domain = entity["domain"]["domain"] owner_email, owner_name = self._parse_owner(entity) - properties = self._parse_properties(entity) + properties, custom_properties = self._parse_properties(entity) tags = self._parse_tags(entity) last_updated = self._parse_last_updated(entity) + metadata = { + "owner": owner_name, + "owner_email": owner_email, + "number_of_assets": properties["numAssets"], + } + metadata.update(self._parse_domain(entity)) + metadata.update(custom_properties) return SearchResult( id=entity["urn"], @@ -242,12 +269,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: matches=matches, name=properties["name"], description=properties.get("description", ""), - metadata={ - "owner": owner_name, - "owner_email": owner_email, - "domain": domain, - "number_of_assets": properties["numAssets"], - }, + metadata=metadata, tags=tags, last_updated=last_updated, ) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 08a4a871..08c3b2a5 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.9.0" +version = "0.10.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 214093f5..8d4cc308 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -98,14 +98,8 @@ def test_one_search_result(mock_graph, searcher): name="Use of force", description="Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 metadata={ - "domain": { - "id": "3dc18e48-c062-4407-84a9-73e23f768023", - "properties": { - "name": "HMPPS", - "description": "HMPPS is an executive agency that carries out sentences given by the courts, in custody and the community, and rehabilitates people through education and employment.", # noqa E501 - }, - "urn": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", - }, + "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + "domain_name": "HMPPS", "owner": "", "owner_email": "", "number_of_assets": 7, @@ -116,6 +110,78 @@ def test_one_search_result(mock_graph, searcher): ) +def test_dataset_result(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "insights": [], + "matchedFields": [], + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "platform": {"name": "bigquery"}, + "ownership": None, + "name": "calm-pagoda-323403.jaffle_shop.customers", + "properties": { + "name": "customers", + "customProperties": [ + {"key": "StoredAsSubDirectories", "value": "False"}, + { + "key": "CreatedByJob", + "value": "moj-reg-prod-hmpps-assess-risks-and-needs-prod-glue-job", + }, + ], + }, + "domain": { + "domain": { + "urn": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + "id": "3dc18e48-c062-4407-84a9-73e23f768023", + "properties": { + "name": "HMPPS", + "description": "HMPPS is an executive agency that ...", + }, + }, + "editableProperties": None, + "tags": None, + "lastIngested": 1705990502353, + }, + }, + } + ], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers", + description="", + metadata={ + "owner": "", + "owner_email": "", + "data_products": [], + "total_data_products": 0, + "domain_name": "HMPPS", + "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + "StoredAsSubDirectories": "False", + "CreatedByJob": "moj-reg-prod-hmpps-assess-risks-and-needs-prod-glue-job", + }, + tags=[], + ), + ], + ) + + def test_full_page(mock_graph, searcher): datahub_response = { "searchAcrossEntities": { @@ -184,6 +250,8 @@ def test_full_page(mock_graph, searcher): "owner_email": "", "data_products": [], "total_data_products": 0, + "domain_name": "", + "domain_id": "", }, tags=[], last_updated=datetime(2024, 1, 23, 6, 15, 2, 353000), @@ -199,6 +267,8 @@ def test_full_page(mock_graph, searcher): "owner_email": "", "data_products": [], "total_data_products": 0, + "domain_name": "", + "domain_id": "", }, tags=[], last_updated=None, @@ -214,6 +284,8 @@ def test_full_page(mock_graph, searcher): "owner_email": "", "data_products": [], "total_data_products": 0, + "domain_name": "", + "domain_id": "", }, tags=[], last_updated=None, @@ -271,6 +343,8 @@ def test_query_match(mock_graph, searcher): "owner_email": "", "data_products": [], "total_data_products": 0, + "domain_id": "", + "domain_name": "", }, tags=[], ) @@ -329,6 +403,8 @@ def test_result_with_owner(mock_graph, searcher): "owner_email": "shannon@longtail.com", "data_products": [], "total_data_products": 0, + "domain_id": "", + "domain_name": "", }, tags=[], ) @@ -593,6 +669,8 @@ def test_result_with_data_product(mock_graph, searcher): "owner_email": "", "data_products": [{"id": "urn:abc", "name": "abc"}], "total_data_products": 1, + "domain_id": "", + "domain_name": "", }, tags=[], ) From d20f15cbb0bb1437606dc91e6cd28e63bae1f9a0 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:26:41 +0000 Subject: [PATCH 22/64] Dp-3048-add-metadata-items (#3206) * Added additional metadata fields --- lib/datahub-client/CHANGELOG.md | 7 ++ .../client/datahub/datahub_client.py | 7 +- .../data_platform_catalogue/entities.py | 44 ++++++++++ lib/datahub-client/pyproject.toml | 2 +- .../tests/snapshots/datahub_create_table.json | 9 +- .../datahub_create_table_with_metadata.json | 19 ++++- ...tahub_create_two_tables_with_metadata.json | 43 +++++----- .../tests/test_client_datahub.py | 16 ++++ .../tests/test_client_openmetadata.py | 13 +++ lib/datahub-client/tests/test_entities.py | 31 ++++++- .../test_integration_with_datahub_server.py | 85 ++++++++++++++----- ...st_integration_with_openmetadata_server.py | 31 +++++-- 12 files changed, 252 insertions(+), 55 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index a3fab307..4a9f01c7 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.11.0] 2024-02-05 + +### Changed + +- Added data product level and table level metadata items +- Added metadata items to the datahub client + ## [0.10.0] 2024-02-01 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 659fc0b7..11d0051d 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -229,7 +229,12 @@ def upsert_table( dataset_properties = DatasetPropertiesClass( description=metadata.description, - # customProperties={"dpia_required": "yes"}, + customProperties={ + "sourceDatasetName": metadata.source_dataset_name, + "sourceDatasetLocation": metadata.source_dataset_location, + "sensitivityLevel": metadata.data_sensitivity_level, + "rowCount": "1177", + }, ) metadata_event = MetadataChangeProposalWrapper( diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index a758f3d2..a55c81c3 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -1,6 +1,10 @@ from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum, auto from typing import Any +DATAHUB_DATE_FORMAT = "%Y%m%d" + @dataclass class CatalogueMetadata: @@ -22,16 +26,30 @@ class DataLocation: platform_id: str = "glue" +class DataProductStatus(Enum): + DRAFT = auto() + PUBLISHED = auto() + RETIRED = auto() + + @dataclass class DataProductMetadata: name: str description: str version: str owner: str + owner_display_name: str + maintainer: str | None + maintainer_display_name: str | None email: str retention_period_in_days: int domain: str dpia_required: bool + dpia_location: str | None + last_updated: datetime + creation_date: datetime + s3_location: str | None + status: DataProductStatus = DataProductStatus.DRAFT tags: list[str] = field(default_factory=list) @staticmethod @@ -50,22 +68,43 @@ def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): description=metadata["description"], version=version, owner=owner_id, + owner_display_name=metadata["dataProductOwnerDisplayName"], + maintainer=metadata.get("dataProductMaintainer"), + maintainer_display_name=metadata.get("dataProductMaintainerDisplayName"), email=metadata["email"], + status=DataProductStatus[metadata["status"]], retention_period_in_days=metadata["retentionPeriod"], domain=metadata["domain"], dpia_required=metadata["dpiaRequired"], + dpia_location=metadata.get("dpiaLocation"), + last_updated=datetime.strptime( + metadata["lastUpdated"], DATAHUB_DATE_FORMAT + ), + creation_date=datetime.strptime( + metadata["creationDate"], DATAHUB_DATE_FORMAT + ), + s3_location=metadata.get("s3Location"), tags=metadata.get("tags", []), ) return new_metadata +class SecurityClassification(Enum): + OFFICIAL = auto() + SECRET = auto() + TOP_SECRET = auto() + + @dataclass class TableMetadata: name: str description: str column_details: list retention_period_in_days: int | None + source_dataset_name: str | None = None + source_dataset_location: str | None = None + data_sensitivity_level: SecurityClassification = SecurityClassification["OFFICIAL"] tags: list[str] = field(default_factory=list) major_version: int = 1 @@ -88,6 +127,11 @@ def from_data_product_schema_dict( description=metadata["tableDescription"], column_details=metadata["columns"], retention_period_in_days=retention_period, + source_dataset_name=metadata.get("sourceDatasetName"), + source_dataset_location=metadata.get("sourceDatasetLocation"), + data_sensitivity_level=SecurityClassification[ + metadata.get("securityClassification", "OFFICIAL") + ], tags=metadata.get("tags", []), ) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 08c3b2a5..a855ef7c 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.10.0" +version = "0.11.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json index 08236691..da0c017d 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -6,7 +6,12 @@ "aspectName": "datasetProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "sourceDatasetName": "my_source_table", + "sourceDatasetLocation": "s3://databucket/table1", + "sensitivityLevel": "TOP SECRET", + "rowCount": "1177" + }, "description": "bla bla", "tags": [] } @@ -67,4 +72,4 @@ } } } -] +] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index d6d2bd5f..6af7728f 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -6,7 +6,12 @@ "aspectName": "datasetProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "sourceDatasetName": "my_source_table", + "sourceDatasetLocation": "s3://databucket/table1", + "sensitivityLevel": "TOP SECRET", + "rowCount": "1177" + }, "description": "bla bla", "tags": [] } @@ -100,9 +105,17 @@ "aspect": { "json": { "customProperties": { + "owner_display_name": "April Gonzalez", + "maintainer": "j.shelvey@digital.justice.gov.uk", + "maintainer_display_name": "Jonjo Shelvey", "email": "justice@justice.gov.uk", "retention_period_in_days": "365", - "dpia_required": "False" + "dpia_required": "False", + "dpia_location": "None", + "last_updated": "2020-05-17 00:00:00", + "creation_date": "2020-05-17 00:00:00", + "s3_location": "s3://databucket/", + "status": "DataProductStatus.DRAFT" }, "name": "my_data_product", "description": "bla bla" @@ -126,4 +139,4 @@ } } } -] +] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index 4d424eef..49b8eaa0 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -6,7 +6,12 @@ "aspectName": "datasetProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "sourceDatasetName": "my_source_table", + "sourceDatasetLocation": "s3://databucket/table1", + "sensitivityLevel": "TOP SECRET", + "rowCount": "1177" + }, "description": "bla bla", "tags": [] } @@ -100,9 +105,17 @@ "aspect": { "json": { "customProperties": { + "owner_display_name": "April Gonzalez", + "maintainer": "j.shelvey@digital.justice.gov.uk", + "maintainer_display_name": "Jonjo Shelvey", "email": "justice@justice.gov.uk", "retention_period_in_days": "365", - "dpia_required": "False" + "dpia_required": "False", + "dpia_location": "None", + "last_updated": "2020-05-17 00:00:00", + "creation_date": "2020-05-17 00:00:00", + "s3_location": "s3://databucket/", + "status": "DataProductStatus.DRAFT" }, "name": "my_data_product", "description": "bla bla" @@ -133,7 +146,12 @@ "aspectName": "datasetProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "sourceDatasetName": "my_source_table", + "sourceDatasetLocation": "s3://databucket/table2", + "sensitivityLevel": "OFFICIAL", + "rowCount": "1177" + }, "description": "this is a different table", "tags": [] } @@ -219,23 +237,6 @@ } } }, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "email": "justice@justice.gov.uk", - "retention_period_in_days": "365", - "dpia_required": "False" - }, - "name": "my_data_product", - "description": "bla bla" - } - } -}, { "entityType": "dataproduct", "entityUrn": "urn:li:dataProduct:my_data_product", @@ -253,4 +254,4 @@ } } } -] +] \ No newline at end of file diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py index c56fe4be..2c984ccd 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -1,3 +1,4 @@ +from datetime import datetime from pathlib import Path import pytest @@ -6,6 +7,7 @@ CatalogueMetadata, DataLocation, DataProductMetadata, + DataProductStatus, TableMetadata, ) @@ -32,10 +34,18 @@ def data_product(self): description="bla bla", version="v1.0.0", owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) @@ -49,6 +59,9 @@ def table(self): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=365, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/table1", + data_sensitivity_level="TOP SECRET", ) @pytest.fixture @@ -61,6 +74,9 @@ def table2(self): {"name": "yar", "type": "string", "description": "shiver my timbers"}, ], retention_period_in_days=1, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/table2", + data_sensitivity_level="OFFICIAL", ) @pytest.fixture diff --git a/lib/datahub-client/tests/test_client_openmetadata.py b/lib/datahub-client/tests/test_client_openmetadata.py index d386a8d0..4220784e 100644 --- a/lib/datahub-client/tests/test_client_openmetadata.py +++ b/lib/datahub-client/tests/test_client_openmetadata.py @@ -1,3 +1,5 @@ +from datetime import datetime + import pytest from data_platform_catalogue.client import ReferencedEntityMissing from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient @@ -5,6 +7,7 @@ CatalogueMetadata, DataLocation, DataProductMetadata, + DataProductStatus, TableMetadata, ) @@ -85,10 +88,18 @@ def data_product(self): description="bla bla", version="v1.0.0", owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) @@ -102,6 +113,8 @@ def table(self): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=365, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/folder", ) @pytest.fixture diff --git a/lib/datahub-client/tests/test_entities.py b/lib/datahub-client/tests/test_entities.py index d425aeae..1e7debc8 100644 --- a/lib/datahub-client/tests/test_entities.py +++ b/lib/datahub-client/tests/test_entities.py @@ -1,5 +1,12 @@ +from datetime import datetime + import pytest -from data_platform_catalogue.entities import DataProductMetadata, TableMetadata +from data_platform_catalogue.entities import ( + DataProductMetadata, + DataProductStatus, + SecurityClassification, + TableMetadata, +) @pytest.fixture @@ -9,10 +16,18 @@ def data_product(): description="bla bla", version="v1.0.0", owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) @@ -27,6 +42,9 @@ def table(): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=None, + source_dataset_name="my_source_table", + source_dataset_location="s3://source-bucket/folder", + data_sensitivity_level=SecurityClassification.OFFICIAL, tags=["test"], ) @@ -38,11 +56,16 @@ def test_from_data_product_metadata_dict(data_product): "description": "bla bla", "domain": "legal-aid", "dataProductOwner": "justice@justice.gov.uk", - "dataProductOwnerDisplayName": "justice", + "dataProductOwnerDisplayName": "April Gonzalez", + "dataProductMaintainer": "j.shelvey@digital.justice.gov.uk", + "dataProductMaintainerDisplayName": "Jonjo Shelvey", "email": "justice@justice.gov.uk", - "status": "draft", + "status": "DRAFT", "dpiaRequired": False, "retentionPeriod": 365, + "lastUpdated": "20200517", + "creationDate": "20200517", + "s3Location": "s3://databucket/", "tags": ["test"], }, "v1.0.0", @@ -60,6 +83,8 @@ def test_from_data_product_schema_dict(table): {"name": "bar", "type": "int", "description": "b"}, ], "tags": ["test"], + "sourceDatasetName": "my_source_table", + "sourceDatasetLocation": "s3://source-bucket/folder", }, "my_table", ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 6011fda3..cd18ff25 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -8,11 +8,12 @@ """ import os +from datetime import datetime import pytest from data_platform_catalogue import DataProductMetadata, TableMetadata from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient -from data_platform_catalogue.entities import DataLocation +from data_platform_catalogue.entities import DataLocation, DataProductStatus from data_platform_catalogue.search_types import MultiSelectFilter, ResultType from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass @@ -26,14 +27,23 @@ def test_upsert_test_hierarchy(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( - name="test_data_product", + name="my_data_product", description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="Sample", + domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], ) table = TableMetadata( @@ -44,6 +54,8 @@ def test_upsert_test_hierarchy(): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=365, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/folder", tags=["test"], ) @@ -75,14 +87,22 @@ def test_search_for_data_product(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( - name="lfdskjflkjflkjsdflksfjds", - description="lfdskjflkjflkjsdflksfjds", + name="my_data_product", + description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="Sample", + domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) client.upsert_data_product(data_product) @@ -110,14 +130,22 @@ def test_domain_facets_are_returned(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( - name="lfdskjflkjflkjsdflksfjds", - description="lfdskjflkjflkjsdflksfjds", + name="my_data_product", + description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="Sample", + domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) client.upsert_data_product(data_product) @@ -132,14 +160,22 @@ def test_filter_by_urn(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( - name="lfdskjflkjflkjsdflksfjds", - description="lfdskjflkjflkjsdflksfjds", + name="my_data_product", + description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="Sample", + domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", tags=["test"], ) urn = client.upsert_data_product(data_product) @@ -155,14 +191,23 @@ def test_fetch_dataset_belonging_to_data_product(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( - name="test_data_product", + name="my_data_product", description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="Sample", + domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], ) table = TableMetadata( @@ -173,6 +218,8 @@ def test_fetch_dataset_belonging_to_data_product(): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=365, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/folder", tags=["test"], ) diff --git a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py index 0337d631..a34863a0 100644 --- a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py +++ b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py @@ -8,11 +8,12 @@ """ import os +from datetime import datetime import pytest from data_platform_catalogue import DataProductMetadata, TableMetadata from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient -from data_platform_catalogue.entities import DataLocation +from data_platform_catalogue.entities import DataLocation, DataProductStatus jwt_token = os.environ.get("JWT_TOKEN") api_url = os.environ.get("API_URL", "") @@ -29,22 +30,40 @@ def test_upsert_test_hierarchy(): name="my_data_product", description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], ) data_product_schema = DataProductMetadata( - name="Tables", - description="All the tables contained within my_data_product", + name="my_data_product", + description="bla bla", version="v1.0.0", - owner="7804c127-d677-4900-82f9-83517e51bb94", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", + status=DataProductStatus.DRAFT, retention_period_in_days=365, domain="legal-aid", dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], ) table = TableMetadata( @@ -55,6 +74,8 @@ def test_upsert_test_hierarchy(): {"name": "bar", "type": "int", "description": "b"}, ], retention_period_in_days=365, + source_dataset_name="my_source_table", + source_dataset_location="s3://databucket/folder", ) service_fqn = client.upsert_database_service(name="data_platform") From 7aa60d8289cf553a84c19d17cb9ce90a2ed5c21c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:27:08 +0000 Subject: [PATCH 23/64] Bump aiohttp from 3.9.1 to 3.9.2 in /python-libraries/data-platform-catalogue (#3126) Bump aiohttp in /python-libraries/data-platform-catalogue Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.1 to 3.9.2. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.1...v3.9.2) --- updated-dependencies: - dependency-name: aiohttp dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gary <26419401+Gary-H9@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 157 +++++++++++++++++---------------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index ddfcb8de..a6cd08df 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "acryl-datahub" @@ -123,87 +123,87 @@ vertica = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (< [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, + {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, + {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, + {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, + {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, + {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, + {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, + {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, + {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, + {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, + {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, + {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, ] [package.dependencies] @@ -2149,6 +2149,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, From ee4d45872aeb45db9c3f921fa327e6f5c8eb075a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 12:27:28 +0000 Subject: [PATCH 24/64] Bump cryptography from 41.0.6 to 42.0.0 in /python-libraries/data-platform-catalogue (#3211) Bump cryptography in /python-libraries/data-platform-catalogue Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.6 to 42.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.6...42.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 65 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index a6cd08df..675d8609 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -665,47 +665,56 @@ python-dateutil = "*" [[package]] name = "cryptography" -version = "41.0.6" +version = "42.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, - {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, - {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, - {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, + {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434"}, + {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b"}, + {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec"}, + {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc"}, + {file = "cryptography-42.0.0-cp37-abi3-win32.whl", hash = "sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4"}, + {file = "cryptography-42.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0"}, + {file = "cryptography-42.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139"}, + {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81"}, + {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221"}, + {file = "cryptography-42.0.0-cp39-abi3-win32.whl", hash = "sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b"}, + {file = "cryptography-42.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f"}, + {file = "cryptography-42.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0"}, + {file = "cryptography-42.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce"}, + {file = "cryptography-42.0.0.tar.gz", hash = "sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] From df7c6dc3780f97ef5fd670333a53a9126a249dd1 Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:15:23 +0000 Subject: [PATCH 25/64] Dpl 3207 data product metadata bug (#3235) * add fix to upsert_table to persist data product metadata * test data product metadata input * make tests pass and add check for product data persisting * updated snapshot golden files * Empty Commit * Empty Commit * remove to dot. * lint * update snapshot file for test * updates for new release --- lib/datahub-client/CHANGELOG.md | 6 ++ .../client/datahub/datahub_client.py | 7 ++- lib/datahub-client/pyproject.toml | 2 +- .../datahub_create_table_with_metadata.json | 7 ++- ...tahub_create_two_tables_with_metadata.json | 14 ++++- .../tests/test_client_datahub.py | 37 +++++++++++- .../golden_data_product_in.json | 59 +++++++++++++++++++ 7 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 lib/datahub-client/tests/test_resources/golden_data_product_in.json diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 4a9f01c7..d739a33a 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.12.0] 2024-02-08 + +### Changed + +- Added fix so upsert_table does not overwrite data product metadata. + ## [0.11.0] 2024-02-05 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 11d0051d..49c331e4 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -310,7 +310,12 @@ def upsert_table( else: assets = [data_product_association] - data_product_properties = DataProductPropertiesClass(assets=assets) + data_product_properties = DataProductPropertiesClass( + description=data_product_existing_properties.description, + name=data_product_existing_properties.name, + customProperties=data_product_existing_properties.customProperties, + assets=assets, + ) metadata_event = MetadataChangeProposalWrapper( entityType="dataproduct", diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index a855ef7c..386dd587 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.11.0" +version = "0.12.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index 6af7728f..9ca2cd54 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -129,7 +129,12 @@ "aspectName": "dataProductProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "version": "2.0", + "dpia": "false" + }, + "name": "my_data_product", + "description": "bla bla", "assets": [ { "sourceUrn": "urn:li:dataProduct:my_data_product", diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index 49b8eaa0..c9da106a 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -129,7 +129,12 @@ "aspectName": "dataProductProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "version": "2.0", + "dpia": "false" + }, + "name": "my_data_product", + "description": "bla bla", "assets": [ { "sourceUrn": "urn:li:dataProduct:my_data_product", @@ -244,7 +249,12 @@ "aspectName": "dataProductProperties", "aspect": { "json": { - "customProperties": {}, + "customProperties": { + "version": "2.0", + "dpia": "false" + }, + "name": "my_data_product", + "description": "bla bla", "assets": [ { "sourceUrn": "urn:li:dataProduct:my_data_product", diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py index 2c984ccd..0fe6ebc5 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -10,6 +10,7 @@ DataProductStatus, TableMetadata, ) +from datahub.metadata.schema_classes import DataProductPropertiesClass class TestCatalogueClientWithDatahub: @@ -85,12 +86,27 @@ def datahub_client(self, base_mock_graph) -> DataHubCatalogueClient: jwt_token="abc", api_url="http://example.com/api/gms", graph=base_mock_graph ) + @pytest.fixture + def golden_file_in(self): + return Path( + Path(__file__).parent / "test_resources/golden_data_product_in.json" + ) + def test_create_table_datahub( - self, datahub_client, base_mock_graph, table, tmp_path, check_snapshot + self, + datahub_client, + base_mock_graph, + table, + tmp_path, + check_snapshot, + golden_file_in, ): """ Case where we just create a dataset (no data product) """ + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in) + fqn = datahub_client.upsert_table( metadata=table, location=DataLocation(fully_qualified_name="my_database"), @@ -111,10 +127,14 @@ def test_create_table_with_metadata_datahub( base_mock_graph, tmp_path, check_snapshot, + golden_file_in, ): """ Case where we create a dataset, data product and domain """ + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in) + fqn = datahub_client.upsert_table( metadata=table, data_product_metadata=data_product, @@ -124,6 +144,12 @@ def test_create_table_with_metadata_datahub( assert fqn == fqn_out + # check data product properties persist + dp_properties = mock_graph.get_aspect( + "urn:li:dataProduct:my_data_product", aspect_type=DataProductPropertiesClass + ) + assert dp_properties.description == "bla bla" + assert dp_properties.customProperties == {"version": "2.0", "dpia": "false"} output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") base_mock_graph.sink_to_file(output_file) check_snapshot("datahub_create_table_with_metadata.json", output_file) @@ -137,10 +163,15 @@ def test_create_two_tables_with_metadata( base_mock_graph, tmp_path, check_snapshot, + golden_file_in, ): """ Case where we create a dataset, data product and domain """ + + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in) + fqn = datahub_client.upsert_table( metadata=table, data_product_metadata=data_product, @@ -171,10 +202,14 @@ def test_create_table_and_metadata_idempotent_datahub( base_mock_graph, tmp_path, check_snapshot, + golden_file_in, ): """ `create_table` should work even if the entities already exist in the metadata graph. """ + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in) + datahub_client.upsert_table( metadata=table, data_product_metadata=data_product, diff --git a/lib/datahub-client/tests/test_resources/golden_data_product_in.json b/lib/datahub-client/tests/test_resources/golden_data_product_in.json new file mode 100644 index 00000000..7cd57425 --- /dev/null +++ b/lib/datahub-client/tests/test_resources/golden_data_product_in.json @@ -0,0 +1,59 @@ +[ + { + "entityType": "dataProduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "dataProductProperties", + "aspect": { + "json": { + "customProperties": { + "version": "2.0", + "dpia": "false" + }, + "externalUrl": "https://github.com/datahub-project/datahub", + "name": "my_data_product", + "description": "bla bla", + "assets": [] + } + } + }, + { + "entityType": "dataProduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:legal-aid" + ] + } + } + }, + { + "entityType": "dataProduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "globalTags", + "aspect": { + "json": { + "tags": [ + { + "tag": "urn:li:tag:awesome" + } + ] + } + } + }, + { + "entityType": "dataProduct", + "entityUrn": "urn:li:dataProduct:my_data_product", + "changeType": "UPSERT", + "aspectName": "status", + "aspect": { + "json": { + "removed": false + } + } + } + ] From 5ff5989d8fe5f33a618dfd47e70d0634273cbeb8 Mon Sep 17 00:00:00 2001 From: Mitch Dawson <86007219+mitchdawson1982@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:32:34 +0000 Subject: [PATCH 26/64] :sparkles: Add subdomain field to dataproduct metadata (#3304) * :sparkles: Add subdomain field to dataproduct metadata * remove openmetadata files, adds subdomain and updates domain and subdomain refferences. * resolve broken tests * resolve comments * resolve comments pt 2 --- lib/datahub-client/CHANGELOG.md | 11 + lib/datahub-client/README.md | 3 +- .../client/datahub/datahub_client.py | 78 +++-- .../client/openmetadata.py | 231 ------------- .../data_platform_catalogue/entities.py | 14 +- .../tests/snapshots/datahub_create_table.json | 4 +- .../datahub_create_table_with_metadata.json | 23 +- ...tahub_create_two_tables_with_metadata.json | 50 ++- .../tests/test_client_datahub.py | 12 +- .../tests/test_client_openmetadata.py | 323 ------------------ lib/datahub-client/tests/test_entities.py | 8 +- .../test_integration_with_datahub_server.py | 39 ++- ...st_integration_with_openmetadata_server.py | 95 ------ .../golden_data_product_in.json | 4 +- 14 files changed, 140 insertions(+), 755 deletions(-) delete mode 100644 lib/datahub-client/data_platform_catalogue/client/openmetadata.py delete mode 100644 lib/datahub-client/tests/test_client_openmetadata.py delete mode 100644 lib/datahub-client/tests/test_integration_with_openmetadata_server.py diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index d739a33a..478183b1 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.13.0] 2024-02-14 + +### Changed + +- Added subdomain property to dataproduct +- Renamed `source_dataset_location` to `where_to_access_dataset` + +### Removed + +- openmetadata code + ## [0.12.0] 2024-02-08 ### Changed diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index 3a2f60dc..ed3202dc 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -45,7 +45,8 @@ data_product = DataProductMetadata( owner = "7804c127-d677-4900-82f9-83517e51bb94", email = "justice@justice.gov.uk", retention_period_in_days = 365, - domain = "HMCTS", + domain = "LAA", + subdomain = "Legal Aid", dpia_required = False ) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 49c331e4..1a269b12 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -31,7 +31,7 @@ SearchResponse, SortOption, ) -from ..base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger +from ..base import BaseCatalogueClient, CatalogueError, logger from .search import SearchClient DATAHUB_DATA_TYPE_MAPPING = { @@ -151,41 +151,51 @@ def upsert_data_product(self, metadata: DataProductMetadata): data_product_urn = "urn:li:dataProduct:" + "".join(name.split()) - if metadata.domain: - domain = metadata_dict.pop("domain") - domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) + domain = metadata_dict.pop("domain") + domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) - if domain_urn is None: - logger.info(f"creating new domain {domain} for {name}") - domain_urn = self.create_domain(domain=domain) + if domain_urn is None: + logger.info(f"creating new domain {domain} for {name}") + domain_urn = self.create_domain(domain=domain) - data_product_domain = DomainsClass(domains=[domain_urn]) - metadata_event = MetadataChangeProposalWrapper( - entityType="dataproduct", - changeType=ChangeTypeClass.UPSERT, - entityUrn=data_product_urn, - aspect=data_product_domain, - ) - self.graph.emit(metadata_event) - logger.info(f"Data Product {name} associated with domain {domain}") + subdomain_urn = None + if metadata.subdomain: + subdomain = metadata_dict.pop("subdomain") + subdomain_urn = self.graph.get_domain_urn_by_name(domain_name=subdomain) - data_product_properties = DataProductPropertiesClass( - customProperties={key: str(val) for key, val in metadata_dict.items()}, - description=description, - name=name, - ) - metadata_event = MetadataChangeProposalWrapper( - entityType="dataproduct", - changeType=ChangeTypeClass.UPSERT, - entityUrn=data_product_urn, - aspect=data_product_properties, - ) - self.graph.emit(metadata_event) - logger.info(f"Properties updated for Data Product {name} ") + if subdomain_urn is None: + logger.info(f"creating new subdomain {domain} for {name}") + domain_urn = self.create_domain( + domain=subdomain, parentDomain=domain_urn + ) - return data_product_urn - else: - raise ReferencedEntityMissing("Data Product must belong to a Domain") + data_product_domain = DomainsClass( + domains=[subdomain_urn if subdomain_urn else domain_urn] + ) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataproduct", + changeType=ChangeTypeClass.UPSERT, + entityUrn=data_product_urn, + aspect=data_product_domain, + ) + self.graph.emit(metadata_event) + logger.info(f"Data Product {name} associated with domain {domain}") + + data_product_properties = DataProductPropertiesClass( + customProperties={key: str(val) for key, val in metadata_dict.items()}, + description=description, + name=name, + ) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataproduct", + changeType=ChangeTypeClass.UPSERT, + entityUrn=data_product_urn, + aspect=data_product_properties, + ) + self.graph.emit(metadata_event) + logger.info(f"Properties updated for Data Product {name} ") + + return data_product_urn def upsert_table( self, @@ -231,8 +241,8 @@ def upsert_table( description=metadata.description, customProperties={ "sourceDatasetName": metadata.source_dataset_name, - "sourceDatasetLocation": metadata.source_dataset_location, - "sensitivityLevel": metadata.data_sensitivity_level, + "whereToAccessDataset": metadata.where_to_access_dataset, + "sensitivityLevel": metadata.data_sensitivity_level.name, "rowCount": "1177", }, ) diff --git a/lib/datahub-client/data_platform_catalogue/client/openmetadata.py b/lib/datahub-client/data_platform_catalogue/client/openmetadata.py deleted file mode 100644 index 989072dd..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/openmetadata.py +++ /dev/null @@ -1,231 +0,0 @@ -import json -from http import HTTPStatus - -from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest -from metadata.generated.schema.api.data.createDatabaseSchema import ( - CreateDatabaseSchemaRequest, -) -from metadata.generated.schema.api.data.createTable import CreateTableRequest -from metadata.generated.schema.entity.data.table import Column -from metadata.generated.schema.entity.data.table import DataType as OpenMetadataDataType -from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import ( - OpenMetadataConnection, -) -from metadata.generated.schema.entity.services.databaseService import ( - DatabaseServiceType, -) -from metadata.generated.schema.entity.teams.user import User -from metadata.generated.schema.security.client.openMetadataJWTClientConfig import ( - OpenMetadataJWTClientConfig, -) -from metadata.generated.schema.type.basic import Duration -from metadata.generated.schema.type.entityReference import EntityReference -from metadata.generated.schema.type.tagLabel import ( - LabelType, - State, - TagLabel, - TagSource, -) -from metadata.ingestion.ometa.ometa_api import APIError, OpenMetadata - -from ..entities import ( - CatalogueMetadata, - DataLocation, - DataProductMetadata, - TableMetadata, -) -from .base import BaseCatalogueClient, CatalogueError, ReferencedEntityMissing, logger - -OMD_DATA_TYPE_MAPPING = { - "boolean": OpenMetadataDataType.BOOLEAN, - "tinyint": OpenMetadataDataType.TINYINT, - "smallint": OpenMetadataDataType.SMALLINT, - "int": OpenMetadataDataType.INT, - "integer": OpenMetadataDataType.INT, - "bigint": OpenMetadataDataType.BIGINT, - "double": OpenMetadataDataType.DOUBLE, - "float": OpenMetadataDataType.FLOAT, - "decimal": OpenMetadataDataType.DECIMAL, - "char": OpenMetadataDataType.CHAR, - "varchar": OpenMetadataDataType.VARCHAR, - "string": OpenMetadataDataType.STRING, - "date": OpenMetadataDataType.DATE, - "timestamp": OpenMetadataDataType.TIMESTAMP, -} - - -class OpenMetadataCatalogueClient(BaseCatalogueClient): - """ - Client for pushing metadata to the OpenMetadata catalogue. - - Tables in the catalogue are arranged into the following hierarchy: - DatabaseService -> Database -> Schema -> Table - - If there is a problem communicating with the catalogue, methods will raise an instance of - CatalogueError. - """ - - def __init__( - self, - jwt_token, - api_url: str, - ): - self.server_config = OpenMetadataConnection( - hostPort=api_url, - securityConfig=OpenMetadataJWTClientConfig(jwtToken=jwt_token), - authProvider="openmetadata", - ) # pyright: ignore[reportGeneralTypeIssues] - self.metadata = OpenMetadata(self.server_config) - - def is_healthy(self) -> bool: - """ - Ping the catalogue health check and return True if healthy. - """ - return self.metadata.health_check() - - def upsert_database_service( - self, name: str = "data-platform", display_name: str = "Data platform" - ) -> str: - """ - Define a database service. - We have one service representing the connection to the data platform's internal - glue catalogue. - - Returns the fully qualified name of the metadata object in the catalogue. - """ - # Directly pass JSON as a workaround because in metadata.create_or_update won't let us pass - # an empty connection config if serviceType = Glue. - # This workaround can be removed when we update to OpenMetadata 1.2.0. - service = { - "name": name, - "displayName": display_name, - "serviceType": DatabaseServiceType.Glue.value, - "connection": {"config": {}}, - } - - logger.info(f"Creating {service}") - - response = self.metadata.client.put( - "/services/databaseServices", data=json.dumps(service) - ) - if response is not None: - return response["fullyQualifiedName"] - else: - raise ReferencedEntityMissing - - def upsert_database( - self, metadata: CatalogueMetadata | DataProductMetadata, location: DataLocation - ) -> str: - """ - Define a database. - There should be one database per data product. - """ - create_db = CreateDatabaseRequest( - name=metadata.name, - description=metadata.description, - tags=self._generate_tags(metadata.tags), - service=location.fully_qualified_name, - owner=EntityReference( - id=metadata.owner, type="user" - ), # pyright: ignore[reportGeneralTypeIssues] - ) - return self._upsert_entity(create_db) - - def upsert_schema( - self, metadata: DataProductMetadata, location: DataLocation - ) -> str: - """ - Define a database schema. - There should be one schema per data product and for now flexibility is retained - and metadata is of type DataProductMetadata but we'd expect a uniform name and - description for each data product, e.g: - name="Tables", description="All the tables contained within {data_product_name}" - """ - create_schema = CreateDatabaseSchemaRequest( - name=metadata.name, - description=metadata.description, - owner=EntityReference( - id=metadata.owner, type="user" - ), # pyright: ignore[reportGeneralTypeIssues] - tags=self._generate_tags(metadata.tags), - retentionPeriod=self._generate_duration(metadata.retention_period_in_days), - database=location.fully_qualified_name, - ) - return self._upsert_entity(create_schema) - - def upsert_table( - self, - metadata: TableMetadata, - location: DataLocation, - *args, - **kwargs, - ) -> str: - """ - Define a table. - There can be many tables per data product. - columns are expected to be a list of dicts in the format - {"name": "column1", "type": "string", "description": "just an example"} - """ - columns = [ - Column( - name=column["name"], - dataType=OMD_DATA_TYPE_MAPPING[column["type"]], - description=column["description"], - ) # pyright: ignore[reportGeneralTypeIssues] - # pyright is ignoring field(None,x) - for column in metadata.column_details - ] - create_table = CreateTableRequest( - name=metadata.name, - description=metadata.description, - retentionPeriod=self._generate_duration(metadata.retention_period_in_days), - tags=self._generate_tags(metadata.tags), - databaseSchema=location.fully_qualified_name, - columns=columns, - ) # pyright: ignore[reportGeneralTypeIssues] - return self._upsert_entity(create_table) - - def _upsert_entity(self, data) -> str: - logger.info(f"Creating {data.json()}") - - try: - response = self.metadata.create_or_update(data=data) - except APIError as exception: - if exception.status_code == HTTPStatus.NOT_FOUND: - raise ReferencedEntityMissing from exception - else: - raise CatalogueError from exception - except Exception as exception: - raise CatalogueError from exception - - return response.dict()["fullyQualifiedName"] - - def get_user_id(self, user_email: str): - """ - returns the user id from openmetadata when given a user's email - """ - username = user_email.split("@")[0] - user = self.metadata.get_by_name(entity=User, fqn=username) - if user is not None: - return user.id - else: - raise ReferencedEntityMissing - - def _generate_tags(self, tags: list[str]): - # TODO? update using the sdk logic: - # https://docs.open-metadata.org/v1.2.x/sdk/python/ingestion/tags#6-creating-the-classification - return [ - TagLabel( - tagFQN=tag, - labelType=LabelType.Automated, - source=TagSource.Classification, - state=State.Confirmed, - ) # pyright: ignore[reportGeneralTypeIssues] - for tag in tags - ] - - def _generate_duration(self, duration_in_days: int | None): - if duration_in_days is None: - return None - else: - return Duration.parse_obj(f"P{duration_in_days}D") diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index a55c81c3..0d28f3cd 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -44,6 +44,7 @@ class DataProductMetadata: email: str retention_period_in_days: int domain: str + subdomain: str | None dpia_required: bool dpia_location: str | None last_updated: datetime @@ -59,8 +60,6 @@ def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): required fields in the json schema at https://github.com/ministryofjustice/modernisation-platform-environments/tree/main/terraform/environments/data-platform/data-product-metadata-json-schema - and should be pass version and owner id as in openmetadata. - Then populates a DataProductMetadata object with the given data. """ new_metadata = DataProductMetadata( @@ -75,6 +74,7 @@ def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): status=DataProductStatus[metadata["status"]], retention_period_in_days=metadata["retentionPeriod"], domain=metadata["domain"], + subdomain=metadata.get("subdomain"), dpia_required=metadata["dpiaRequired"], dpia_location=metadata.get("dpiaLocation"), last_updated=datetime.strptime( @@ -102,9 +102,9 @@ class TableMetadata: description: str column_details: list retention_period_in_days: int | None - source_dataset_name: str | None = None - source_dataset_location: str | None = None - data_sensitivity_level: SecurityClassification = SecurityClassification["OFFICIAL"] + source_dataset_name: str = "" + where_to_access_dataset: str = "" + data_sensitivity_level: SecurityClassification = SecurityClassification.OFFICIAL tags: list[str] = field(default_factory=list) major_version: int = 1 @@ -127,8 +127,8 @@ def from_data_product_schema_dict( description=metadata["tableDescription"], column_details=metadata["columns"], retention_period_in_days=retention_period, - source_dataset_name=metadata.get("sourceDatasetName"), - source_dataset_location=metadata.get("sourceDatasetLocation"), + source_dataset_name=metadata.get("sourceDatasetName", ""), + where_to_access_dataset=metadata.get("sourceDatasetLocation", ""), data_sensitivity_level=SecurityClassification[ metadata.get("securityClassification", "OFFICIAL") ], diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json index da0c017d..14d6cd0a 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -8,8 +8,8 @@ "json": { "customProperties": { "sourceDatasetName": "my_source_table", - "sourceDatasetLocation": "s3://databucket/table1", - "sensitivityLevel": "TOP SECRET", + "whereToAccessDataset": "s3://databucket/table1", + "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, "description": "bla bla", diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index 9ca2cd54..fcc35275 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -8,8 +8,8 @@ "json": { "customProperties": { "sourceDatasetName": "my_source_table", - "sourceDatasetLocation": "s3://databucket/table1", - "sensitivityLevel": "TOP SECRET", + "whereToAccessDataset": "s3://databucket/table1", + "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, "description": "bla bla", @@ -74,16 +74,29 @@ }, { "entityType": "domain", - "entityUrn": "urn:li:domain:legal-aid", + "entityUrn": "urn:li:domain:LAA", "changeType": "UPSERT", "aspectName": "domainProperties", "aspect": { "json": { - "name": "legal-aid", + "name": "LAA", "description": "" } } }, +{ + "entityType": "domain", + "entityUrn": "urn:li:domain:Legal Aid", + "changeType": "UPSERT", + "aspectName": "domainProperties", + "aspect": { + "json": { + "name": "Legal Aid", + "description": "", + "parentDomain": "urn:li:domain:LAA" + } + } +}, { "entityType": "dataproduct", "entityUrn": "urn:li:dataProduct:my_data_product", @@ -92,7 +105,7 @@ "aspect": { "json": { "domains": [ - "urn:li:domain:legal-aid" + "urn:li:domain:Legal Aid" ] } } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index c9da106a..00898550 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -8,8 +8,8 @@ "json": { "customProperties": { "sourceDatasetName": "my_source_table", - "sourceDatasetLocation": "s3://databucket/table1", - "sensitivityLevel": "TOP SECRET", + "whereToAccessDataset": "s3://databucket/table1", + "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, "description": "bla bla", @@ -74,16 +74,29 @@ }, { "entityType": "domain", - "entityUrn": "urn:li:domain:legal-aid", + "entityUrn": "urn:li:domain:LAA", "changeType": "UPSERT", "aspectName": "domainProperties", "aspect": { "json": { - "name": "legal-aid", + "name": "LAA", "description": "" } } }, +{ + "entityType": "domain", + "entityUrn": "urn:li:domain:Legal Aid", + "changeType": "UPSERT", + "aspectName": "domainProperties", + "aspect": { + "json": { + "name": "Legal Aid", + "description": "", + "parentDomain": "urn:li:domain:LAA" + } + } +}, { "entityType": "dataproduct", "entityUrn": "urn:li:dataProduct:my_data_product", @@ -92,7 +105,7 @@ "aspect": { "json": { "domains": [ - "urn:li:domain:legal-aid" + "urn:li:domain:Legal Aid" ] } } @@ -153,7 +166,7 @@ "json": { "customProperties": { "sourceDatasetName": "my_source_table", - "sourceDatasetLocation": "s3://databucket/table2", + "whereToAccessDataset": "s3://databucket/table2", "sensitivityLevel": "OFFICIAL", "rowCount": "1177" }, @@ -217,31 +230,6 @@ } } }, -{ - "entityType": "domain", - "entityUrn": "urn:li:domain:legal-aid", - "changeType": "UPSERT", - "aspectName": "domainProperties", - "aspect": { - "json": { - "name": "legal-aid", - "description": "" - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "domains", - "aspect": { - "json": { - "domains": [ - "urn:li:domain:legal-aid" - ] - } - } -}, { "entityType": "dataproduct", "entityUrn": "urn:li:dataProduct:my_data_product", diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/test_client_datahub.py index 0fe6ebc5..2a2435a5 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/test_client_datahub.py @@ -8,6 +8,7 @@ DataLocation, DataProductMetadata, DataProductStatus, + SecurityClassification, TableMetadata, ) from datahub.metadata.schema_classes import DataProductPropertiesClass @@ -41,7 +42,8 @@ def data_product(self): email="justice@justice.gov.uk", status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -61,8 +63,8 @@ def table(self): ], retention_period_in_days=365, source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/table1", - data_sensitivity_level="TOP SECRET", + where_to_access_dataset="s3://databucket/table1", + data_sensitivity_level=SecurityClassification.TOP_SECRET, ) @pytest.fixture @@ -76,8 +78,8 @@ def table2(self): ], retention_period_in_days=1, source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/table2", - data_sensitivity_level="OFFICIAL", + where_to_access_dataset="s3://databucket/table2", + data_sensitivity_level=SecurityClassification.OFFICIAL, ) @pytest.fixture diff --git a/lib/datahub-client/tests/test_client_openmetadata.py b/lib/datahub-client/tests/test_client_openmetadata.py deleted file mode 100644 index 4220784e..00000000 --- a/lib/datahub-client/tests/test_client_openmetadata.py +++ /dev/null @@ -1,323 +0,0 @@ -from datetime import datetime - -import pytest -from data_platform_catalogue.client import ReferencedEntityMissing -from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient -from data_platform_catalogue.entities import ( - CatalogueMetadata, - DataLocation, - DataProductMetadata, - DataProductStatus, - TableMetadata, -) - - -class TestCatalogueClientWithOMD: - def mock_service_response(self, fqn): - return { - "fullyQualifiedName": fqn, - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "name": "foo", - "serviceType": "Glue", - } - - def mock_database_response(self, fqn): - return { - "fullyQualifiedName": fqn, - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "name": "foo", - "service": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "Glue", - }, - } - - def mock_schema_response(self, fqn): - return { - "fullyQualifiedName": fqn, - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "name": "foo", - "service": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "Glue", - }, - "database": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "", - }, - } - - def mock_table_response_omd(self): - return { - "fullyQualifiedName": "my_table", - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "name": "foo", - "service": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "Glue", - }, - "database": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "", - }, - "databaseSchema": { - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - "type": "", - }, - "columns": [], - } - - def mock_user_response(self, fqn): - return { - "email": "justice@justice.gov.uk", - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - } - - @pytest.fixture - def catalogue(self): - return CatalogueMetadata( - name="data_platform", - description="All data products hosted on the data platform", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - ) - - @pytest.fixture - def data_product(self): - return DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, - retention_period_in_days=365, - domain="legal-aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - @pytest.fixture - def table(self): - return TableMetadata( - name="my_table", - description="bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days=365, - source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/folder", - ) - - @pytest.fixture - def omd_client(self, requests_mock) -> OpenMetadataCatalogueClient: - requests_mock.get( - "http://example.com/api/v1/system/version", - json={"version": "1.2.0.1", "revision": "1", "timestamp": 0}, - ) - - return OpenMetadataCatalogueClient( - jwt_token="abc", api_url="http://example.com/api" - ) - - def test_create_service_omd(self, omd_client, requests_mock): - requests_mock.put( - "http://example.com/api/v1/services/databaseServices", - json=self.mock_service_response("some-service"), - ) - - fqn = omd_client.upsert_database_service() - - assert requests_mock.last_request.json() == { - "name": "data-platform", - "displayName": "Data platform", - "serviceType": "Glue", - "connection": {"config": {}}, - } - assert fqn == "some-service" - - def test_create_database_omd(self, request, omd_client, requests_mock, catalogue): - requests_mock.put( - "http://example.com/api/v1/databases", - json=self.mock_database_response("some-db"), - ) - - fqn: str = omd_client.upsert_database( - metadata=catalogue, location=DataLocation("data-platform") - ) - assert requests_mock.last_request.json() == { - "name": "data_platform", - "displayName": None, - "domain": None, - "lifeCycle": None, - "description": "All data products hosted on the data platform", - "tags": [], - "owner": { - "id": "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - "type": "user", - "name": None, - "fullyQualifiedName": None, - "description": None, - "displayName": None, - "deleted": None, - "href": None, - }, - "service": "data-platform", - "default": False, - "retentionPeriod": None, - "extension": None, - "sourceUrl": None, - } - assert fqn == "some-db" - - def test_create_schema(self, request, omd_client, requests_mock, data_product): - requests_mock.put( - "http://example.com/api/v1/databaseSchemas", - json=self.mock_schema_response("some-schema"), - ) - - fqn = omd_client.upsert_schema( - metadata=data_product, location=DataLocation("data-product") - ) - assert requests_mock.last_request.json() == { - "name": "my_data_product", - "displayName": None, - "domain": None, - "lifeCycle": None, - "description": "bla bla", - "owner": { - "deleted": None, - "description": None, - "displayName": None, - "fullyQualifiedName": None, - "href": None, - "id": "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - "name": None, - "type": "user", - }, - "database": "data-product", - "tags": [ - { - "description": None, - "displayName": None, - "name": None, - "href": None, - "labelType": "Automated", - "source": "Classification", - "state": "Confirmed", - "style": None, - "tagFQN": "test", - } - ], - "retentionPeriod": "P365D", - "extension": None, - "sourceUrl": None, - } - assert fqn == "some-schema" - - def test_create_table_omd(self, request, omd_client, requests_mock, table): - requests_mock.put( - "http://example.com/api/v1/tables", - json=self.mock_table_response_omd(), - ) - - fqn = omd_client.upsert_table( - metadata=table, location=DataLocation("data-platform.data-product.schema") - ) - assert requests_mock.called - assert requests_mock.last_request.json() == { - "name": "my_table", - "displayName": None, - "domain": None, - "lifeCycle": None, - "description": "bla bla", - "dataProducts": None, - "tableType": None, - "columns": [ - { - "name": "foo", - "displayName": None, - "dataType": "STRING", - "arrayDataType": None, - "dataLength": None, - "precision": None, - "scale": None, - "dataTypeDisplay": None, - "description": "a", - "fullyQualifiedName": None, - "tags": None, - "constraint": None, - "ordinalPosition": None, - "jsonSchema": None, - "children": None, - "customMetrics": None, - "profile": None, - }, - { - "name": "bar", - "displayName": None, - "dataType": "INT", - "arrayDataType": None, - "dataLength": None, - "precision": None, - "scale": None, - "dataTypeDisplay": None, - "description": "b", - "fullyQualifiedName": None, - "tags": None, - "constraint": None, - "ordinalPosition": None, - "jsonSchema": None, - "children": None, - "customMetrics": None, - "profile": None, - }, - ], - "tableConstraints": None, - "tablePartition": None, - "tableProfilerConfig": None, - "owner": None, - "databaseSchema": "data-platform.data-product.schema", - "tags": [], - "viewDefinition": None, - "retentionPeriod": "P365D", - "extension": None, - "sourceUrl": None, - "fileFormat": None, - } - assert fqn == "my_table" - - def test_404_handling_omd(self, request, omd_client, requests_mock, table): - requests_mock.put( - "http://example.com/api/v1/tables", - status_code=404, - json={"code": "something", "message": "something"}, - ) - - with pytest.raises(ReferencedEntityMissing): - omd_client.upsert_table( - metadata=table, - location=DataLocation("data-platform.data-product.schema"), - ) - - def test_get_user_id(self, request, requests_mock, omd_client): - requests_mock.get( - "http://example.com/api/v1/users/name/justice", - json={ - "email": "justice@justice.gov.uk", - "name": "justice", - "id": "39b855e3-84a5-491e-b9a5-c411e626e340", - }, - ) - - user_id = omd_client.get_user_id("justice@justice.gov.uk") - - assert "39b855e3-84a5-491e-b9a5-c411e626e340" in str(user_id) diff --git a/lib/datahub-client/tests/test_entities.py b/lib/datahub-client/tests/test_entities.py index 1e7debc8..99ec92ea 100644 --- a/lib/datahub-client/tests/test_entities.py +++ b/lib/datahub-client/tests/test_entities.py @@ -22,7 +22,8 @@ def data_product(): email="justice@justice.gov.uk", status=DataProductStatus.DRAFT, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -43,7 +44,7 @@ def table(): ], retention_period_in_days=None, source_dataset_name="my_source_table", - source_dataset_location="s3://source-bucket/folder", + where_to_access_dataset="s3://source-bucket/folder", data_sensitivity_level=SecurityClassification.OFFICIAL, tags=["test"], ) @@ -54,7 +55,8 @@ def test_from_data_product_metadata_dict(data_product): { "name": "my_data_product", "description": "bla bla", - "domain": "legal-aid", + "domain": "LAA", + "subdomain": "Legal Aid", "dataProductOwner": "justice@justice.gov.uk", "dataProductOwnerDisplayName": "April Gonzalez", "dataProductMaintainer": "j.shelvey@digital.justice.gov.uk", diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index cd18ff25..6419dc68 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -8,6 +8,7 @@ """ import os +import time from datetime import datetime import pytest @@ -35,9 +36,10 @@ def test_upsert_test_hierarchy(): maintainer="j.shelvey@digital.justice.gov.uk", maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, + status=DataProductStatus.DRAFT.name, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -55,7 +57,7 @@ def test_upsert_test_hierarchy(): ], retention_period_in_days=365, source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/folder", + where_to_access_dataset="s3://databucket/folder", tags=["test"], ) @@ -95,9 +97,10 @@ def test_search_for_data_product(): maintainer="j.shelvey@digital.justice.gov.uk", maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, + status=DataProductStatus.DRAFT.name, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -108,10 +111,10 @@ def test_search_for_data_product(): client.upsert_data_product(data_product) response = client.search( - query="lfdskjflkjflkjsdflksfjds", result_types=(ResultType.DATA_PRODUCT,) + query="my_data_product", result_types=(ResultType.DATA_PRODUCT,) ) assert response.total_results >= 1 - assert response.page_results[0].id == "urn:li:dataProduct:lfdskjflkjflkjsdflksfjds" + assert response.page_results[0].id == "urn:li:dataProduct:my_data_product" @runs_on_development_server @@ -138,9 +141,10 @@ def test_domain_facets_are_returned(): maintainer="j.shelvey@digital.justice.gov.uk", maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, + status=DataProductStatus.DRAFT.name, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -168,9 +172,10 @@ def test_filter_by_urn(): maintainer="j.shelvey@digital.justice.gov.uk", maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, + status=DataProductStatus.DRAFT.name, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -199,9 +204,10 @@ def test_fetch_dataset_belonging_to_data_product(): maintainer="j.shelvey@digital.justice.gov.uk", maintainer_display_name="Jonjo Shelvey", email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, + status=DataProductStatus.DRAFT.name, retention_period_in_days=365, - domain="legal-aid", + domain="LAA", + subdomain="Legal Aid", dpia_required=False, dpia_location=None, last_updated=datetime(2020, 5, 17), @@ -219,7 +225,7 @@ def test_fetch_dataset_belonging_to_data_product(): ], retention_period_in_days=365, source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/folder", + where_to_access_dataset="s3://databucket/folder", tags=["test"], ) @@ -228,13 +234,14 @@ def test_fetch_dataset_belonging_to_data_product(): data_product_metadata=data_product, location=DataLocation("test_data_product_v2"), ) + # Introduce sleep to combat race conditions with table association + time.sleep(2) response = client.search( filters=[MultiSelectFilter(filter_name="urn", included_values=[urn])] ) - assert response.total_results == 1 metadata = response.page_results[0].metadata assert metadata["total_data_products"] == 1 - assert metadata["data_products"][0]["name"] == "test_data_product" + assert metadata["data_products"][0]["name"] == "my_data_product" diff --git a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py b/lib/datahub-client/tests/test_integration_with_openmetadata_server.py deleted file mode 100644 index a34863a0..00000000 --- a/lib/datahub-client/tests/test_integration_with_openmetadata_server.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Integration test that runs against a development OpenMetadata server. - -Run with: -export API_URL='https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api' -export JWT_TOKEN=****** -poetry run pytest tests/test_integration_with_server.py -""" - -import os -from datetime import datetime - -import pytest -from data_platform_catalogue import DataProductMetadata, TableMetadata -from data_platform_catalogue.client.openmetadata import OpenMetadataCatalogueClient -from data_platform_catalogue.entities import DataLocation, DataProductStatus - -jwt_token = os.environ.get("JWT_TOKEN") -api_url = os.environ.get("API_URL", "") -runs_on_development_server = pytest.mark.skipif("not jwt_token or not api_url") - - -@runs_on_development_server -def test_upsert_test_hierarchy(): - client = OpenMetadataCatalogueClient(jwt_token=jwt_token, api_url=api_url) - - assert client.is_healthy() - - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, - retention_period_in_days=365, - domain="legal-aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - data_product_schema = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, - retention_period_in_days=365, - domain="legal-aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - table = TableMetadata( - name="my_table", - description="bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days=365, - source_dataset_name="my_source_table", - source_dataset_location="s3://databucket/folder", - ) - - service_fqn = client.upsert_database_service(name="data_platform") - assert service_fqn == "data_platform" - - database_fqn = client.upsert_database( - metadata=data_product, location=DataLocation(service_fqn) - ) - assert database_fqn == "data_platform.my_data_product" - - schema_fqn = client.upsert_schema( - metadata=data_product_schema, location=DataLocation(database_fqn) - ) - assert schema_fqn == "data_platform.my_data_product.Tables" - - table_fqn = client.upsert_table(metadata=table, location=DataLocation(schema_fqn)) - assert table_fqn == "data_platform.my_data_product.Tables.my_table" diff --git a/lib/datahub-client/tests/test_resources/golden_data_product_in.json b/lib/datahub-client/tests/test_resources/golden_data_product_in.json index 7cd57425..4fc6a087 100644 --- a/lib/datahub-client/tests/test_resources/golden_data_product_in.json +++ b/lib/datahub-client/tests/test_resources/golden_data_product_in.json @@ -25,7 +25,7 @@ "aspect": { "json": { "domains": [ - "urn:li:domain:legal-aid" + "urn:li:domain:LAA" ] } } @@ -56,4 +56,4 @@ } } } - ] +] From 63c0eebcfd08c677dad21ad97925a32ace5b9023 Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:12:07 +0000 Subject: [PATCH 27/64] Bug fix catalogue name and page search (#3381) * fix for getting correct page returned in search result * fix for dataset name add and not duplicate asset attach to data product * tests dataset properties return as expected * extra integration test for unique page results * updated golden files * add decorator... * poetry version and changelog * lint --- lib/datahub-client/CHANGELOG.md | 10 ++++++++ .../client/datahub/datahub_client.py | 4 ++- .../client/datahub/search.py | 2 +- lib/datahub-client/pyproject.toml | 2 +- .../tests/snapshots/datahub_create_table.json | 1 + .../datahub_create_table_with_metadata.json | 1 + ...tahub_create_two_tables_with_metadata.json | 2 ++ .../test_integration_with_datahub_server.py | 25 ++++++++++++++++++- 8 files changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 478183b1..2eb2e509 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.14.0] 2024-02-19 + +### Changed + +- bugfix - search now returns correct page results, where start is +individual search result index. +- bugfix - `upsert_table` client method now adds dataset name to datahub. +- bugfix - `upsert_table` client method no longer duplicates assets assocaited +with data product. + ## [0.13.0] 2024-02-14 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 1a269b12..6f85af16 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -238,6 +238,7 @@ def upsert_table( ) dataset_properties = DatasetPropertiesClass( + name=metadata.name, description=metadata.description, customProperties={ "sourceDatasetName": metadata.source_dataset_name, @@ -316,7 +317,8 @@ def upsert_table( and data_product_existing_properties.assets is not None ): assets = data_product_existing_properties.assets[::] - assets.append(data_product_association) + if data_product_association not in assets: + assets.append(data_product_association) else: assets = [data_product_association] diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 4fbfceb5..bac15997 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -52,7 +52,7 @@ def search( if page is None: start = 0 else: - start = int(page) + start = int(page) * count types = self._map_result_types(result_types) formatted_filters = self._map_filters(filters) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 386dd587..8edb982c 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.12.0" +version = "0.14.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json index 14d6cd0a..2f541e39 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -12,6 +12,7 @@ "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, + "name": "my_table", "description": "bla bla", "tags": [] } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index fcc35275..3699dd72 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -12,6 +12,7 @@ "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, + "name": "my_table", "description": "bla bla", "tags": [] } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index 00898550..6fb37172 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -12,6 +12,7 @@ "sensitivityLevel": "TOP_SECRET", "rowCount": "1177" }, + "name": "my_table", "description": "bla bla", "tags": [] } @@ -170,6 +171,7 @@ "sensitivityLevel": "OFFICIAL", "rowCount": "1177" }, + "name": "my_table2", "description": "this is a different table", "tags": [] } diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 6419dc68..39c27a25 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -4,7 +4,7 @@ Run with: export API_URL='https://catalogue.apps-tools.development.data-platform.service.justice.gov.uk/api' export JWT_TOKEN=****** -poetry run pytest tests/test_integration_with_server.py +poetry run pytest tests/test_integration_with_datahub_server.py """ import os @@ -75,6 +75,21 @@ def test_upsert_test_hierarchy(): assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) + dataset_properties = client.graph.get_aspect( + table_fqn, aspect_type=DatasetPropertiesClass + ) + # check properties been loaded to datahub dataset + assert dataset_properties.description == table.description + assert dataset_properties.name == table.name + assert ( + dataset_properties.customProperties["sourceDatasetName"] + == table.source_dataset_name + ) + assert ( + dataset_properties.customProperties["whereToAccessDataset"] + == table.where_to_access_dataset + ) + @runs_on_development_server def test_search(): @@ -245,3 +260,11 @@ def test_fetch_dataset_belonging_to_data_product(): metadata = response.page_results[0].metadata assert metadata["total_data_products"] == 1 assert metadata["data_products"][0]["name"] == "my_data_product" + + +@runs_on_development_server +def test_paginated_search_results_unique(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + results1 = client.search(page="1").page_results + results2 = client.search(page="2").page_results + assert not any(x in results1 for x in results2) From 98d1bad101fbf52b0ed642ce95193908951c3ce6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:07:32 +0000 Subject: [PATCH 28/64] Bump cryptography from 42.0.0 to 42.0.2 in /python-libraries/data-platform-catalogue (#3382) Bump cryptography in /python-libraries/data-platform-catalogue Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.0 to 42.0.2. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.0...42.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 675d8609..5949d5a8 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -665,43 +665,43 @@ python-dateutil = "*" [[package]] name = "cryptography" -version = "42.0.0" +version = "42.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:c640b0ef54138fde761ec99a6c7dc4ce05e80420262c20fa239e694ca371d434"}, - {file = "cryptography-42.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:678cfa0d1e72ef41d48993a7be75a76b0725d29b820ff3cfd606a5b2b33fda01"}, - {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:146e971e92a6dd042214b537a726c9750496128453146ab0ee8971a0299dc9bd"}, - {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87086eae86a700307b544625e3ba11cc600c3c0ef8ab97b0fda0705d6db3d4e3"}, - {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a68bfcf57a6887818307600c3c0ebc3f62fbb6ccad2240aa21887cda1f8df1b"}, - {file = "cryptography-42.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5a217bca51f3b91971400890905a9323ad805838ca3fa1e202a01844f485ee87"}, - {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ca20550bb590db16223eb9ccc5852335b48b8f597e2f6f0878bbfd9e7314eb17"}, - {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:33588310b5c886dfb87dba5f013b8d27df7ffd31dc753775342a1e5ab139e59d"}, - {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9515ea7f596c8092fdc9902627e51b23a75daa2c7815ed5aa8cf4f07469212ec"}, - {file = "cryptography-42.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:35cf6ed4c38f054478a9df14f03c1169bb14bd98f0b1705751079b25e1cb58bc"}, - {file = "cryptography-42.0.0-cp37-abi3-win32.whl", hash = "sha256:8814722cffcfd1fbd91edd9f3451b88a8f26a5fd41b28c1c9193949d1c689dc4"}, - {file = "cryptography-42.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:a2a8d873667e4fd2f34aedab02ba500b824692c6542e017075a2efc38f60a4c0"}, - {file = "cryptography-42.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8fedec73d590fd30c4e3f0d0f4bc961aeca8390c72f3eaa1a0874d180e868ddf"}, - {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be41b0c7366e5549265adf2145135dca107718fa44b6e418dc7499cfff6b4689"}, - {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca482ea80626048975360c8e62be3ceb0f11803180b73163acd24bf014133a0"}, - {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c58115384bdcfe9c7f644c72f10f6f42bed7cf59f7b52fe1bf7ae0a622b3a139"}, - {file = "cryptography-42.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:56ce0c106d5c3fec1038c3cca3d55ac320a5be1b44bf15116732d0bc716979a2"}, - {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:324721d93b998cb7367f1e6897370644751e5580ff9b370c0a50dc60a2003513"}, - {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:d97aae66b7de41cdf5b12087b5509e4e9805ed6f562406dfcf60e8481a9a28f8"}, - {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:85f759ed59ffd1d0baad296e72780aa62ff8a71f94dc1ab340386a1207d0ea81"}, - {file = "cryptography-42.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:206aaf42e031b93f86ad60f9f5d9da1b09164f25488238ac1dc488334eb5e221"}, - {file = "cryptography-42.0.0-cp39-abi3-win32.whl", hash = "sha256:74f18a4c8ca04134d2052a140322002fef535c99cdbc2a6afc18a8024d5c9d5b"}, - {file = "cryptography-42.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:14e4b909373bc5bf1095311fa0f7fcabf2d1a160ca13f1e9e467be1ac4cbdf94"}, - {file = "cryptography-42.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3005166a39b70c8b94455fdbe78d87a444da31ff70de3331cdec2c568cf25b7e"}, - {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:be14b31eb3a293fc6e6aa2807c8a3224c71426f7c4e3639ccf1a2f3ffd6df8c3"}, - {file = "cryptography-42.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bd7cf7a8d9f34cc67220f1195884151426ce616fdc8285df9054bfa10135925f"}, - {file = "cryptography-42.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c310767268d88803b653fffe6d6f2f17bb9d49ffceb8d70aed50ad45ea49ab08"}, - {file = "cryptography-42.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bdce70e562c69bb089523e75ef1d9625b7417c6297a76ac27b1b8b1eb51b7d0f"}, - {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e9326ca78111e4c645f7e49cbce4ed2f3f85e17b61a563328c85a5208cf34440"}, - {file = "cryptography-42.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:69fd009a325cad6fbfd5b04c711a4da563c6c4854fc4c9544bff3088387c77c0"}, - {file = "cryptography-42.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:988b738f56c665366b1e4bfd9045c3efae89ee366ca3839cd5af53eaa1401bce"}, - {file = "cryptography-42.0.0.tar.gz", hash = "sha256:6cf9b76d6e93c62114bd19485e5cb003115c134cf9ce91f8ac924c44f8c8c3f4"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, + {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, + {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, + {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, + {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, + {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, + {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, + {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, + {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, + {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, + {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, + {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, + {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, + {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, ] [package.dependencies] From d08f251f5d823f4e4026d2bd19c67cc14f008250 Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:02:47 +0000 Subject: [PATCH 29/64] Add list data product to catalogue client (#3396) * add list_data_product_assets method to client * tests for list_data_product_assets * up changelog and poetry version * add listDataProductAssets graphql file * lint * suggestions for graphql query * search client suggestion * add data_product_list_assets to base client * lint --- lib/datahub-client/CHANGELOG.md | 6 ++ .../data_platform_catalogue/client/base.py | 7 ++ .../client/datahub/datahub_client.py | 8 ++ .../graphql/listDataProductAssets.graphql | 94 +++++++++++++++++++ .../client/datahub/search.py | 39 ++++++++ lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 64 +++++++++++++ .../test_integration_with_datahub_server.py | 9 ++ 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 2eb2e509..8e605c05 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.15.0] 2024-02-20 + +### Added + +- a list_data_product_assets method the the datahub client and SearchClient + ## [0.14.0] 2024-02-19 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 5ec19358..9bfcc0c7 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -95,3 +95,10 @@ def search_facets( def list_data_products(self) -> SearchResponse: return self.search(count=500, result_types=[ResultType.DATA_PRODUCT]) + + @abstractmethod + def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: + """ + returns a list of data product children + """ + pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 6f85af16..f2803ada 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -378,3 +378,11 @@ def search_facets( return self.search_client.search_facets( query=query, result_types=result_types, filters=filters ) + + def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: + """ + returns a list of data product children + """ + return self.search_client.list_data_product_assets( + urn=urn, count=count, start=start + ) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql new file mode 100644 index 00000000..d1000032 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql @@ -0,0 +1,94 @@ +query dataProductDetails($urn: String!, $query: String!, $count: Int!, $start: Int!) { + listDataProductAssets( + urn: $urn + input: {query: $query, start: $start, count: $count} + ) { + start + count + total + searchResults { + entity { + type + ... on Dataset { + urn + type + platform { + name + } + relationships( + input: {types: ["DataProductContains"], direction: INCOMING, count: 10} + ) { + total + relationships { + entity { + urn + ... on DataProduct { + properties { + name + } + } + } + } + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + name + properties { + name + qualifiedName + description + customProperties { + key + value + } + created + lastModified + } + editableProperties { + description + } + tags { + tags { + tag { + urn + properties { + name + description + } + } + } + } + lastIngested + domain { + domain { + urn + id + properties { + name + description + } + } + } + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index bac15997..2d48732f 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -34,6 +34,12 @@ def __init__(self, graph: DataHubGraph): .read_text() ) + self.data_product_asset_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("listDataProductAssets.graphql") + .read_text() + ) + def search( self, query: str = "*", @@ -127,6 +133,39 @@ def search_facets( response = response["aggregateAcrossEntities"] return self._parse_facets(response.get("facets", [])) + def list_data_product_assets( + self, urn: str, count: int, start: int = 0 + ) -> SearchResponse: + """ + returns a SearchResponse containing all assets in given data product (by urn) + """ + variables = { + "urn": urn, + "query": "*", + "start": start, + "count": count, + } + try: + response = self.graph.execute_graphql( + self.data_product_asset_query, variables + ) + except GraphError as e: + raise Exception("Unable to execute listDataProductAssets query") from e + page_results = [] + for result in response["listDataProductAssets"]["searchResults"]: + entity = result["entity"] + entity_type = entity["type"] + matched_fields: dict = {} + if entity_type == "DATASET": + page_results.append(self._parse_dataset(entity, matched_fields)) + else: + raise ValueError(f"Unexpected entity type: {entity_type}") + + return SearchResponse( + total_results=response["listDataProductAssets"]["total"], + page_results=page_results, + ) + def _map_result_types(self, result_types: Sequence[ResultType]): """ Map result types to Datahub EntityTypes diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 8edb982c..8ed0d853 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.14.0" +version = "0.15.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 8d4cc308..7a16014b 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -676,3 +676,67 @@ def test_result_with_data_product(mock_graph, searcher): ) ], ) + + +def test_list_data_product_assets(mock_graph, searcher): + datahub_response = { + "listDataProductAssets": { + "start": 0, + "count": 20, + "total": 1, + "searchResults": [ + { + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "name": "calm-pagoda-323403.jaffle_shop.customers", + "relationships": { + "total": 1, + "relationships": [ + { + "entity": { + "urn": "urn:abc", + "properties": {"name": "abc"}, + } + } + ], + }, + "properties": { + "name": "customers", + "description": "just some customers", + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.list_data_product_assets( + urn="urn:li:dataProduct:test", + start=0, + count=20, + ) + + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + matches={}, + result_type=ResultType.TABLE, + name="customers", + description="just some customers", + metadata={ + "owner": "", + "owner_email": "", + "data_products": [{"id": "urn:abc", "name": "abc"}], + "total_data_products": 1, + "domain_id": "", + "domain_name": "", + }, + tags=[], + ) + ], + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 39c27a25..7f91bf0c 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -268,3 +268,12 @@ def test_paginated_search_results_unique(): results1 = client.search(page="1").page_results results2 = client.search(page="2").page_results assert not any(x in results1 for x in results2) + + +@runs_on_development_server +def test_list_data_product_assets_returns(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + assets = client.list_data_product_assets( + urn="urn:li:dataProduct:my_data_product", count=20 + ) + assert assets From eeecbb258a5da30d49d261c2e4c93484e37b4596 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:50:09 +0000 Subject: [PATCH 30/64] Bump cryptography from 42.0.2 to 42.0.4 in /python-libraries/data-platform-catalogue (#3408) Bump cryptography in /python-libraries/data-platform-catalogue Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 66 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 5949d5a8..18520283 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -665,43 +665,43 @@ python-dateutil = "*" [[package]] name = "cryptography" -version = "42.0.2" +version = "42.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, - {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, - {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, - {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, - {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, - {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, - {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, + {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, + {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, + {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, + {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, + {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, + {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, + {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, + {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, + {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, + {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, + {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, + {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, + {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, ] [package.dependencies] From 2e7e5343be9097ac2d8aa87cab48b7c950a9fe97 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Mon, 26 Feb 2024 13:12:53 +0000 Subject: [PATCH 31/64] Find-moj-data-83/glossary (#3449) * Added client method to get glossary terms * Bumped changelog for 0.16.0 * Linting fixes * Linting changes * Shortened some lines --- lib/datahub-client/CHANGELOG.md | 10 ++- .../client/datahub/datahub_client.py | 4 + .../datahub/graphql/getGlossaryTerms.graphql | 28 +++++++ .../client/datahub/search.py | 43 +++++++++++ .../data_platform_catalogue/search_types.py | 1 + lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 74 +++++++++++++++++++ .../test_integration_with_datahub_server.py | 7 ++ 8 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 8e605c05..bbf00530 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.16.0] 2024-02-20 + +### Added + +- a get_glossary method in the datahub client and SearchClient + ## [0.15.0] 2024-02-20 ### Added @@ -18,10 +24,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - bugfix - search now returns correct page results, where start is -individual search result index. + individual search result index. - bugfix - `upsert_table` client method now adds dataset name to datahub. - bugfix - `upsert_table` client method no longer duplicates assets assocaited -with data product. + with data product. ## [0.13.0] 2024-02-14 diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index f2803ada..67d64ecb 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -386,3 +386,7 @@ def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: return self.search_client.list_data_product_assets( urn=urn, count=count, start=start ) + + def get_glossary_terms(self, count: int = 1000) -> SearchResponse: + """Wraps the client's glossary terms query""" + return self.search_client.get_glossary_terms(count) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql new file mode 100644 index 00000000..1df2e8c8 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql @@ -0,0 +1,28 @@ +query getGlossaryTerms($count: Int!) { + searchAcrossEntities( + input: { types: GLOSSARY_TERM, query: "*", start: 0, count: $count } + ) { + start + count + total + searchResults { + entity { + ... on GlossaryTerm { + urn + properties { + name + description + } + parentNodes { + nodes { + properties { + name + description + } + } + } + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 2d48732f..fbbd71f0 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -39,6 +39,11 @@ def __init__(self, graph: DataHubGraph): .joinpath("listDataProductAssets.graphql") .read_text() ) + self.get_glossary_terms_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("getGlossaryTerms.graphql") + .read_text() + ) def search( self, @@ -175,6 +180,8 @@ def _map_result_types(self, result_types: Sequence[ResultType]): types.append("DATA_PRODUCT") if ResultType.TABLE in result_types: types.append("DATASET") + if ResultType.GLOSSARY_TERM in result_types: + types.append("GLOSSARY_TERM") return types def _map_filters(self, filters: Sequence[MultiSelectFilter]): @@ -335,3 +342,39 @@ def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: results[field] = options return SearchFacets(results) + + def _parse_glossary_term(self, entity) -> SearchResult: + properties, custom_properties = self._parse_properties(entity) + metadata = {"parentNodes": entity["parentNodes"]["nodes"]} + + return SearchResult( + id=entity["urn"], + result_type=ResultType.GLOSSARY_TERM, + matches={}, + name=properties["name"], + description=properties.get("description", ""), + metadata=metadata, + tags=[], + last_updated=None, + ) + + def get_glossary_terms(self, count: int = 1000) -> SearchResponse: + "Get some number of glossary terms from DataHub" + variables = {"count": count} + try: + response = self.graph.execute_graphql( + self.get_glossary_terms_query, variables + ) + except GraphError as e: + raise Exception("Unable to execute getGlossaryTerms query") from e + + page_results = [] + response = response["searchAcrossEntities"] + logger.debug(json.dumps(response, indent=2)) + + for result in response["searchResults"]: + page_results.append(self._parse_glossary_term(entity=result["entity"])) + + return SearchResponse( + total_results=response["total"], page_results=page_results + ) diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 706262a4..9976471b 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -7,6 +7,7 @@ class ResultType(Enum): DATA_PRODUCT = auto() TABLE = auto() + GLOSSARY_TERM = auto() @dataclass diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 8ed0d853..bf3e84c0 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.15.0" +version = "0.16.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 7a16014b..68a7fe34 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -740,3 +740,77 @@ def test_list_data_product_assets(mock_graph, searcher): ) ], ) + + +def test_get_glossary_terms(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 2, + "total": 2, + "searchResults": [ + { + "entity": { + "urn": "urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + "properties": { + "name": "IAO", + "description": "Information asset owner.\n", + }, + "parentNodes": { + "nodes": [ + { + "properties": { + "name": "Data protection terms", + "description": "Data protection terms", + } + } + ] + }, + } + }, + { + "entity": { + "urn": "urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", + "properties": { + "name": "Security classification", + "description": "Only data that is 'official'", + }, + "parentNodes": {"nodes": []}, + } + }, + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.get_glossary_terms(count=2) + print(response) + assert response == SearchResponse( + total_results=2, + page_results=[ + SearchResult( + id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + name="IAO", + description="Information asset owner.\n", + metadata={ + "parentNodes": [ + { + "properties": { + "name": "Data protection terms", + "description": "Data protection terms", + } + } + ] + }, + result_type=ResultType.GLOSSARY_TERM, + ), + SearchResult( + id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", + name="Security classification", + description="Only data that is 'official'", + metadata={"parentNodes": []}, + result_type=ResultType.GLOSSARY_TERM, + ), + ], + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 7f91bf0c..4392d1b2 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -277,3 +277,10 @@ def test_list_data_product_assets_returns(): urn="urn:li:dataProduct:my_data_product", count=20 ) assert assets + + +@runs_on_development_server +def test_get_glossary_terms_returns(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + assets = client.get_glossary_terms(count=20) + assert assets From 1f729a8d6fe29564dafaf1fd9fbd7fa593bc8fc5 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:23:59 +0000 Subject: [PATCH 32/64] Added get_glossary_terms to ABC to fix test mocking (#3463) * Added get_glossary_terms as an abstract method to fix test mocking --- lib/datahub-client/CHANGELOG.md | 6 ++++++ lib/datahub-client/data_platform_catalogue/client/base.py | 7 +++++++ lib/datahub-client/pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index bbf00530..710c4600 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.16.1] 2024-02-20 + +### Added + +- a get_glossary_terms to catalogue ABC for mocking in pytest + ## [0.16.0] 2024-02-20 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 9bfcc0c7..0e3d9847 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -102,3 +102,10 @@ def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: returns a list of data product children """ pass + + @abstractmethod + def get_glossary_terms(self, count: int) -> SearchResponse: + """ + returns a searchresponse of glossary terms + """ + pass diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index bf3e84c0..7c4d0956 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.16.0" +version = "0.16.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From 8cc363d7221572db74511b138dfb8cde5658a2be Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:07:09 +0000 Subject: [PATCH 33/64] use fully qualified name in upsert_table (#3468) * use fully qualified name in upsert_table * new golden files and update server test * changelog and poetry version * ad * redo the tests for qualifiedname and name * redo changelog and poetry version --- lib/datahub-client/CHANGELOG.md | 7 +++++++ .../client/datahub/datahub_client.py | 1 + lib/datahub-client/pyproject.toml | 2 +- .../tests/snapshots/datahub_create_table.json | 1 + .../snapshots/datahub_create_table_with_metadata.json | 1 + .../snapshots/datahub_create_two_tables_with_metadata.json | 2 ++ .../tests/test_integration_with_datahub_server.py | 1 + 7 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 710c4600..bfd4c742 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.17.0] 2024-02-28 + +### Added + +- upsert_table now sets datset name as single table name and + `qualifiedName` as the fully qualified name we define. + ## [0.16.1] 2024-02-20 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 67d64ecb..d132909a 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -239,6 +239,7 @@ def upsert_table( dataset_properties = DatasetPropertiesClass( name=metadata.name, + qualifiedName=name, description=metadata.description, customProperties={ "sourceDatasetName": metadata.source_dataset_name, diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 7c4d0956..d31d4b5f 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.16.1" +version = "0.17.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json index 2f541e39..089f8c98 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -13,6 +13,7 @@ "rowCount": "1177" }, "name": "my_table", + "qualifiedName": "my_database.my_table", "description": "bla bla", "tags": [] } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index 3699dd72..fbbcbcf8 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -13,6 +13,7 @@ "rowCount": "1177" }, "name": "my_table", + "qualifiedName": "my_database.my_table", "description": "bla bla", "tags": [] } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index 6fb37172..d810525e 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -13,6 +13,7 @@ "rowCount": "1177" }, "name": "my_table", + "qualifiedName": "my_database.my_table", "description": "bla bla", "tags": [] } @@ -172,6 +173,7 @@ "rowCount": "1177" }, "name": "my_table2", + "qualifiedName": "my_database.my_table2", "description": "this is a different table", "tags": [] } diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 4392d1b2..5e4f1d6d 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -80,6 +80,7 @@ def test_upsert_test_hierarchy(): ) # check properties been loaded to datahub dataset assert dataset_properties.description == table.description + assert dataset_properties.qualifiedName == f"test_data_product_v2.{table.name}" assert dataset_properties.name == table.name assert ( dataset_properties.customProperties["sourceDatasetName"] From 7d3f5034511e333866379e23efcca56515a5a8bb Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:05:20 +0000 Subject: [PATCH 34/64] Dc 3512 fqn in catalogue search (#3556) * add fqn into parsers for dataset and data product * add `fully_qualified_name` to `SearchResult` class * update tests * changelog and poetry --- lib/datahub-client/CHANGELOG.md | 9 ++++++ .../client/datahub/search.py | 2 ++ .../data_platform_catalogue/search_types.py | 1 + lib/datahub-client/poetry.lock | 32 ------------------- lib/datahub-client/pyproject.toml | 2 +- .../tests/test_datahub_search.py | 11 +++++++ 6 files changed, 24 insertions(+), 33 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index bfd4c742..fac139b4 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.18.0] 2024-03-04 + +### Added + +- `SearchResult` now returns a fully qualified name along with name + for datasets and data products. This is implemented in the clients + `search` method. We default fully_qualified_name for a data product + entity to `name` + ## [0.17.0] 2024-02-28 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index fbbd71f0..0bc63663 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -287,6 +287,7 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: result_type=ResultType.TABLE, matches=matches, name=properties.get("name", name), + fully_qualified_name=properties.get("qualifiedName", name), description=properties.get("description", ""), metadata=metadata, tags=tags, @@ -314,6 +315,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: result_type=ResultType.DATA_PRODUCT, matches=matches, name=properties["name"], + fully_qualified_name=properties.get("qualifiedName", properties["name"]), description=properties.get("description", ""), metadata=metadata, tags=tags, diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 9976471b..89b88b9f 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -52,6 +52,7 @@ class SearchResult: id: str result_type: ResultType name: str + fully_qualified_name: str = "" description: str = "" matches: dict[str, str] = field(default_factory=dict) metadata: dict[str, Any] = field(default_factory=dict) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 18520283..ccaa195d 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1493,16 +1493,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2158,7 +2148,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2684,51 +2673,30 @@ description = "Database Abstraction Library" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-1.4.50-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:54138aa80d2dedd364f4e8220eef284c364d3270aaef621570aa2bd99902e2e8"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-win32.whl", hash = "sha256:6c78e3fb4a58e900ec433b6b5f4efe1a0bf81bbb366ae7761c6e0051dd310ee3"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-win_amd64.whl", hash = "sha256:d55f7a33e8631e15af1b9e67c9387c894fedf6deb1a19f94be8731263c51d515"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:324b1fdd50e960a93a231abb11d7e0f227989a371e3b9bd4f1259920f15d0304"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-win32.whl", hash = "sha256:8bdab03ff34fc91bfab005e96f672ae207d87e0ac7ee716d74e87e7046079d8b"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-win_amd64.whl", hash = "sha256:52e01d60b06f03b0a5fc303c8aada405729cbc91a56a64cead8cb7c0b9b13c1a"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fb9adc4c6752d62c6078c107d23327aa3023ef737938d0135ece8ffb67d07030"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-win32.whl", hash = "sha256:1b9c4359d3198f341480e57494471201e736de459452caaacf6faa1aca852bd8"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-win_amd64.whl", hash = "sha256:35e4520f7c33c77f2636a1e860e4f8cafaac84b0b44abe5de4c6c8890b6aaa6d"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f5b1fb2943d13aba17795a770d22a2ec2214fc65cff46c487790192dda3a3ee7"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-win32.whl", hash = "sha256:7b4396452273aedda447e5aebe68077aa7516abf3b3f48408793e771d696f397"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-win_amd64.whl", hash = "sha256:752f9df3dddbacb5f42d8405b2d5885675a93501eb5f86b88f2e47a839cf6337"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:35c7ed095a4b17dbc8813a2bfb38b5998318439da8e6db10a804df855e3a9e3a"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-win32.whl", hash = "sha256:8a219688297ee5e887a93ce4679c87a60da4a5ce62b7cb4ee03d47e9e767f558"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-win_amd64.whl", hash = "sha256:a648770db002452703b729bdcf7d194e904aa4092b9a4d6ab185b48d13252f63"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4be4da121d297ce81e1ba745a0a0521c6cf8704634d7b520e350dce5964c71ac"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-win32.whl", hash = "sha256:a7c9b9dca64036008962dd6b0d9fdab2dfdbf96c82f74dbd5d86006d8d24a30f"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-win_amd64.whl", hash = "sha256:df200762efbd672f7621b253721644642ff04a6ff957236e0e2fe56d9ca34d2c"}, {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"}, ] diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index d31d4b5f..2511ec47 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.17.0" +version = "0.18.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 68a7fe34..10c6d17e 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -96,6 +96,7 @@ def test_one_search_result(mock_graph, searcher): matches={}, result_type=ResultType.DATA_PRODUCT, name="Use of force", + fully_qualified_name="Use of force", description="Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 metadata={ "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", @@ -128,6 +129,7 @@ def test_dataset_result(mock_graph, searcher): "name": "calm-pagoda-323403.jaffle_shop.customers", "properties": { "name": "customers", + "qualifiedName": "jaffle_shop.customers", "customProperties": [ {"key": "StoredAsSubDirectories", "value": "False"}, { @@ -165,6 +167,7 @@ def test_dataset_result(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="jaffle_shop.customers", description="", metadata={ "owner": "", @@ -200,6 +203,7 @@ def test_full_page(mock_graph, searcher): "name": "calm-pagoda-323403.jaffle_shop.customers", "properties": { "name": "customers", + "qualifiedName": "jaffle_shop.customers", }, "editableProperties": None, "tags": None, @@ -244,6 +248,7 @@ def test_full_page(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="jaffle_shop.customers", description="", metadata={ "owner": "", @@ -261,6 +266,7 @@ def test_full_page(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers2", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers2", description="", metadata={ "owner": "", @@ -278,6 +284,7 @@ def test_full_page(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers3", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers3", description="", metadata={ "owner": "", @@ -337,6 +344,7 @@ def test_query_match(mock_graph, searcher): }, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", metadata={ "owner": "", @@ -397,6 +405,7 @@ def test_result_with_owner(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", metadata={ "owner": "Shannon Lovett", @@ -663,6 +672,7 @@ def test_result_with_data_product(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", metadata={ "owner": "", @@ -727,6 +737,7 @@ def test_list_data_product_assets(mock_graph, searcher): matches={}, result_type=ResultType.TABLE, name="customers", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="just some customers", metadata={ "owner": "", From e3a2ace02c046a2992b98d611bf1b66afa7ccf9b Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Tue, 5 Mar 2024 19:34:51 +0000 Subject: [PATCH 35/64] Fix none fqn data catalogue (#3603) * do not let fqn equal `None` * update test * changelog and poetry --- lib/datahub-client/CHANGELOG.md | 8 ++++++++ .../client/datahub/search.py | 16 ++++++++++++++-- lib/datahub-client/pyproject.toml | 2 +- lib/datahub-client/tests/test_datahub_search.py | 4 +--- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index fac139b4..a7df64ca 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.18.1] 2024-03-05 + +### Changed + +- `SearchResult.fully_qualified_name` now returns `name` if datahub metadata + property `qualifiedName` has a value of `None`, which it can do in the + case of dbt ingestions. + ## [0.18.0] 2024-03-04 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 0bc63663..f0ad4566 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -282,12 +282,18 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: metadata.update(self._parse_domain(entity)) metadata.update(custom_properties) + fqn = ( + properties.get("qualifiedName", name) + if properties.get("qualifiedName") is not None + else name + ) + return SearchResult( id=entity["urn"], result_type=ResultType.TABLE, matches=matches, name=properties.get("name", name), - fully_qualified_name=properties.get("qualifiedName", name), + fully_qualified_name=fqn, description=properties.get("description", ""), metadata=metadata, tags=tags, @@ -310,12 +316,18 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: metadata.update(self._parse_domain(entity)) metadata.update(custom_properties) + fqn = ( + properties.get("qualifiedName", properties["name"]) + if properties.get("qualifiedName") is not None + else properties["name"] + ) + return SearchResult( id=entity["urn"], result_type=ResultType.DATA_PRODUCT, matches=matches, name=properties["name"], - fully_qualified_name=properties.get("qualifiedName", properties["name"]), + fully_qualified_name=fqn, description=properties.get("description", ""), metadata=metadata, tags=tags, diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 2511ec47..d03b0bba 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.18.0" +version = "0.18.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/test_datahub_search.py index 10c6d17e..6d088f1f 100644 --- a/lib/datahub-client/tests/test_datahub_search.py +++ b/lib/datahub-client/tests/test_datahub_search.py @@ -217,9 +217,7 @@ def test_full_page(mock_graph, searcher): "type": "DATASET", "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", # noqa E501 "name": "calm-pagoda-323403.jaffle_shop.customers2", - "properties": { - "name": "customers2", - }, + "properties": {"name": "customers2", "qualifiedName": None}, }, }, { From 29cd9e71b822df5eae22a953663c6dbc9d93d86c Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 6 Mar 2024 15:54:57 +0000 Subject: [PATCH 36/64] Add method for fetching schema metadata (#3604) In order to display table schemas we need a way to lookup dataset metadata in Datahub. This method fetches the schemaMetadata aspect and transforms it into a simpler structure for presentation. --- lib/datahub-client/CHANGELOG.md | 14 +- .../data_platform_catalogue/client/base.py | 7 + .../client/datahub/datahub_client.py | 26 ++++ .../datahub/graphql/getDatasetDetails.graphql | 93 ++++++++++++ .../client/datahub/graphql_helpers.py | 136 ++++++++++++++++++ .../client/datahub/search.py | 96 +++---------- lib/datahub-client/pyproject.toml | 2 +- lib/datahub-client/tests/client/__init__.py | 0 .../tests/client/datahub/__init__.py | 0 .../datahub/test_datahub_client.py} | 105 +++++++++++++- .../client/datahub/test_graphql_helpers.py | 120 ++++++++++++++++ .../datahub/test_search.py} | 0 12 files changed, 516 insertions(+), 83 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py create mode 100644 lib/datahub-client/tests/client/__init__.py create mode 100644 lib/datahub-client/tests/client/datahub/__init__.py rename lib/datahub-client/tests/{test_client_datahub.py => client/datahub/test_datahub_client.py} (64%) create mode 100644 lib/datahub-client/tests/client/datahub/test_graphql_helpers.py rename lib/datahub-client/tests/{test_datahub_search.py => client/datahub/test_search.py} (100%) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index a7df64ca..5a400c30 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.19.0] 2024-03-06 + +### Added + +- `get_table_details(urn)` method to fetch table details including column level metadata + ## [0.18.1] 2024-03-05 ### Changed @@ -20,16 +26,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `SearchResult` now returns a fully qualified name along with name - for datasets and data products. This is implemented in the clients - `search` method. We default fully_qualified_name for a data product - entity to `name` + for datasets and data products. This is implemented in the clients + `search` method. We default fully_qualified_name for a data product + entity to `name` ## [0.17.0] 2024-02-28 ### Added - upsert_table now sets datset name as single table name and - `qualifiedName` as the fully qualified name we define. + `qualifiedName` as the fully qualified name we define. ## [0.16.1] 2024-02-20 diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 0e3d9847..3c6cd4eb 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -109,3 +109,10 @@ def get_glossary_terms(self, count: int) -> SearchResponse: returns a searchresponse of glossary terms """ pass + + @abstractmethod + def get_table_details(self, urn) -> TableMetadata: + """ + returns the schema and detailed information about a table + """ + pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index d132909a..c2db12e8 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -1,7 +1,9 @@ +from importlib.resources import files from typing import Sequence import datahub.emitter.mce_builder as mce_builder import datahub.metadata.schema_classes as schema_classes +from datahub.configuration.common import GraphError from datahub.emitter.mce_builder import make_data_platform_urn from datahub.emitter.mcp import MetadataChangeProposalWrapper from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph @@ -32,6 +34,7 @@ SortOption, ) from ..base import BaseCatalogueClient, CatalogueError, logger +from .graphql_helpers import parse_columns, parse_properties from .search import SearchClient DATAHUB_DATA_TYPE_MAPPING = { @@ -88,6 +91,12 @@ def __init__(self, jwt_token, api_url: str, graph=None): self.graph = graph or DataHubGraph(self.server_config) self.search_client = SearchClient(self.graph) + self.dataset_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("getDatasetDetails.graphql") + .read_text() + ) + def upsert_database_service(self, platform: str = "glue", *args, **kwargs) -> str: """ Define a DataHub 'Data Platform'. This is a type of connection, e.g. 'hive' or 'glue'. @@ -391,3 +400,20 @@ def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: def get_glossary_terms(self, count: int = 1000) -> SearchResponse: """Wraps the client's glossary terms query""" return self.search_client.get_glossary_terms(count) + + def get_table_details(self, urn) -> TableMetadata: + try: + response = self.graph.execute_graphql(self.dataset_query, {"urn": urn})[ + "data" + ]["dataset"] + properties, custom_properties = parse_properties(response) + columns = parse_columns(response) + + return TableMetadata( + name=properties["name"], + description=properties.get("description", ""), + column_details=columns, + retention_period_in_days=custom_properties.get("retentionPeriodInDays"), + ) + except GraphError as e: + raise Exception("Unable to execute getDataset query") from e diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql new file mode 100644 index 00000000..526e4f55 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql @@ -0,0 +1,93 @@ +query getDatasetDetails($urn: String!) { + dataset(urn: $urn) { + platform { + name + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + name + properties { + name + qualifiedName + description + customProperties { + key + value + } + created + lastModified { + actor + } + } + editableProperties { + description + } + tags { + tags { + tag { + urn + properties { + name + description + } + } + } + } + lastIngested + domain { + domain { + urn + id + properties { + name + description + } + } + } + schemaMetadata { + fields { + fieldPath + label + nullable + description + type + nativeDataType + } + primaryKeys + foreignKeys { + name + foreignFields { + fieldPath + } + foreignDataset { + urn + properties { + name + qualifiedName + } + } + sourceFields { + fieldPath + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py new file mode 100644 index 00000000..c40f3630 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py @@ -0,0 +1,136 @@ +from collections import defaultdict +from datetime import datetime +from typing import Any, Tuple + + +def parse_owner(entity: dict[str, Any]): + """ + Parse ownership information, if it is set. + """ + ownership = entity.get("ownership") or {} + owners = [i["owner"] for i in ownership.get("owners", [])] + if owners: + properties = owners[0].get("properties") or {} + owner_email = properties.get("email", "") + owner_name = properties.get("fullName", properties.get("displayName", "")) + else: + owner_email = "" + owner_name = "" + + return owner_email, owner_name + + +def parse_last_updated(entity: dict[str, Any]) -> datetime | None: + """ + Parse the last updated timestamp, if available + """ + timestamp = entity.get("lastIngested") + if timestamp is None: + return None + return datetime.utcfromtimestamp(timestamp / 1000) + + +def parse_tags(entity: dict[str, Any]) -> list[str]: + """ + Parse tag information into a flat list of strings for displaying + as part of the search result. + """ + outer_tags = entity.get("tags") or {} + tags = [] + for tag in outer_tags.get("tags", []): + properties = tag["tag"]["properties"] + if properties: + tags.append(properties["name"]) + return tags + + +def parse_properties(entity: dict[str, Any]) -> Tuple[dict[str, Any], dict[str, Any]]: + """ + Parse properties and editableProperties into a single dictionary. + """ + properties = entity["properties"] or {} + editable_properties = entity.get("editableProperties") or {} + properties.update(editable_properties) + custom_properties = { + i["key"]: i["value"] for i in properties.get("customProperties", []) + } + return properties, custom_properties + + +def parse_domain(entity: dict[str, Any]): + metadata = {} + domain = entity.get("domain") or {} + inner_domain = domain.get("domain") or {} + metadata["domain_id"] = inner_domain.get("urn", "") + if inner_domain: + domain_properties, _ = parse_properties(inner_domain) + metadata["domain_name"] = domain_properties.get("name", "") + else: + metadata["domain_name"] = "" + return metadata + + +def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: + """ + Parse the schema metadata from Datahub into a flattened list of column + information. + + Note: The format of each column is similar to but not the same + as the format used when ingesting table metadata. + - `type` refers to the Datahub type, not AWS glue type + - `nullable`, 'isPrimaryKey` and `foreignKeys` metadata is added + """ + result = [] + + schema_metadata = entity.get("schemaMetadata", {}) + if not schema_metadata: + return [] + + primary_keys = set(schema_metadata.get("primaryKeys", ())) + + foreign_keys = defaultdict(list) + + # Attempt to match foreign keys to the main fields. + # + # Assumptions: + # - A given field may have multiple foreign keys to other datasets + # - Some foreign keys will not match on fieldPath, because fields + # may be defined using STRUCT types and foreign keys can reference + # subfields within the struct. We will simply ignore these. + for foreign_key in schema_metadata.get("foreignKeys", ()): + if not foreign_key["sourceFields"] or not foreign_key["foreignFields"]: + continue + + source_path = foreign_key["sourceFields"][0]["fieldPath"] + foreign_path = foreign_key["foreignFields"][0]["fieldPath"] + foreign_table_id = foreign_key["foreignDataset"]["urn"] + foreign_table_name = foreign_key["foreignDataset"]["properties"]["name"] + foreign_keys[source_path].append( + { + "tableId": foreign_table_id, + "fieldName": foreign_path, + "tableName": foreign_table_name, + } + ) + + for field in schema_metadata.get("fields", ()): + foreign_keys_for_field = foreign_keys[field["fieldPath"]] + + # Work out if the field is primary. + # This is an oversimplification: in the case of a composite + # primary key, we report that each component field is primary. + is_primary_key = field["fieldPath"] in primary_keys + + result.append( + { + "name": field["fieldPath"], + "description": field["description"], + "type": field["type"], + "nullable": field["nullable"], + "isPrimaryKey": is_primary_key, + "foreignKeys": foreign_keys_for_field, + } + ) + + # Sort primary keys first, then sort alphabetically + return sorted(result, key=lambda c: (0 if c["isPrimaryKey"] else 1, c["name"])) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index f0ad4566..a4bc97d0 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -1,8 +1,7 @@ import json import logging -from datetime import datetime from importlib.resources import files -from typing import Any, Sequence, Tuple +from typing import Any, Sequence from datahub.configuration.common import GraphError from datahub.ingestion.graph.client import DataHubGraph @@ -16,6 +15,13 @@ SearchResult, SortOption, ) +from .graphql_helpers import ( + parse_domain, + parse_last_updated, + parse_owner, + parse_properties, + parse_tags, +) logger = logging.getLogger(__name__) @@ -192,78 +198,14 @@ def _map_filters(self, filters: Sequence[MultiSelectFilter]): ) return result - def _parse_owner(self, entity: dict[str, Any]): - """ - Parse ownership information, if it is set. - """ - ownership = entity.get("ownership") or {} - owners = [i["owner"] for i in ownership.get("owners", [])] - if owners: - properties = owners[0].get("properties") or {} - owner_email = properties.get("email", "") - owner_name = properties.get("fullName", properties.get("displayName", "")) - else: - owner_email = "" - owner_name = "" - - return owner_email, owner_name - - def _parse_last_updated(self, entity: dict[str, Any]) -> datetime | None: - """ - Parse the last updated timestamp, if available - """ - timestamp = entity.get("lastIngested") - if timestamp is None: - return None - return datetime.utcfromtimestamp(timestamp / 1000) - - def _parse_tags(self, entity: dict[str, Any]) -> list[str]: - """ - Parse tag information into a flat list of strings for displaying - as part of the search result. - """ - outer_tags = entity.get("tags") or {} - tags = [] - for tag in outer_tags.get("tags", []): - properties = tag["tag"]["properties"] - if properties: - tags.append(properties["name"]) - return tags - - def _parse_properties( - self, entity: dict[str, Any] - ) -> Tuple[dict[str, Any], dict[str, Any]]: - """ - Parse properties and editableProperties into a single dictionary. - """ - properties = entity["properties"] or {} - editable_properties = entity.get("editableProperties") or {} - properties.update(editable_properties) - custom_properties = { - i["key"]: i["value"] for i in properties.get("customProperties", []) - } - return properties, custom_properties - - def _parse_domain(self, entity: dict[str, Any]): - metadata = {} - domain = entity.get("domain") or {} - inner_domain = domain.get("domain") or {} - metadata["domain_id"] = inner_domain.get("urn", "") - if inner_domain: - domain_properties, _ = self._parse_properties(inner_domain) - metadata["domain_name"] = domain_properties.get("name", "") - else: - metadata["domain_name"] = "" - return metadata - def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: """ Map a dataset entity to a SearchResult """ - owner_email, owner_name = self._parse_owner(entity) - properties, custom_properties = self._parse_properties(entity) - tags = self._parse_tags(entity) - last_updated = self._parse_last_updated(entity) + owner_email, owner_name = parse_owner(entity) + properties, custom_properties = parse_properties(entity) + tags = parse_tags(entity) + last_updated = parse_last_updated(entity) name = entity["name"] relationships = entity.get("relationships", {}) total_data_products = relationships.get("total", 0) @@ -279,7 +221,7 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: "total_data_products": total_data_products, "data_products": data_products, } - metadata.update(self._parse_domain(entity)) + metadata.update(parse_domain(entity)) metadata.update(custom_properties) fqn = ( @@ -304,16 +246,16 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: """ Map a data product entity to a SearchResult """ - owner_email, owner_name = self._parse_owner(entity) - properties, custom_properties = self._parse_properties(entity) - tags = self._parse_tags(entity) - last_updated = self._parse_last_updated(entity) + owner_email, owner_name = parse_owner(entity) + properties, custom_properties = parse_properties(entity) + tags = parse_tags(entity) + last_updated = parse_last_updated(entity) metadata = { "owner": owner_name, "owner_email": owner_email, "number_of_assets": properties["numAssets"], } - metadata.update(self._parse_domain(entity)) + metadata.update(parse_domain(entity)) metadata.update(custom_properties) fqn = ( @@ -358,7 +300,7 @@ def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: return SearchFacets(results) def _parse_glossary_term(self, entity) -> SearchResult: - properties, custom_properties = self._parse_properties(entity) + properties, custom_properties = parse_properties(entity) metadata = {"parentNodes": entity["parentNodes"]["nodes"]} return SearchResult( diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index d03b0bba..c2fe6fe1 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.18.1" +version = "0.19.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/__init__.py b/lib/datahub-client/tests/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/datahub-client/tests/client/datahub/__init__.py b/lib/datahub-client/tests/client/datahub/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/datahub-client/tests/test_client_datahub.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py similarity index 64% rename from lib/datahub-client/tests/test_client_datahub.py rename to lib/datahub-client/tests/client/datahub/test_datahub_client.py index 2a2435a5..5ffd507e 100644 --- a/lib/datahub-client/tests/test_client_datahub.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -1,5 +1,6 @@ from datetime import datetime from pathlib import Path +from unittest.mock import MagicMock import pytest from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient @@ -91,7 +92,7 @@ def datahub_client(self, base_mock_graph) -> DataHubCatalogueClient: @pytest.fixture def golden_file_in(self): return Path( - Path(__file__).parent / "test_resources/golden_data_product_in.json" + Path(__file__).parent / "../../test_resources/golden_data_product_in.json" ) def test_create_table_datahub( @@ -230,3 +231,105 @@ def test_create_table_and_metadata_idempotent_datahub( output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") base_mock_graph.sink_to_file(output_file) check_snapshot("datahub_create_table_with_metadata.json", output_file) + + def test_get_dataset( + self, + datahub_client, + base_mock_graph, + ): + urn = "abc" + datahub_response = { + "data": { + "dataset": { + "platform": {"name": "datahub"}, + "ownership": None, + "name": "Dataset", + "properties": { + "name": "Dataset", + "qualifiedName": None, + "description": "Dataset", + }, + "editableProperties": None, + "tags": { + "tags": [ + {"tag": {"urn": "urn:li:tag:Entity", "properties": None}} + ] + }, + "lastIngested": 1709619407814, + "domain": None, + "schemaMetadata": { + "fields": [ + { + "fieldPath": "urn", + "label": None, + "nullable": False, + "description": "The primary identifier for the dataset entity.", + "type": "STRING", + "nativeDataType": "string", + }, + { + "fieldPath": "upstreamLineage", + "label": None, + "nullable": False, + "description": "Upstream lineage of a dataset", + "type": "STRUCT", + "nativeDataType": "upstreamLineage", + }, + ], + "primaryKeys": ["urn"], + "foreignKeys": [ + { + "name": "DownstreamOf", + "foreignFields": [{"fieldPath": "urn"}], + "foreignDataset": { + "urn": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + "properties": { + "name": "Dataset", + "qualifiedName": None, + }, + }, + "sourceFields": [{"fieldPath": "upstreamLineage"}], + }, + ], + }, + } + } + } + base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + dataset = datahub_client.get_table_details(urn) + + assert dataset == TableMetadata( + name="Dataset", + description="Dataset", + column_details=[ + { + "name": "urn", + "type": "STRING", + "description": "The primary identifier for the dataset entity.", + "isPrimaryKey": True, + "foreignKeys": [], + "nullable": False, + }, + { + "name": "upstreamLineage", + "type": "STRUCT", + "description": "Upstream lineage of a dataset", + "foreignKeys": [ + { + "tableId": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + "fieldName": "urn", + "tableName": "Dataset", + } + ], + "isPrimaryKey": False, + "nullable": False, + }, + ], + retention_period_in_days=None, + source_dataset_name="", + where_to_access_dataset="", + data_sensitivity_level=SecurityClassification.OFFICIAL, + tags=[], + major_version=1, + ) diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py new file mode 100644 index 00000000..8e27d397 --- /dev/null +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -0,0 +1,120 @@ +from data_platform_catalogue.client.datahub.graphql_helpers import parse_columns + + +def test_parse_columns_with_primary_key_and_foreign_key(): + entity = { + "schemaMetadata": { + "fields": [ + { + "fieldPath": "urn", + "label": None, + "nullable": False, + "description": "The primary identifier for the dataset entity.", + "type": "STRING", + "nativeDataType": "string", + }, + { + "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "label": None, + "nullable": False, + "description": "Upstream lineage of a dataset", + "type": "STRUCT", + "nativeDataType": "upstreamLineage", + }, + ], + "primaryKeys": ["urn"], + "foreignKeys": [ + { + "name": "DownstreamOf", + "foreignFields": [{"fieldPath": "urn"}], + "foreignDataset": { + "urn": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + "properties": {"name": "Dataset", "qualifiedName": None}, + }, + "sourceFields": [ + { + "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage" + } + ], + } + ], + } + } + + assert parse_columns(entity) == [ + { + "name": "urn", + "type": "STRING", + "isPrimaryKey": True, + "foreignKeys": [], + "nullable": False, + "description": "The primary identifier for the dataset entity.", + }, + { + "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "type": "STRUCT", + "description": "Upstream lineage of a dataset", + "nullable": False, + "isPrimaryKey": False, + "foreignKeys": [ + { + "tableId": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + "fieldName": "urn", + "tableName": "Dataset", + } + ], + }, + ] + + +def test_parse_columns_with_no_keys(): + entity = { + "schemaMetadata": { + "fields": [ + { + "fieldPath": "urn", + "label": None, + "nullable": False, + "description": "The primary identifier for the dataset entity.", + "type": "STRING", + "nativeDataType": "string", + }, + { + "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "label": None, + "nullable": False, + "description": "Upstream lineage of a dataset", + "type": "STRUCT", + "nativeDataType": "upstreamLineage", + }, + ], + "primaryKeys": [], + "foreignKeys": [], + } + } + + assert parse_columns(entity) == [ + { + "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "type": "STRUCT", + "description": "Upstream lineage of a dataset", + "nullable": False, + "isPrimaryKey": False, + "foreignKeys": [], + }, + { + "name": "urn", + "type": "STRING", + "isPrimaryKey": False, + "foreignKeys": [], + "nullable": False, + "description": "The primary identifier for the dataset entity.", + }, + ] + + +def test_parse_columns_with_no_schema(): + entity = {} + + assert parse_columns(entity) == [] + assert parse_columns(entity) == [] diff --git a/lib/datahub-client/tests/test_datahub_search.py b/lib/datahub-client/tests/client/datahub/test_search.py similarity index 100% rename from lib/datahub-client/tests/test_datahub_search.py rename to lib/datahub-client/tests/client/datahub/test_search.py From 07f9d55b8f635c797ed1bc6978bce38fbface36d Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 7 Mar 2024 14:14:46 +0000 Subject: [PATCH 37/64] Fix dodgy getDataset query (#3637) * Fix dodgy getDataset query - lastModified is a long - I'm not sure why I had it as an object - added missing end to end test - handle query returning None for primary and foreign keys * words --- lib/datahub-client/CHANGELOG.md | 6 ++ .../client/datahub/datahub_client.py | 4 +- .../datahub/graphql/getDatasetDetails.graphql | 4 +- .../client/datahub/graphql_helpers.py | 4 +- lib/datahub-client/pyproject.toml | 2 +- .../client/datahub/test_datahub_client.py | 98 +++++++++---------- .../test_integration_with_datahub_server.py | 9 ++ 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 5a400c30..e0ce7a8f 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.19.1] 2024-03-07 + +### Changed + +- Fix error executing getDataset query and added end-to-end test + ## [0.19.0] 2024-03-06 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index c2db12e8..915ae7b2 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -404,8 +404,8 @@ def get_glossary_terms(self, count: int = 1000) -> SearchResponse: def get_table_details(self, urn) -> TableMetadata: try: response = self.graph.execute_graphql(self.dataset_query, {"urn": urn})[ - "data" - ]["dataset"] + "dataset" + ] properties, custom_properties = parse_properties(response) columns = parse_columns(response) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql index 526e4f55..771f62d9 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql @@ -33,9 +33,7 @@ query getDatasetDetails($urn: String!) { value } created - lastModified { - actor - } + lastModified } editableProperties { description diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py index c40f3630..97296463 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py @@ -86,7 +86,7 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: if not schema_metadata: return [] - primary_keys = set(schema_metadata.get("primaryKeys", ())) + primary_keys = set(schema_metadata.get("primaryKeys") or ()) foreign_keys = defaultdict(list) @@ -97,7 +97,7 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: # - Some foreign keys will not match on fieldPath, because fields # may be defined using STRUCT types and foreign keys can reference # subfields within the struct. We will simply ignore these. - for foreign_key in schema_metadata.get("foreignKeys", ()): + for foreign_key in schema_metadata.get("foreignKeys") or (): if not foreign_key["sourceFields"] or not foreign_key["foreignFields"]: continue diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index c2fe6fe1..ef545448 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.19.0" +version = "0.19.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 5ffd507e..8d15c068 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -239,60 +239,56 @@ def test_get_dataset( ): urn = "abc" datahub_response = { - "data": { - "dataset": { - "platform": {"name": "datahub"}, - "ownership": None, + "dataset": { + "platform": {"name": "datahub"}, + "ownership": None, + "name": "Dataset", + "properties": { "name": "Dataset", - "properties": { - "name": "Dataset", - "qualifiedName": None, - "description": "Dataset", - }, - "editableProperties": None, - "tags": { - "tags": [ - {"tag": {"urn": "urn:li:tag:Entity", "properties": None}} - ] - }, - "lastIngested": 1709619407814, - "domain": None, - "schemaMetadata": { - "fields": [ - { - "fieldPath": "urn", - "label": None, - "nullable": False, - "description": "The primary identifier for the dataset entity.", - "type": "STRING", - "nativeDataType": "string", - }, - { - "fieldPath": "upstreamLineage", - "label": None, - "nullable": False, - "description": "Upstream lineage of a dataset", - "type": "STRUCT", - "nativeDataType": "upstreamLineage", - }, - ], - "primaryKeys": ["urn"], - "foreignKeys": [ - { - "name": "DownstreamOf", - "foreignFields": [{"fieldPath": "urn"}], - "foreignDataset": { - "urn": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", - "properties": { - "name": "Dataset", - "qualifiedName": None, - }, + "qualifiedName": None, + "description": "Dataset", + }, + "editableProperties": None, + "tags": { + "tags": [{"tag": {"urn": "urn:li:tag:Entity", "properties": None}}] + }, + "lastIngested": 1709619407814, + "domain": None, + "schemaMetadata": { + "fields": [ + { + "fieldPath": "urn", + "label": None, + "nullable": False, + "description": "The primary identifier for the dataset entity.", + "type": "STRING", + "nativeDataType": "string", + }, + { + "fieldPath": "upstreamLineage", + "label": None, + "nullable": False, + "description": "Upstream lineage of a dataset", + "type": "STRUCT", + "nativeDataType": "upstreamLineage", + }, + ], + "primaryKeys": ["urn"], + "foreignKeys": [ + { + "name": "DownstreamOf", + "foreignFields": [{"fieldPath": "urn"}], + "foreignDataset": { + "urn": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + "properties": { + "name": "Dataset", + "qualifiedName": None, }, - "sourceFields": [{"fieldPath": "upstreamLineage"}], }, - ], - }, - } + "sourceFields": [{"fieldPath": "upstreamLineage"}], + }, + ], + }, } } base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 5e4f1d6d..25f81d1e 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -285,3 +285,12 @@ def test_get_glossary_terms_returns(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) assets = client.get_glossary_terms(count=20) assert assets + + +@runs_on_development_server +def test_get_dataset(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + table = client.get_table_details( + urn="urn:li:dataset:(urn:li:dataPlatform:glue,nomis.agency_release_beds,PROD)" + ) + assert table From e625bc23778d31bcb5dfa93c9639e98c732d88f6 Mon Sep 17 00:00:00 2001 From: Mitch Dawson <86007219+mitchdawson1982@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:59:20 +0000 Subject: [PATCH 38/64] Support handling of customProperties in matched fields (#3641) * Updated changlog with test details * add method to handle returning matched fields * update tests with custom property field * update catalogue version --- lib/datahub-client/CHANGELOG.md | 11 +++++++++++ .../client/datahub/search.py | 16 +++++++++++++--- lib/datahub-client/pyproject.toml | 2 +- .../tests/client/datahub/test_search.py | 5 +++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index e0ce7a8f..e00f4fa1 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.19.2] 2024-03-07 + +### Added + +- `_get_matched_fields` static method to return matched fields including logic + for the values of custom property fields. + +### Changed + +- changed query test matches to include a customProperties field with value + ## [0.19.1] 2024-03-07 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index a4bc97d0..475ebd4c 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -99,9 +99,7 @@ def search( for result in response["searchResults"]: entity = result["entity"] entity_type = entity["type"] - matched_fields = { - i["name"]: i["value"] for i in result.get("matchedFields", []) - } + matched_fields = self._get_matched_fields(result=result) if entity_type == "DATA_PRODUCT": page_results.append(self._parse_data_product(entity, matched_fields)) @@ -114,6 +112,18 @@ def search( total_results=response["total"], page_results=page_results, facets=facets ) + @staticmethod + def _get_matched_fields(result: dict) -> dict: + fields = result.get("matchedFields", []) + matched_fields = {} + for field in fields: + name = field.get("name") + value = field.get("value") + if name == "customProperties" and value != "": + name, value = value.split("=") + matched_fields[name] = value + return matched_fields + def search_facets( self, query: str = "*", diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index ef545448..024a5e1f 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.19.1" +version = "0.19.2" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 6d088f1f..436815bc 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -314,6 +314,10 @@ def test_query_match(mock_graph, searcher): "value": "urn:li:dataset:(urn:li:dataPlatform:looker,long_tail_companions.view.customer_focused,PROD)", # noqa E501 }, {"name": "name", "value": "customer_focused"}, + { + "name": "customProperties", + "value": "sensitivityLevel=OFFICIAL", + }, ], "entity": { "type": "DATASET", @@ -339,6 +343,7 @@ def test_query_match(mock_graph, searcher): matches={ "urn": "urn:li:dataset:(urn:li:dataPlatform:looker,long_tail_companions.view.customer_focused,PROD)", # noqa E501 "name": "customer_focused", + "sensitivityLevel": "OFFICIAL", }, result_type=ResultType.TABLE, name="customers", From a98deda8b5a3bab7b213d60deac7e8d34e1f1a19 Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:51:08 +0000 Subject: [PATCH 39/64] Dc 175 add upsert athena methods (#3767) * add upsert athena methods * and correct metadata obj * entity changes and new `DatabaseStatus` enum * updated and new tests * poetry and changelog * one more test * sort types * lint * more lint * add bullet point * remove security classifications not used from entities --- lib/datahub-client/CHANGELOG.md | 18 ++ .../data_platform_catalogue/__init__.py | 9 +- .../client/datahub/datahub_client.py | 236 ++++++++++++++++++ .../data_platform_catalogue/entities.py | 42 +++- lib/datahub-client/pyproject.toml | 2 +- .../client/datahub/test_datahub_client.py | 140 ++++++++++- .../datahub_create_athena_table.json | 150 +++++++++++ ...hub_create_athena_table_with_metadata.json | 100 ++++++++ .../tests/snapshots/datahub_create_table.json | 2 +- .../datahub_create_table_with_metadata.json | 2 +- ...tahub_create_two_tables_with_metadata.json | 2 +- .../test_integration_with_datahub_server.py | 78 +++++- .../test_resources/golden_database_in.json | 59 +++++ 13 files changed, 817 insertions(+), 23 deletions(-) create mode 100644 lib/datahub-client/tests/snapshots/datahub_create_athena_table.json create mode 100644 lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json create mode 100644 lib/datahub-client/tests/test_resources/golden_database_in.json diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index e00f4fa1..f9611129 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.20.0] 2024-03-15 + +### Added + +- `upsert_athena_database` and `upsert_athena_table` methods to + `DataHubCatalogueClient` - to register/update databases or tables in + datahub. These methods do not create a domain if it does not exist. +- `DatabaseMetadata` class to `entities.py` - for defining metadata for an + athena database +- `DatabaseStatus` enum to `entities.py` +- 2 custom exceptions in `datahub_client.py` for invalid domains and missing metadata. + +### Changed + +- `entities.TableMetadata` to have some optional properties to the class - + `parent_database_name`, `domain`, `row_count` +- `entities.SecurityClassification` to remove classicifation we will not be using. + ## [0.19.2] 2024-03-07 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py index d0a34b65..4155a3e7 100644 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/__init__.py @@ -1,3 +1,8 @@ from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 -from .entities import DataProductMetadata # noqa: F401 -from .entities import CatalogueMetadata, DataLocation, TableMetadata # noqa: F401 +from .entities import ( # noqa: F401 + CatalogueMetadata, + DatabaseMetadata, + DataLocation, + DataProductMetadata, + TableMetadata, +) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 915ae7b2..5099583b 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -7,8 +7,15 @@ from datahub.emitter.mce_builder import make_data_platform_urn from datahub.emitter.mcp import MetadataChangeProposalWrapper from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph +from datahub.ingestion.source.common.subtypes import ( + DatasetContainerSubTypes, + DatasetSubTypes, +) +from datahub.metadata.com.linkedin.pegasus2avro.common import DataPlatformInstance from datahub.metadata.schema_classes import ( ChangeTypeClass, + ContainerClass, + ContainerPropertiesClass, DataProductAssociationClass, DataProductPropertiesClass, DatasetPropertiesClass, @@ -18,10 +25,12 @@ SchemaFieldClass, SchemaFieldDataTypeClass, SchemaMetadataClass, + SubTypesClass, ) from ...entities import ( CatalogueMetadata, + DatabaseMetadata, DataLocation, DataProductMetadata, TableMetadata, @@ -55,6 +64,19 @@ } +class InvalidDomain(Exception): + """ + Exception thrown when a domain does not exist + """ + + +class MissingDatabaseMetadata(Exception): + """ + Exception thrown when a database is attempted to be ingested without + a given metadata specification + """ + + class DataHubCatalogueClient(BaseCatalogueClient): """Client for pushing metadata to the DataHub catalogue. @@ -97,6 +119,14 @@ def __init__(self, jwt_token, api_url: str, graph=None): .read_text() ) + def check_entity_exists_by_urn(self, urn: str | None): + if urn is not None: + exists = self.graph.exists(entity_urn=urn) + else: + exists = False + + return exists + def upsert_database_service(self, platform: str = "glue", *args, **kwargs) -> str: """ Define a DataHub 'Data Platform'. This is a type of connection, e.g. 'hive' or 'glue'. @@ -146,6 +176,212 @@ def create_domain( return domain_urn + def upsert_athena_database(self, metadata: DatabaseMetadata) -> str: + """ + Define a database. Must belong to a domain + """ + metadata_dict = dict(metadata.__dict__) + metadata_dict.pop("version") + metadata_dict.pop("owner") + metadata_dict.pop("tags") + + name = metadata_dict.pop("name") + description = metadata_dict.pop("description") + + database_urn = "urn:li:container:" + "".join(name.split()) + + domain = metadata_dict.pop("domain") + domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) + + # domains are to be controlled and limited so we dont want to create one + # when it doesn't exist + domain_exists = self.check_entity_exists_by_urn(domain_urn) + if not domain_exists: + raise InvalidDomain( + f"{domain} does not exist in datahub - please align data to an existing domain" + ) + + database_domain = DomainsClass(domains=[domain_urn]) + + if domain_urn is not None: + metadata_event = MetadataChangeProposalWrapper( + entityType="container", + changeType=ChangeTypeClass.UPSERT, + entityUrn=database_urn, + aspect=database_domain, + ) + self.graph.emit(metadata_event) + logger.info(f"Database {name} associated with domain {domain}") + + database_properties = ContainerPropertiesClass( + customProperties={key: str(val) for key, val in metadata_dict.items()}, + description=description, + name=name, + ) + metadata_event = MetadataChangeProposalWrapper( + entityType="container", + changeType=ChangeTypeClass.UPSERT, + entityUrn=database_urn, + aspect=database_properties, + ) + self.graph.emit(metadata_event) + logger.info(f"Properties updated for Database {name} ") + + # set platform to athena + metadata_event = MetadataChangeProposalWrapper( + entityUrn=database_urn, + aspect=DataPlatformInstance( + platform="urn:li:dataPlatform:athena", + ), + ) + self.graph.emit(metadata_event) + logger.info(f"Platform updated for Database {name} ") + + # container type update + metadata_event = MetadataChangeProposalWrapper( + entityUrn=database_urn, + aspect=SubTypesClass(typeNames=[DatasetContainerSubTypes.DATABASE]), + ) + self.graph.emit(metadata_event) + logger.info(f"Type updated for Database {name} ") + + return database_urn + + def upsert_athena_table( + self, + metadata: TableMetadata, + database_metadata: DatabaseMetadata | None = None, + ) -> str: + if not metadata.parent_database_name: + raise ValueError("parent_database_name needs to be set in TableMetadata") + + fully_qualified_name = f"{metadata.parent_database_name}.{metadata.name}" + + dataset_urn = mce_builder.make_dataset_urn( + platform="athena", name=fully_qualified_name, env="PROD" + ) + # jscpd:ignore-start + dataset_properties = DatasetPropertiesClass( + name=metadata.name, + qualifiedName=fully_qualified_name, + description=metadata.description, + customProperties={ + "sourceDatasetName": metadata.source_dataset_name, + "whereToAccessDataset": metadata.where_to_access_dataset, + "sensitivityLevel": metadata.data_sensitivity_level.name, + **( + {"rowCount": str(metadata.row_count)} + if metadata.row_count is not None + else {} + ), + }, + ) + + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=dataset_properties, + ) + self.graph.emit(metadata_event) + + dataset_schema_properties = SchemaMetadataClass( + schemaName=metadata.name, + platform="urn:li:dataPlatform:athena", + version=metadata.major_version, + hash="", + platformSchema=OtherSchemaClass(rawSchema=""), + fields=[ + SchemaFieldClass( + fieldPath=f"{column['name']}", + type=SchemaFieldDataTypeClass( + type=DATAHUB_DATA_TYPE_MAPPING[column["type"]] + ), + nativeDataType=column["type"], + description=column["description"], + ) + for column in metadata.column_details + ], + ) + + metadata_event = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=dataset_schema_properties, + ) + self.graph.emit(metadata_event) + + # set dataset type to table + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=SubTypesClass(typeNames=[DatasetSubTypes.TABLE]), + ) + + self.graph.emit(metadata_event) + + if metadata.tags: + tags_to_add = mce_builder.make_global_tag_aspect_with_tag_list( + tags=metadata.tags + ) + event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=tags_to_add, + ) + self.graph.emit(event) + # jscpd:ignore-end + + database_urn = "urn:li:container:" + "".join( + metadata.parent_database_name.split() + ) + + database_exists = self.check_entity_exists_by_urn(urn=database_urn) + + if not database_exists and database_metadata: + database_urn = self.upsert_athena_database(metadata=database_metadata) + domain_urn = self.graph.get_domain_urn_by_name( + domain_name=database_metadata.domain + ) + elif not database_exists and not database_metadata: + raise MissingDatabaseMetadata( + "DatabaseMetadata object needs to be passed in to create a database" + ) + + # add dataset to database + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=ContainerClass(container=database_urn), + ) + + self.graph.emit(metadata_event) + + # add domain to table - tables inherit the domain of parent database if not given + if metadata.domain: + domain_urn = self.graph.get_domain_urn_by_name(domain_name=metadata.domain) + else: + domain_urn = self.graph.get_aspect( + entity_urn=database_urn, aspect_type=DomainsClass + ).domains[0] + + domain_exists = self.check_entity_exists_by_urn(domain_urn) + if not domain_exists: + raise InvalidDomain( + f"{metadata.domain} does not exist in datahub - please align data to an existing domain" + ) + + if domain_urn is not None: + table_domain = DomainsClass(domains=[domain_urn]) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=table_domain, + ) + + self.graph.emit(metadata_event) + + return dataset_urn + def upsert_data_product(self, metadata: DataProductMetadata): """ Define a data product. Must belong to a domain diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index 0d28f3cd..895f46c5 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -32,6 +32,12 @@ class DataProductStatus(Enum): RETIRED = auto() +class DatabaseStatus(Enum): + PROD = auto() + PREPROD = auto() + DEV = auto() + + @dataclass class DataProductMetadata: name: str @@ -92,8 +98,6 @@ def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): class SecurityClassification(Enum): OFFICIAL = auto() - SECRET = auto() - TOP_SECRET = auto() @dataclass @@ -102,11 +106,14 @@ class TableMetadata: description: str column_details: list retention_period_in_days: int | None + domain: str | None = None + parent_database_name: str | None = None source_dataset_name: str = "" where_to_access_dataset: str = "" data_sensitivity_level: SecurityClassification = SecurityClassification.OFFICIAL tags: list[str] = field(default_factory=list) major_version: int = 1 + row_count: int | None = None @staticmethod def from_data_product_schema_dict( @@ -136,3 +143,34 @@ def from_data_product_schema_dict( ) return new_metadata + + +# jscpd:ignore-start +@dataclass +class DatabaseMetadata: + """ + For source system databases - currently matches DataProductMetadata + but will need to be revisted and refined potentially + """ + + name: str + description: str + version: str + owner: str + owner_display_name: str + maintainer: str | None + maintainer_display_name: str | None + email: str + retention_period_in_days: int + domain: str + subdomain: str | None + dpia_required: bool + dpia_location: str | None + last_updated: datetime + creation_date: datetime + s3_location: str | None + status: DataProductStatus = DataProductStatus.DRAFT + tags: list[str] = field(default_factory=list) + + +# jscpd:ignore-end diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 024a5e1f..891e23d5 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.19.2" +version = "0.20.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 8d15c068..2c5aee16 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -1,11 +1,17 @@ from datetime import datetime from pathlib import Path -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest -from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient +from data_platform_catalogue.client.datahub.datahub_client import ( + DataHubCatalogueClient, + InvalidDomain, + MissingDatabaseMetadata, +) from data_platform_catalogue.entities import ( CatalogueMetadata, + DatabaseMetadata, + DatabaseStatus, DataLocation, DataProductMetadata, DataProductStatus, @@ -53,6 +59,29 @@ def data_product(self): tags=["test"], ) + @pytest.fixture + def database(self): + return DatabaseMetadata( + name="my_database", + description="little test db", + version="v1.0.0", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", + email="justice@justice.gov.uk", + status=DatabaseStatus.PROD.name, + retention_period_in_days=365, + domain="LAA", + subdomain="Legal Aid", + dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], + ) + @pytest.fixture def table(self): return TableMetadata( @@ -65,7 +94,9 @@ def table(self): retention_period_in_days=365, source_dataset_name="my_source_table", where_to_access_dataset="s3://databucket/table1", - data_sensitivity_level=SecurityClassification.TOP_SECRET, + data_sensitivity_level=SecurityClassification.OFFICIAL, + parent_database_name="my_database", + domain="LAA", ) @pytest.fixture @@ -90,11 +121,17 @@ def datahub_client(self, base_mock_graph) -> DataHubCatalogueClient: ) @pytest.fixture - def golden_file_in(self): + def golden_file_in_dp(self): return Path( Path(__file__).parent / "../../test_resources/golden_data_product_in.json" ) + @pytest.fixture + def golden_file_in_db(self): + return Path( + Path(__file__).parent / "../../test_resources/golden_database_in.json" + ) + def test_create_table_datahub( self, datahub_client, @@ -102,13 +139,13 @@ def test_create_table_datahub( table, tmp_path, check_snapshot, - golden_file_in, + golden_file_in_dp, ): """ Case where we just create a dataset (no data product) """ mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in) + mock_graph.import_file(golden_file_in_dp) fqn = datahub_client.upsert_table( metadata=table, @@ -130,13 +167,13 @@ def test_create_table_with_metadata_datahub( base_mock_graph, tmp_path, check_snapshot, - golden_file_in, + golden_file_in_dp, ): """ Case where we create a dataset, data product and domain """ mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in) + mock_graph.import_file(golden_file_in_dp) fqn = datahub_client.upsert_table( metadata=table, @@ -166,14 +203,14 @@ def test_create_two_tables_with_metadata( base_mock_graph, tmp_path, check_snapshot, - golden_file_in, + golden_file_in_dp, ): """ Case where we create a dataset, data product and domain """ mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in) + mock_graph.import_file(golden_file_in_dp) fqn = datahub_client.upsert_table( metadata=table, @@ -205,13 +242,13 @@ def test_create_table_and_metadata_idempotent_datahub( base_mock_graph, tmp_path, check_snapshot, - golden_file_in, + golden_file_in_dp, ): """ `create_table` should work even if the entities already exist in the metadata graph. """ mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in) + mock_graph.import_file(golden_file_in_dp) datahub_client.upsert_table( metadata=table, @@ -329,3 +366,82 @@ def test_get_dataset( tags=[], major_version=1, ) + + def test_create_athena_database_and_table( + self, + datahub_client, + base_mock_graph, + database, + table, + tmp_path, + check_snapshot, + golden_file_in_db, + ): + """ + Case where we create separate database and table + """ + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in_db) + with patch( + "data_platform_catalogue.client.datahub.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + ) as mock_exists: + mock_exists.return_value = True + fqn_db = datahub_client.upsert_athena_database(metadata=database) + fqn_t = datahub_client.upsert_athena_table(metadata=table) + + fqn_db_out = "urn:li:container:my_database" + assert fqn_db == fqn_db_out + + fqn_t_out = ( + "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)" + ) + assert fqn_t == fqn_t_out + + output_file = Path(tmp_path / "datahub_create_athena_table.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_athena_table.json", output_file) + + def test_create_athena_table_with_metadata( + self, + datahub_client, + table, + database, + base_mock_graph, + tmp_path, + check_snapshot, + golden_file_in_db, + ): + """ + Case where we create a dataset (athena table) and container (athena database) + via upsert_athena_table method + """ + mock_graph = base_mock_graph + mock_graph.import_file(golden_file_in_db) + + with patch( + "data_platform_catalogue.client.datahub.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + ) as mock_exists: + mock_exists.return_value = True + fqn = datahub_client.upsert_athena_table( + metadata=table, + database_metadata=database, + ) + fqn_out = ( + "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)" + ) + + assert fqn == fqn_out + + output_file = Path(tmp_path / "datahub_create_athena_table_with_metadata.json") + base_mock_graph.sink_to_file(output_file) + check_snapshot("datahub_create_athena_table_with_metadata.json", output_file) + + def test_domain_does_not_exist_error(self, datahub_client, database): + with pytest.raises(InvalidDomain): + datahub_client.upsert_athena_database(metadata=database) + + def test_database_not_exist_with_no_metadata_given_error( + self, datahub_client, table + ): + with pytest.raises(MissingDatabaseMetadata): + datahub_client.upsert_athena_table(metadata=table) diff --git a/lib/datahub-client/tests/snapshots/datahub_create_athena_table.json b/lib/datahub-client/tests/snapshots/datahub_create_athena_table.json new file mode 100644 index 00000000..906198e6 --- /dev/null +++ b/lib/datahub-client/tests/snapshots/datahub_create_athena_table.json @@ -0,0 +1,150 @@ +[ +{ + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "containerProperties", + "aspect": { + "json": { + "customProperties": { + "owner_display_name": "April Gonzalez", + "maintainer": "j.shelvey@digital.justice.gov.uk", + "maintainer_display_name": "Jonjo Shelvey", + "email": "justice@justice.gov.uk", + "retention_period_in_days": "365", + "subdomain": "Legal Aid", + "dpia_required": "False", + "dpia_location": "None", + "last_updated": "2020-05-17 00:00:00", + "creation_date": "2020-05-17 00:00:00", + "s3_location": "s3://databucket/", + "status": "PROD" + }, + "name": "my_database", + "description": "little test db" + } + } +}, +{ + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "subTypes", + "aspect": { + "json": { + "typeNames": [ + "Database" + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": { + "sourceDatasetName": "my_source_table", + "whereToAccessDataset": "s3://databucket/table1", + "sensitivityLevel": "OFFICIAL" + }, + "name": "my_table", + "qualifiedName": "my_database.my_table", + "description": "bla bla", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table", + "platform": "urn:li:dataPlatform:athena", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "foo", + "nullable": false, + "description": "a", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "bar", + "nullable": false, + "description": "b", + "type": { + "type": { + "com.linkedin.schema.NumberType": {} + } + }, + "nativeDataType": "int", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "subTypes", + "aspect": { + "json": { + "typeNames": [ + "Table" + ] + } + } +}, +{ + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "dataPlatformInstance", + "aspect": { + "json": { + "platform": "urn:li:dataPlatform:athena" + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "container", + "aspect": { + "json": { + "container": "urn:li:container:my_database" + } + } +} +] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json new file mode 100644 index 00000000..356be2f7 --- /dev/null +++ b/lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json @@ -0,0 +1,100 @@ +[ +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "datasetProperties", + "aspect": { + "json": { + "customProperties": { + "sourceDatasetName": "my_source_table", + "whereToAccessDataset": "s3://databucket/table1", + "sensitivityLevel": "OFFICIAL" + }, + "name": "my_table", + "qualifiedName": "my_database.my_table", + "description": "bla bla", + "tags": [] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "schemaMetadata", + "aspect": { + "json": { + "schemaName": "my_table", + "platform": "urn:li:dataPlatform:athena", + "version": 1, + "created": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "lastModified": { + "time": 0, + "actor": "urn:li:corpuser:unknown" + }, + "hash": "", + "platformSchema": { + "com.linkedin.schema.OtherSchema": { + "rawSchema": "" + } + }, + "fields": [ + { + "fieldPath": "foo", + "nullable": false, + "description": "a", + "type": { + "type": { + "com.linkedin.schema.StringType": {} + } + }, + "nativeDataType": "string", + "recursive": false, + "isPartOfKey": false + }, + { + "fieldPath": "bar", + "nullable": false, + "description": "b", + "type": { + "type": { + "com.linkedin.schema.NumberType": {} + } + }, + "nativeDataType": "int", + "recursive": false, + "isPartOfKey": false + } + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "subTypes", + "aspect": { + "json": { + "typeNames": [ + "Table" + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "changeType": "UPSERT", + "aspectName": "container", + "aspect": { + "json": { + "container": "urn:li:container:my_database" + } + } +} +] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json index 089f8c98..75a4dd6b 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table.json @@ -9,7 +9,7 @@ "customProperties": { "sourceDatasetName": "my_source_table", "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "TOP_SECRET", + "sensitivityLevel": "OFFICIAL", "rowCount": "1177" }, "name": "my_table", diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json index fbbcbcf8..833a67bb 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json @@ -9,7 +9,7 @@ "customProperties": { "sourceDatasetName": "my_source_table", "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "TOP_SECRET", + "sensitivityLevel": "OFFICIAL", "rowCount": "1177" }, "name": "my_table", diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json index d810525e..d9fe0634 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json @@ -9,7 +9,7 @@ "customProperties": { "sourceDatasetName": "my_source_table", "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "TOP_SECRET", + "sensitivityLevel": "OFFICIAL", "rowCount": "1177" }, "name": "my_table", diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 25f81d1e..18e0b9b5 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -12,9 +12,13 @@ from datetime import datetime import pytest -from data_platform_catalogue import DataProductMetadata, TableMetadata +from data_platform_catalogue import DatabaseMetadata, DataProductMetadata, TableMetadata from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient -from data_platform_catalogue.entities import DataLocation, DataProductStatus +from data_platform_catalogue.entities import ( + DatabaseStatus, + DataLocation, + DataProductStatus, +) from data_platform_catalogue.search_types import MultiSelectFilter, ResultType from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass @@ -24,7 +28,7 @@ @runs_on_development_server -def test_upsert_test_hierarchy(): +def test_data_product_upsert_test_hierarchy(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) data_product = DataProductMetadata( @@ -294,3 +298,71 @@ def test_get_dataset(): urn="urn:li:dataset:(urn:li:dataPlatform:glue,nomis.agency_release_beds,PROD)" ) assert table + + +@runs_on_development_server +def test_athena_upsert_test_hierarchy(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + + database = DatabaseMetadata( + name="my_database", + description="testing", + version="v1.0.0", + owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + owner_display_name="April Gonzalez", + maintainer="j.shelvey@digital.justice.gov.uk", + maintainer_display_name="Jonjo Shelvey", + email="justice@justice.gov.uk", + status=DatabaseStatus.PROD.name, + retention_period_in_days=365, + domain="prison", + subdomain=None, + dpia_required=False, + dpia_location=None, + last_updated=datetime(2020, 5, 17), + creation_date=datetime(2020, 5, 17), + s3_location="s3://databucket/", + tags=["test"], + ) + + table = TableMetadata( + name="test_table", + parent_database_name="my_database", + description="bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days=365, + where_to_access_dataset="analytical_platform", + tags=["test"], + ) + + table_fqn = client.upsert_athena_table( + metadata=table, + database_metadata=database, + ) + assert ( + table_fqn + == "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.test_table,PROD)" + ) + + # Ensure data went through + assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) + assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) + + dataset_properties = client.graph.get_aspect( + table_fqn, aspect_type=DatasetPropertiesClass + ) + # check properties been loaded to datahub dataset + assert dataset_properties.description == table.description + assert dataset_properties.qualifiedName == f"{database.name}.{table.name}" + assert dataset_properties.name == table.name + assert ( + dataset_properties.customProperties["sourceDatasetName"] + == table.source_dataset_name + ) + assert ( + dataset_properties.customProperties["whereToAccessDataset"] + == table.where_to_access_dataset + ) diff --git a/lib/datahub-client/tests/test_resources/golden_database_in.json b/lib/datahub-client/tests/test_resources/golden_database_in.json new file mode 100644 index 00000000..64d93b96 --- /dev/null +++ b/lib/datahub-client/tests/test_resources/golden_database_in.json @@ -0,0 +1,59 @@ +[ + { + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "containerProperties", + "aspect": { + "json": { + "customProperties": { + "version": "2.0", + "dpia": "false" + }, + "externalUrl": "https://github.com/datahub-project/datahub", + "name": "my_database", + "description": "little test db", + "assets": [] + } + } + }, + { + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:LAA" + ] + } + } + }, + { + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "globalTags", + "aspect": { + "json": { + "tags": [ + { + "tag": "urn:li:tag:awesome" + } + ] + } + } + }, + { + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "status", + "aspect": { + "json": { + "removed": false + } + } + } +] From 7faebfc353aac1ab7d8e62e141178fca36713936 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:14:51 +0000 Subject: [PATCH 40/64] Fmd-149-charts (#3772) * Added charts to graphQL queries * Added ChartMetadata class * Added chart ResultType * Added chart parsing to search * Added get_chart_details method to DataHub client * Applied linting corrections * Removed stray print statements --- .../data_platform_catalogue/client/base.py | 8 +++ .../client/datahub/datahub_client.py | 23 +++++++ .../datahub/graphql/getChartDetails.graphql | 41 +++++++++++++ .../client/datahub/graphql/search.graphql | 36 +++++++++++ .../client/datahub/search.py | 21 +++++++ .../data_platform_catalogue/entities.py | 8 +++ .../data_platform_catalogue/search_types.py | 1 + .../client/datahub/test_datahub_client.py | 29 +++++++++ .../tests/client/datahub/test_search.py | 60 +++++++++++++++++++ .../test_integration_with_datahub_server.py | 7 +++ 10 files changed, 234 insertions(+) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index 3c6cd4eb..d5e8c10c 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -4,6 +4,7 @@ from ..entities import ( CatalogueMetadata, + ChartMetadata, DataLocation, DataProductMetadata, TableMetadata, @@ -116,3 +117,10 @@ def get_table_details(self, urn) -> TableMetadata: returns the schema and detailed information about a table """ pass + + @abstractmethod + def get_chart_details(self, urn) -> ChartMetadata: + """ + returns detailed information about a chart + """ + pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 5099583b..ceb72720 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -30,6 +30,7 @@ from ...entities import ( CatalogueMetadata, + ChartMetadata, DatabaseMetadata, DataLocation, DataProductMetadata, @@ -118,6 +119,11 @@ def __init__(self, jwt_token, api_url: str, graph=None): .joinpath("getDatasetDetails.graphql") .read_text() ) + self.chart_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("getChartDetails.graphql") + .read_text() + ) def check_entity_exists_by_urn(self, urn: str | None): if urn is not None: @@ -593,6 +599,7 @@ def search( result_types: Sequence[ResultType] = ( ResultType.DATA_PRODUCT, ResultType.TABLE, + ResultType.CHART, ), filters: Sequence[MultiSelectFilter] = (), sort: SortOption | None = None, @@ -615,6 +622,7 @@ def search_facets( result_types: Sequence[ResultType] = ( ResultType.DATA_PRODUCT, ResultType.TABLE, + ResultType.CHART, ), filters: Sequence[MultiSelectFilter] = (), ) -> SearchFacets: @@ -653,3 +661,18 @@ def get_table_details(self, urn) -> TableMetadata: ) except GraphError as e: raise Exception("Unable to execute getDataset query") from e + + def get_chart_details(self, urn) -> ChartMetadata: + try: + response = self.graph.execute_graphql(self.chart_query, {"urn": urn})[ + "chart" + ] + properties, custom_properties = parse_properties(response) + + return ChartMetadata( + name=properties["name"], + description=properties.get("description", ""), + external_url=properties["externalUrl"], + ) + except GraphError as e: + raise Exception("Unable to execute getDataset query") from e diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql new file mode 100644 index 00000000..65874bfa --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql @@ -0,0 +1,41 @@ +query getChartDetails($urn: String!) { + chart(urn: $urn) { + urn + type + platform { + name + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + properties { + name + description + externalUrl + customProperties { + key + value + } + lastModified { + time + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index 737d6695..a6495efa 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -54,6 +54,42 @@ query Search( } entity { type + ... on Chart { + urn + type + platform { + name + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + properties { + name + description + externalUrl + customProperties { + key + value + } + } + } ... on Dataset { urn type diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 475ebd4c..65311cdb 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -59,6 +59,7 @@ def search( result_types: Sequence[ResultType] = ( ResultType.DATA_PRODUCT, ResultType.TABLE, + ResultType.CHART, ), filters: Sequence[MultiSelectFilter] = (), sort: SortOption | None = None, @@ -105,6 +106,8 @@ def search( page_results.append(self._parse_data_product(entity, matched_fields)) elif entity_type == "DATASET": page_results.append(self._parse_dataset(entity, matched_fields)) + elif entity_type == "CHART": + page_results.append(self._parse_chart(entity, matched_fields)) else: raise ValueError(f"Unexpected entity type: {entity_type}") @@ -198,6 +201,9 @@ def _map_result_types(self, result_types: Sequence[ResultType]): types.append("DATASET") if ResultType.GLOSSARY_TERM in result_types: types.append("GLOSSARY_TERM") + if ResultType.CHART in result_types: + types.append("CHART") + return types def _map_filters(self, filters: Sequence[MultiSelectFilter]): @@ -286,6 +292,21 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: last_updated=last_updated, ) + def _parse_chart(self, entity: dict[str, Any], matches) -> SearchResult: + properties, custom_properties = parse_properties(entity) + last_updated = parse_last_updated(entity) + + return SearchResult( + id=entity["urn"], + result_type=ResultType.CHART, + matches=matches, + name=properties["name"], + fully_qualified_name=properties["name"], + description=properties.get("description", ""), + metadata=custom_properties, + last_updated=last_updated, + ) + def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: """ Parse the facets and aggregate information from the query results diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index 895f46c5..af9d1fee 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -174,3 +174,11 @@ class DatabaseMetadata: # jscpd:ignore-end + + +@dataclass +class ChartMetadata: + name: str + description: str + external_url: str + tags: list[str] = field(default_factory=list) diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 89b88b9f..e3b6b2b0 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -8,6 +8,7 @@ class ResultType(Enum): DATA_PRODUCT = auto() TABLE = auto() GLOSSARY_TERM = auto() + CHART = auto() @dataclass diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 2c5aee16..e0c82533 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -10,6 +10,7 @@ ) from data_platform_catalogue.entities import ( CatalogueMetadata, + ChartMetadata, DatabaseMetadata, DatabaseStatus, DataLocation, @@ -367,6 +368,34 @@ def test_get_dataset( major_version=1, ) + def test_get_chart_details(self, datahub_client, base_mock_graph): + urn = "urn:li:chart:(justice-data,absconds)" + datahub_response = { + "chart": { + "urn": "urn:li:chart:(justice-data,absconds)", + "type": "CHART", + "platform": {"name": "justice-data"}, + "relationships": {"total": 0, "relationships": []}, + "ownership": None, + "properties": { + "name": "Absconds", + "externalUrl": "https://data.justice.gov.uk/prisons/public-protection/absconds", + "description": "a test description", + "customProperties": [], + "lastModified": {"time": 0}, + }, + }, + "extensions": {}, + } + base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + chart = datahub_client.get_chart_details(urn) + assert chart == ChartMetadata( + name="Absconds", + description="a test description", + external_url="https://data.justice.gov.uk/prisons/public-protection/absconds", + ) + def test_create_athena_database_and_table( self, datahub_client, diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 436815bc..9b328289 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -828,3 +828,63 @@ def test_get_glossary_terms(mock_graph, searcher): ), ], ) + + +def test_search_for_charts(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "insights": [], + "matchedFields": [ + { + "name": "urn", + "value": "urn:li:chart:(justice-data,absconds)", + }, + { + "name": "description", + "value": "test", + }, + {"name": "title", "value": "absconds"}, + {"name": "title", "value": "Absconds"}, + ], + "entity": { + "type": "CHART", + "urn": "urn:li:chart:(justice-data,absconds)", + "platform": {"name": "justice-data"}, + "ownership": None, + "properties": { + "name": "Absconds", + "description": "test", + "externalUrl": "https://data.justice.gov.uk/prisons/public-protection/absconds", + "customProperties": [], + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:chart:(justice-data,absconds)", + name="Absconds", + fully_qualified_name="Absconds", + description="test", + matches={ + "urn": "urn:li:chart:(justice-data,absconds)", + "description": "test", + "title": "Absconds", + }, + result_type=ResultType.CHART, + ) + ], + ) diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 18e0b9b5..3612b0d3 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -291,6 +291,13 @@ def test_get_glossary_terms_returns(): assert assets +@runs_on_development_server +def test_get_chart(): + client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + table = client.get_chart_details(urn="urn:li:chart:(justice-data,absconds)") + assert table + + @runs_on_development_server def test_get_dataset(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) From a312363e1b591ae61465f39c0437019ff4d5a022 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:09:05 +0000 Subject: [PATCH 41/64] v0.21.0 (#3781) * v0.21.0 * Applied suggestions from code review --- lib/datahub-client/CHANGELOG.md | 14 ++++++++++++++ lib/datahub-client/pyproject.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index f9611129..19aaf5c0 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.21.0] 2023-03-19 + +### Added + +- A ChartMetadata class with very limited attributes +- ResultType.CHART +- A GraphQL query for chart details +- SearchClient._parse_chart, incorporated chart parsing into the normal search method +- DataHubCatalogueClient.get_chart_details for the chart display page + +### Changed + +- Altered GraphQL search query to include chart parsing + ## [0.20.0] 2024-03-15 ### Added diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 891e23d5..ccaeb2ab 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.20.0" +version = "0.21.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From 5f655f27a53755afb6d9448e2600ee6a4f9e403b Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:19:15 +0000 Subject: [PATCH 42/64] Dc 156 add container entities (#3790) * graphql query to list container entities * add ResultType.DATABASE enum * add container to search graphql query and entity sub type * add containers to search and new list_database_tables method to client * update test_search * handle when entity subtype returned is null * add more useful properties to getDatasetDetails.graphql * add `parse_relations()` to helpers * expand TableMetadata class to hold more useful properties * make `get_table_details` return more metadata properties for a tbale * `_parse_dataset()` made more generic to not be data product specific * update tests for search and client * some lint * changelog and poetry update * language * make the relationships TableMetadata property clearer * lint types * for suggestion re. relationships * make _parse_types_and_subtypes function re. suggestion * lint * reduce fqn duplication with function * whitespace suggestion * chaneglog update --- lib/datahub-client/CHANGELOG.md | 23 ++ .../data_platform_catalogue/client/base.py | 9 + .../client/datahub/datahub_client.py | 44 +++- .../datahub/graphql/getDatasetDetails.graphql | 51 ++++ .../graphql/listContainerEntities.graphql | 36 +++ .../client/datahub/graphql/search.graphql | 59 +++++ .../client/datahub/graphql_helpers.py | 21 ++ .../client/datahub/search.py | 136 ++++++++-- .../data_platform_catalogue/entities.py | 16 +- .../data_platform_catalogue/search_types.py | 1 + lib/datahub-client/pyproject.toml | 2 +- .../client/datahub/test_datahub_client.py | 23 +- .../client/datahub/test_graphql_helpers.py | 34 ++- .../tests/client/datahub/test_search.py | 233 ++++++++++++++++-- 14 files changed, 637 insertions(+), 51 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 19aaf5c0..2e440a89 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.22.0] 2023-03-19 + +### Added + +- New GraphQL query, `listContainerEntities.graphql` that lists all datasets in a + given container +- `ResultType.DATABASE` enum to entities +- `RelationshipType` enum and `RelatedEntity` dataclass to entities +- The datahub container entity to the existing search GraphQL query +- new method to search.py, `SearchClient.list_database_tables` which uses + `listContainerEntities.graphql` +- `_parse_container` method to `search.SearchClient` +- `_parse_relations` function to `graphql_helpers` +- `_parse_types_and_sub_types` method to `search.SearchClient` +- `_get_fully_qualified_name` method to `search.SearchClient` +- `relationships`, `last_updated`, `owner`, `owner_email` to `entities.TableMetadata` + +### Changed + +- `parent_database_name` to `parent_entity_name` within `entities.TableMetadata` +- `search.graphql` query to return containers. +- `SearchClient.search` to return containers and subTypes within results + ## [0.21.0] 2023-03-19 ### Added diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py index d5e8c10c..6afc6c1a 100644 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ b/lib/datahub-client/data_platform_catalogue/client/base.py @@ -71,6 +71,8 @@ def search( result_types: Sequence[ResultType] = ( ResultType.DATA_PRODUCT, ResultType.TABLE, + ResultType.CHART, + ResultType.DATABASE, ), filters: Sequence[MultiSelectFilter] = (), sort: SortOption | None = None, @@ -124,3 +126,10 @@ def get_chart_details(self, urn) -> ChartMetadata: returns detailed information about a chart """ pass + + @abstractmethod + def list_database_tables(self, urn: str, count: int) -> SearchResponse: + """ + returns a list of entities within a datahub container + """ + pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index ceb72720..2de534ab 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -34,6 +34,7 @@ DatabaseMetadata, DataLocation, DataProductMetadata, + RelationshipType, TableMetadata, ) from ...search_types import ( @@ -44,7 +45,14 @@ SortOption, ) from ..base import BaseCatalogueClient, CatalogueError, logger -from .graphql_helpers import parse_columns, parse_properties +from .graphql_helpers import ( + parse_columns, + parse_domain, + parse_owner, + parse_properties, + parse_relations, + parse_tags, +) from .search import SearchClient DATAHUB_DATA_TYPE_MAPPING = { @@ -258,10 +266,10 @@ def upsert_athena_table( metadata: TableMetadata, database_metadata: DatabaseMetadata | None = None, ) -> str: - if not metadata.parent_database_name: - raise ValueError("parent_database_name needs to be set in TableMetadata") + if not metadata.parent_entity_name: + raise ValueError("parent_entity_name needs to be set in TableMetadata") - fully_qualified_name = f"{metadata.parent_database_name}.{metadata.name}" + fully_qualified_name = f"{metadata.parent_entity_name}.{metadata.name}" dataset_urn = mce_builder.make_dataset_urn( platform="athena", name=fully_qualified_name, env="PROD" @@ -338,7 +346,7 @@ def upsert_athena_table( # jscpd:ignore-end database_urn = "urn:li:container:" + "".join( - metadata.parent_database_name.split() + metadata.parent_entity_name.split() ) database_exists = self.check_entity_exists_by_urn(urn=database_urn) @@ -600,6 +608,7 @@ def search( ResultType.DATA_PRODUCT, ResultType.TABLE, ResultType.CHART, + ResultType.DATABASE, ), filters: Sequence[MultiSelectFilter] = (), sort: SortOption | None = None, @@ -652,12 +661,31 @@ def get_table_details(self, urn) -> TableMetadata: ] properties, custom_properties = parse_properties(response) columns = parse_columns(response) - + domain = parse_domain(response) + owner, owner_email = parse_owner(response) + tags = parse_tags(response) + + # A dataset can't have both a container and data product parent, but if we did + # start to use in that we'd need to change this + if response["container_relations"]["total"] > 0: + relations = parse_relations( + RelationshipType.PARENT, response["container_relations"] + ) + elif response["data_product_relations"]["total"] > 0: + relations = parse_relations( + RelationshipType.PARENT, response["data_product_relations"] + ) return TableMetadata( name=properties["name"], description=properties.get("description", ""), column_details=columns, retention_period_in_days=custom_properties.get("retentionPeriodInDays"), + relationships=relations, + domain=domain["domain_name"], + tags=tags, + last_updated=None, # we are not populating this and i think wouldn't refer to lastIngested + owner=owner, + owner_email=owner_email, ) except GraphError as e: raise Exception("Unable to execute getDataset query") from e @@ -676,3 +704,7 @@ def get_chart_details(self, urn) -> ChartMetadata: ) except GraphError as e: raise Exception("Unable to execute getDataset query") from e + + def list_database_tables(self, urn: str, count: int) -> SearchResponse: + """Wraps the client's listDatabaseEntities query""" + return self.search_client.list_database_tables(urn=urn, count=count) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql index 771f62d9..3c26e3df 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql @@ -3,6 +3,57 @@ query getDatasetDetails($urn: String!) { platform { name } + domain { + domain { + urn + id + properties { + name + description + } + } + } + subTypes { + typeNames + } + container_relations:relationships( + input: { + types: ["IsPartOf"] + direction: OUTGOING + count: 10 + } + ) { + total + relationships { + entity { + urn + ... on Container { + properties { + name + } + } + } + } + } + data_product_relations:relationships( + input: { + types: ["DataProductContains"] + direction: INCOMING + count: 10 + } + ) { + total + relationships { + entity { + urn + ... on DataProduct { + properties { + name + } + } + } + } + } ownership { owners { owner { diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql new file mode 100644 index 00000000..74447e99 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql @@ -0,0 +1,36 @@ +query getContainerEntities( + $urn: String! + $start: Int! + $count: Int! +) { + container(urn: $urn) { + entities(input:{start:$start, count:$count}){ + total + searchResults { + entity { + type + ... on Dataset { + urn + type + subTypes { + typeNames + } + platform { + name + } + name + properties { + name + qualifiedName + description + customProperties { + key + value + } + } + } + } + } + } + } +} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index a6495efa..05b887d9 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -96,6 +96,9 @@ query Search( platform { name } + subTypes { + typeNames + } relationships( input: { types: ["DataProductContains"] @@ -227,6 +230,62 @@ query Search( } } } + ... on Container { + urn + type + subTypes { + typeNames + } + ownership { + owners { + owner { + ... on CorpUser { + urn + properties { + fullName + email + } + } + ... on CorpGroup { + urn + properties { + displayName + email + } + } + } + } + } + properties { + name + description + customProperties { + key + value + } + } + domain { + domain { + urn + id + properties { + name + description + } + } + } + tags { + tags { + tag { + urn + properties { + name + description + } + } + } + } + } } } } diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py index 97296463..cc65c69d 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py @@ -2,6 +2,8 @@ from datetime import datetime from typing import Any, Tuple +from data_platform_catalogue.entities import RelatedEntity, RelationshipType + def parse_owner(entity: dict[str, Any]): """ @@ -134,3 +136,22 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: # Sort primary keys first, then sort alphabetically return sorted(result, key=lambda c: (0 if c["isPrimaryKey"] else 1, c["name"])) + + +def parse_relations( + relationship_type: RelationshipType, relations_dict: dict +) -> dict[RelationshipType, list[RelatedEntity]]: + """ + parse the relationships results returned from a graphql querys + """ + # # we may want to do soemthing with total realtion if we are returning child relations + # # and need to paginate through relations - 10 relations returned as is + # total_relations = relations_dict.get("total", 0) + parent_entities = relations_dict.get("relationships", []) + related_entities = [ + RelatedEntity(id=i["entity"]["urn"], name=i["entity"]["properties"]["name"]) + for i in parent_entities + ] + + relations_return = {relationship_type: related_entities} + return relations_return diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index 65311cdb..e0ec2ff4 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -3,6 +3,7 @@ from importlib.resources import files from typing import Any, Sequence +from data_platform_catalogue.entities import RelationshipType from datahub.configuration.common import GraphError from datahub.ingestion.graph.client import DataHubGraph @@ -20,6 +21,7 @@ parse_last_updated, parse_owner, parse_properties, + parse_relations, parse_tags, ) @@ -51,6 +53,12 @@ def __init__(self, graph: DataHubGraph): .read_text() ) + self.get_database_tables_query = ( + files("data_platform_catalogue.client.datahub.graphql") + .joinpath("listContainerEntities.graphql") + .read_text() + ) + def search( self, query: str = "*", @@ -60,6 +68,7 @@ def search( ResultType.DATA_PRODUCT, ResultType.TABLE, ResultType.CHART, + ResultType.DATABASE, ), filters: Sequence[MultiSelectFilter] = (), sort: SortOption | None = None, @@ -108,6 +117,8 @@ def search( page_results.append(self._parse_dataset(entity, matched_fields)) elif entity_type == "CHART": page_results.append(self._parse_chart(entity, matched_fields)) + elif entity_type == "CONTAINER": + page_results.append(self._parse_container(entity, matched_fields)) else: raise ValueError(f"Unexpected entity type: {entity_type}") @@ -176,7 +187,46 @@ def list_data_product_assets( except GraphError as e: raise Exception("Unable to execute listDataProductAssets query") from e page_results = [] - for result in response["listDataProductAssets"]["searchResults"]: + page_results = self._get_data_collection_page_results( + response, "listDataProductAssets" + ) + + return SearchResponse( + total_results=response["listDataProductAssets"]["total"], + page_results=page_results, + ) + + def list_database_tables( + self, urn: str, count: int, start: int = 0 + ) -> SearchResponse: + variables = { + "urn": urn, + "start": start, + "count": count, + } + + try: + response = self.graph.execute_graphql( + self.get_database_tables_query, variables + ) + except GraphError as e: + raise Exception("Unable to execute listDatabaseEntities query") from e + + page_results = self._get_data_collection_page_results( + response["container"], "entities" + ) + + return SearchResponse( + total_results=response["container"]["entities"]["total"], + page_results=page_results, + ) + + def _get_data_collection_page_results(self, response, key_for_results: str): + """ + for use by entities that hold collections of data, eg. data product and container + """ + page_results = [] + for result in response[key_for_results]["searchResults"]: entity = result["entity"] entity_type = entity["type"] matched_fields: dict = {} @@ -184,11 +234,7 @@ def list_data_product_assets( page_results.append(self._parse_dataset(entity, matched_fields)) else: raise ValueError(f"Unexpected entity type: {entity_type}") - - return SearchResponse( - total_results=response["listDataProductAssets"]["total"], - page_results=page_results, - ) + return page_results def _map_result_types(self, result_types: Sequence[ResultType]): """ @@ -203,6 +249,8 @@ def _map_result_types(self, result_types: Sequence[ResultType]): types.append("GLOSSARY_TERM") if ResultType.CHART in result_types: types.append("CHART") + if ResultType.DATABASE in result_types: + types.append("CONTAINER") return types @@ -223,28 +271,22 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: tags = parse_tags(entity) last_updated = parse_last_updated(entity) name = entity["name"] - relationships = entity.get("relationships", {}) - total_data_products = relationships.get("total", 0) - data_products = relationships.get("relationships", []) - data_products = [ - {"id": i["entity"]["urn"], "name": i["entity"]["properties"]["name"]} - for i in data_products - ] + + relations = parse_relations( + RelationshipType.PARENT, entity.get("relationships", {}) + ) metadata = { "owner": owner_name, "owner_email": owner_email, - "total_data_products": total_data_products, - "data_products": data_products, + "total_parents": entity.get("relationships", {}).get("total", 0), + "parents": relations[RelationshipType.PARENT], + "entity_types": self._parse_types_and_sub_types(entity, "Dataset"), } metadata.update(parse_domain(entity)) metadata.update(custom_properties) - fqn = ( - properties.get("qualifiedName", name) - if properties.get("qualifiedName") is not None - else name - ) + fqn = self._get_fully_qualified_name(properties, name) return SearchResult( id=entity["urn"], @@ -274,11 +316,7 @@ def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: metadata.update(parse_domain(entity)) metadata.update(custom_properties) - fqn = ( - properties.get("qualifiedName", properties["name"]) - if properties.get("qualifiedName") is not None - else properties["name"] - ) + fqn = self._get_fully_qualified_name(properties, properties["name"]) return SearchResult( id=entity["urn"], @@ -365,3 +403,51 @@ def get_glossary_terms(self, count: int = 1000) -> SearchResponse: return SearchResponse( total_results=response["total"], page_results=page_results ) + + def _parse_container(self, entity: dict[str, Any], matches) -> SearchResult: + """ + Map a Container entity to a SearchResult + """ + tags = parse_tags(entity) + last_updated = parse_last_updated(entity) + properties, custom_properties = parse_properties(entity) + owner_email, owner_name = parse_owner(entity) + + metadata = { + "owner": owner_name, + "owner_email": owner_email, + "entity_types": self._parse_types_and_sub_types(entity, "Container"), + } + + fqn = self._get_fully_qualified_name(properties, properties["name"]) + + metadata.update(parse_domain(entity)) + metadata.update(custom_properties) + + return SearchResult( + id=entity["urn"], + result_type=ResultType.DATABASE, + matches=matches, + name=properties["name"], + fully_qualified_name=fqn, + description=properties.get("description", ""), + metadata=metadata, + tags=tags, + last_updated=last_updated, + ) + + def _parse_types_and_sub_types(self, entity: dict, entity_type: str) -> dict: + entity_sub_type = ( + entity.get("subTypes", {}).get("typeNames", [entity_type]) + if entity.get("subTypes") is not None + else [entity_type] + ) + return {"entity_type": entity_type, "entity_sub_types": entity_sub_type} + + def _get_fully_qualified_name(self, entity_properties: dict, name: str) -> str: + fqn = ( + entity_properties.get("qualifiedName", name) + if entity_properties.get("qualifiedName") is not None + else name + ) + return fqn diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index af9d1fee..7d9f2d2d 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -38,6 +38,16 @@ class DatabaseStatus(Enum): DEV = auto() +class RelationshipType(Enum): + PARENT = auto() + + +@dataclass +class RelatedEntity: + id: str + name: str + + @dataclass class DataProductMetadata: name: str @@ -107,13 +117,17 @@ class TableMetadata: column_details: list retention_period_in_days: int | None domain: str | None = None - parent_database_name: str | None = None + parent_entity_name: str | None = None + relationships: dict[RelationshipType, list[RelatedEntity]] | None = None source_dataset_name: str = "" where_to_access_dataset: str = "" data_sensitivity_level: SecurityClassification = SecurityClassification.OFFICIAL tags: list[str] = field(default_factory=list) major_version: int = 1 row_count: int | None = None + last_updated: datetime | None = None + owner: str = "" + owner_email: str = "" @staticmethod def from_data_product_schema_dict( diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index e3b6b2b0..4a869038 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -9,6 +9,7 @@ class ResultType(Enum): TABLE = auto() GLOSSARY_TERM = auto() CHART = auto() + DATABASE = auto() @dataclass diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index ccaeb2ab..e6f61f7a 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.21.0" +version = "0.22.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index e0c82533..a0c85c04 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -16,6 +16,8 @@ DataLocation, DataProductMetadata, DataProductStatus, + RelatedEntity, + RelationshipType, SecurityClassification, TableMetadata, ) @@ -96,7 +98,7 @@ def table(self): source_dataset_name="my_source_table", where_to_access_dataset="s3://databucket/table1", data_sensitivity_level=SecurityClassification.OFFICIAL, - parent_database_name="my_database", + parent_entity_name="my_database", domain="LAA", ) @@ -280,6 +282,19 @@ def test_get_dataset( "dataset": { "platform": {"name": "datahub"}, "ownership": None, + "subTypes": None, + "container_relations": { + "total": 1, + "relationships": [ + { + "entity": { + "urn": "urn:li:container:databse", + "properties": {"name": "database"}, + } + } + ], + }, + "data_product_relations": {"total": 0, "relationships": []}, "name": "Dataset", "properties": { "name": "Dataset", @@ -366,6 +381,12 @@ def test_get_dataset( data_sensitivity_level=SecurityClassification.OFFICIAL, tags=[], major_version=1, + relationships={ + RelationshipType.PARENT: [ + RelatedEntity(id="urn:li:container:databse", name="database") + ] + }, + domain="", ) def test_get_chart_details(self, datahub_client, base_mock_graph): diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py index 8e27d397..a229932a 100644 --- a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -1,4 +1,8 @@ -from data_platform_catalogue.client.datahub.graphql_helpers import parse_columns +from data_platform_catalogue.client.datahub.graphql_helpers import ( + parse_columns, + parse_relations, +) +from data_platform_catalogue.entities import RelatedEntity, RelationshipType def test_parse_columns_with_primary_key_and_foreign_key(): @@ -118,3 +122,31 @@ def test_parse_columns_with_no_schema(): assert parse_columns(entity) == [] assert parse_columns(entity) == [] + + +def test_parse_relations(): + relations = { + "relationships": { + "total": 1, + "relationships": [ + { + "entity": { + "urn": "urn:li:dataProduct:test", + "properties": {"name": "test"}, + } + } + ], + } + } + result = parse_relations(RelationshipType.PARENT, relations["relationships"]) + assert result == { + RelationshipType.PARENT: [ + RelatedEntity(id="urn:li:dataProduct:test", name="test") + ] + } + + +def test_parse_relations_blank(): + relations = {"relationships": {"total": 0, "relationships": []}} + result = parse_relations(RelationshipType.PARENT, relations["relationships"]) + assert result == {RelationshipType.PARENT: []} diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 9b328289..551cc0ca 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -3,6 +3,7 @@ import pytest from data_platform_catalogue.client.datahub.search import SearchClient +from data_platform_catalogue.entities import RelatedEntity from data_platform_catalogue.search_types import ( FacetOption, MultiSelectFilter, @@ -172,12 +173,16 @@ def test_dataset_result(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [], - "total_data_products": 0, + "total_parents": 0, + "parents": [], "domain_name": "HMPPS", "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", "StoredAsSubDirectories": "False", "CreatedByJob": "moj-reg-prod-hmpps-assess-risks-and-needs-prod-glue-job", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], ), @@ -251,10 +256,14 @@ def test_full_page(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [], - "total_data_products": 0, + "parents": [], + "total_parents": 0, "domain_name": "", "domain_id": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], last_updated=datetime(2024, 1, 23, 6, 15, 2, 353000), @@ -269,10 +278,14 @@ def test_full_page(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [], - "total_data_products": 0, + "parents": [], + "total_parents": 0, "domain_name": "", "domain_id": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], last_updated=None, @@ -287,10 +300,14 @@ def test_full_page(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [], - "total_data_products": 0, + "parents": [], + "total_parents": 0, "domain_name": "", "domain_id": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], last_updated=None, @@ -352,10 +369,14 @@ def test_query_match(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [], - "total_data_products": 0, + "parents": [], + "total_parents": 0, "domain_id": "", "domain_name": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], ) @@ -413,10 +434,14 @@ def test_result_with_owner(mock_graph, searcher): metadata={ "owner": "Shannon Lovett", "owner_email": "shannon@longtail.com", - "data_products": [], - "total_data_products": 0, + "parents": [], + "total_parents": 0, "domain_id": "", "domain_name": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], ) @@ -644,6 +669,7 @@ def test_result_with_data_product(mock_graph, searcher): "type": "DATASET", "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 "name": "calm-pagoda-323403.jaffle_shop.customers", + "subType": None, "relationships": { "total": 1, "relationships": [ @@ -680,10 +706,14 @@ def test_result_with_data_product(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [{"id": "urn:abc", "name": "abc"}], - "total_data_products": 1, + "parents": [RelatedEntity(id="urn:abc", name="abc")], + "total_parents": 1, "domain_id": "", "domain_name": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], ) @@ -745,10 +775,14 @@ def test_list_data_product_assets(mock_graph, searcher): metadata={ "owner": "", "owner_email": "", - "data_products": [{"id": "urn:abc", "name": "abc"}], - "total_data_products": 1, + "parents": [RelatedEntity(id="urn:abc", name="abc")], + "total_parents": 1, "domain_id": "", "domain_name": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, }, tags=[], ) @@ -888,3 +922,170 @@ def test_search_for_charts(mock_graph, searcher): ) ], ) + + +def test_search_for_container(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 1, + "total": 1, + "searchResults": [ + { + "insights": [], + "matchedFields": [ + { + "name": "urn", + "value": "urn:li:container:test_db", + }, + { + "name": "description", + "value": "test", + }, + {"name": "name", "value": "test_db"}, + ], + "entity": { + "type": "CONTAINER", + "urn": "urn:li:container:test_db", + "platform": {"name": "athena"}, + "subTypes": {"typeNames": ["Database"]}, + "ownership": { + "owners": [ + { + "owner": { + "urn": "urn:li:corpuser:shannon@longtail.com", + "properties": { + "fullName": "Shannon Lovett", + "email": "shannon@longtail.com", + }, + } + } + ] + }, + "properties": { + "name": "test_db", + "description": "test", + "customProperties": [ + {"key": "dpia_required", "value": "False"}, + ], + }, + "domain": { + "domain": { + "urn": "urn:li:domain:testdom", + "id": "general", + "properties": {"name": "testdom", "description": ""}, + } + }, + "tags": { + "tags": [ + { + "tag": { + "urn": "urn:li:tag:test", + "properties": { + "name": "test", + "description": "test tag", + }, + } + } + ] + }, + }, + } + ], + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:container:test_db", + name="test_db", + fully_qualified_name="test_db", + description="test", + matches={ + "urn": "urn:li:container:test_db", + "description": "test", + "name": "test_db", + }, + result_type=ResultType.DATABASE, + metadata={ + "owner": "Shannon Lovett", + "owner_email": "shannon@longtail.com", + "domain_id": "urn:li:domain:testdom", + "domain_name": "testdom", + "dpia_required": "False", + "entity_types": { + "entity_type": "Container", + "entity_sub_types": ["Database"], + }, + }, + tags=["test"], + ) + ], + ) + + +def test_list_database_tables(mock_graph, searcher): + datahub_response = { + "container": { + "entities": { + "total": 1, + "searchResults": [ + { + "entity": { + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:athena,test_db.test_table,PROD)", + "platform": {"name": "athena"}, + "name": "test_db.test_table", + "subTypes": {"typeNames": ["Table"]}, + "properties": { + "name": "test_table", + "qualifiedName": "test_db.test_table", + "description": "just for test", + "customProperties": [ + { + "key": "whereToAccessDataset", + "value": "analytical_platform", + }, + {"key": "sensitivityLevel", "value": "OFFICIAL"}, + ], + }, + } + } + ], + } + } + } + + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + response = searcher.list_database_tables(urn="urn:li:athena:test", count=10) + assert response == SearchResponse( + total_results=1, + page_results=[ + SearchResult( + id="urn:li:dataset:(urn:li:dataPlatform:athena,test_db.test_table,PROD)", + name="test_table", + fully_qualified_name="test_db.test_table", + description="just for test", + result_type=ResultType.TABLE, + metadata={ + "owner": "", + "owner_email": "", + "domain_id": "", + "domain_name": "", + "whereToAccessDataset": "analytical_platform", + "sensitivityLevel": "OFFICIAL", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Table"], + }, + "total_parents": 0, + "parents": [], + }, + ) + ], + ) From df11b2c76370d50fadb5faa9c5c97e025f1b85d9 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 26 Mar 2024 15:28:05 +0000 Subject: [PATCH 43/64] Add missing metadata when fetching datasets (#3722) * Test tags in dataset response * Return native types if available When presenting datasets in our UI, use the native data types if we have them. We suspect these will be more useful to the users. * Return qualifiedName in datasets if available * Return created and modified timestamps Return created and modified timestamps for datasets and search results. Previously we were returning the last ingested timestamp for search results, which is not exactly what we want to present to users. It gives an indication of whether metadata may be stale or not, but we shouldn't present that as when the metadata was last modified, or the user will conclude that there has been some change that never happened. Returning this for datasets as well means we can display it on the details page. * Bump version to 0.23.0 * Lint * Fall back to top level name attribute if missing from properties According to https://datahubproject.io/docs/graphql/objects/#dataset name is not supposed to be used anymore, but it seems like the dbt ingestion is populating this instead of properties.name. --- lib/datahub-client/CHANGELOG.md | 14 ++++- .../client/datahub/datahub_client.py | 11 +++- .../datahub/graphql/getDatasetDetails.graphql | 17 ++--- .../client/datahub/graphql_helpers.py | 20 +++++- .../client/datahub/search.py | 6 +- .../data_platform_catalogue/entities.py | 4 +- .../data_platform_catalogue/search_types.py | 1 + lib/datahub-client/pyproject.toml | 2 +- .../client/datahub/test_datahub_client.py | 63 +++++++++++++++++-- .../client/datahub/test_graphql_helpers.py | 20 ++++-- .../tests/client/datahub/test_search.py | 6 +- 11 files changed, 131 insertions(+), 33 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 2e440a89..be5ec4cd 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.23.0] 2024-03-26 + +### Changed + +- Use timezone aware timestamps when returning last updated values, and source + the information from `lastModified` in Datahub, not `lastIngested`. +- Prefer native data types to Datahub's type when returning schemas for datasets + +### Added + +- Return qualifiedName and tags for datasets if available + ## [0.22.0] 2023-03-19 ### Added @@ -37,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A ChartMetadata class with very limited attributes - ResultType.CHART - A GraphQL query for chart details -- SearchClient._parse_chart, incorporated chart parsing into the normal search method +- SearchClient.\_parse_chart, incorporated chart parsing into the normal search method - DataHubCatalogueClient.get_chart_details for the chart display page ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py index 2de534ab..8a639f95 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py @@ -47,6 +47,7 @@ from ..base import BaseCatalogueClient, CatalogueError, logger from .graphql_helpers import ( parse_columns, + parse_created_and_modified, parse_domain, parse_owner, parse_properties, @@ -664,6 +665,8 @@ def get_table_details(self, urn) -> TableMetadata: domain = parse_domain(response) owner, owner_email = parse_owner(response) tags = parse_tags(response) + name = properties.get("name", response.get("name")) + created, modified = parse_created_and_modified(properties) # A dataset can't have both a container and data product parent, but if we did # start to use in that we'd need to change this @@ -675,17 +678,21 @@ def get_table_details(self, urn) -> TableMetadata: relations = parse_relations( RelationshipType.PARENT, response["data_product_relations"] ) + else: + relations = {} return TableMetadata( - name=properties["name"], + name=name, + fully_qualified_name=properties.get("qualifiedName") or name, description=properties.get("description", ""), column_details=columns, retention_period_in_days=custom_properties.get("retentionPeriodInDays"), relationships=relations, domain=domain["domain_name"], tags=tags, - last_updated=None, # we are not populating this and i think wouldn't refer to lastIngested owner=owner, owner_email=owner_email, + first_created=created, + last_updated=modified, ) except GraphError as e: raise Exception("Unable to execute getDataset query") from e diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql index 3c26e3df..dd5b9409 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql @@ -3,6 +3,7 @@ query getDatasetDetails($urn: String!) { platform { name } + name # Deprecated - prefer properties.name domain { domain { urn @@ -16,12 +17,8 @@ query getDatasetDetails($urn: String!) { subTypes { typeNames } - container_relations:relationships( - input: { - types: ["IsPartOf"] - direction: OUTGOING - count: 10 - } + container_relations: relationships( + input: { types: ["IsPartOf"], direction: OUTGOING, count: 10 } ) { total relationships { @@ -35,12 +32,8 @@ query getDatasetDetails($urn: String!) { } } } - data_product_relations:relationships( - input: { - types: ["DataProductContains"] - direction: INCOMING - count: 10 - } + data_product_relations: relationships( + input: { types: ["DataProductContains"], direction: INCOMING, count: 10 } ) { total relationships { diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py index cc65c69d..10dae7d8 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py @@ -1,5 +1,5 @@ from collections import defaultdict -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Tuple from data_platform_catalogue.entities import RelatedEntity, RelationshipType @@ -29,7 +29,21 @@ def parse_last_updated(entity: dict[str, Any]) -> datetime | None: timestamp = entity.get("lastIngested") if timestamp is None: return None - return datetime.utcfromtimestamp(timestamp / 1000) + return datetime.fromtimestamp(timestamp / 1000, timezone.utc) + + +def parse_created_and_modified( + properties: dict[str, Any] +) -> Tuple[datetime | None, datetime | None]: + created = properties.get("created") + modified = properties.get("lastModified") + + if created is not None: + created = datetime.fromtimestamp(created / 1000, timezone.utc) + if modified is not None: + modified = datetime.fromtimestamp(modified / 1000, timezone.utc) + + return created, modified def parse_tags(entity: dict[str, Any]) -> list[str]: @@ -127,7 +141,7 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: { "name": field["fieldPath"], "description": field["description"], - "type": field["type"], + "type": field.get("nativeDataType", field["type"]), "nullable": field["nullable"], "isPrimaryKey": is_primary_key, "foreignKeys": foreign_keys_for_field, diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py index e0ec2ff4..57428018 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/search.py @@ -17,6 +17,7 @@ SortOption, ) from .graphql_helpers import ( + parse_created_and_modified, parse_domain, parse_last_updated, parse_owner, @@ -288,6 +289,8 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: fqn = self._get_fully_qualified_name(properties, name) + created, modified = parse_created_and_modified(properties) + return SearchResult( id=entity["urn"], result_type=ResultType.TABLE, @@ -297,7 +300,8 @@ def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: description=properties.get("description", ""), metadata=metadata, tags=tags, - last_updated=last_updated, + last_updated=modified or last_updated, + first_created=created, ) def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index 7d9f2d2d..b9cb60ae 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -121,13 +121,15 @@ class TableMetadata: relationships: dict[RelationshipType, list[RelatedEntity]] | None = None source_dataset_name: str = "" where_to_access_dataset: str = "" + fully_qualified_name: str | None = None data_sensitivity_level: SecurityClassification = SecurityClassification.OFFICIAL tags: list[str] = field(default_factory=list) major_version: int = 1 row_count: int | None = None - last_updated: datetime | None = None owner: str = "" owner_email: str = "" + last_updated: datetime | None = None + first_created: datetime | None = None @staticmethod def from_data_product_schema_dict( diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 4a869038..9057cd0d 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -60,6 +60,7 @@ class SearchResult: metadata: dict[str, Any] = field(default_factory=dict) tags: list[str] = field(default_factory=list) last_updated: datetime | None = None + first_created: datetime | None = None @dataclass diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index e6f61f7a..683215ad 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.22.0" +version = "0.23.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index a0c85c04..84c671a2 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from unittest.mock import MagicMock, patch @@ -298,12 +298,23 @@ def test_get_dataset( "name": "Dataset", "properties": { "name": "Dataset", - "qualifiedName": None, + "qualifiedName": "Foo.Dataset", "description": "Dataset", + "customProperties": [ + {"key": "sensitivityLevel", "value": "OFFICIAL-SENSITIVE"} + ], + "lastModified": 1709619407814, }, "editableProperties": None, "tags": { - "tags": [{"tag": {"urn": "urn:li:tag:Entity", "properties": None}}] + "tags": [ + { + "tag": { + "urn": "urn:li:tag:Entity", + "properties": {"name": "some-tag"}, + } + } + ] }, "lastIngested": 1709619407814, "domain": None, @@ -351,10 +362,11 @@ def test_get_dataset( assert dataset == TableMetadata( name="Dataset", description="Dataset", + fully_qualified_name="Foo.Dataset", column_details=[ { "name": "urn", - "type": "STRING", + "type": "string", "description": "The primary identifier for the dataset entity.", "isPrimaryKey": True, "foreignKeys": [], @@ -362,7 +374,7 @@ def test_get_dataset( }, { "name": "upstreamLineage", - "type": "STRUCT", + "type": "upstreamLineage", "description": "Upstream lineage of a dataset", "foreignKeys": [ { @@ -379,7 +391,7 @@ def test_get_dataset( source_dataset_name="", where_to_access_dataset="", data_sensitivity_level=SecurityClassification.OFFICIAL, - tags=[], + tags=["some-tag"], major_version=1, relationships={ RelationshipType.PARENT: [ @@ -387,6 +399,45 @@ def test_get_dataset( ] }, domain="", + last_updated=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + ) + + def test_get_dataset_minimal_properties( + self, + datahub_client, + base_mock_graph, + ): + urn = "abc" + datahub_response = { + "dataset": { + "platform": {"name": "datahub"}, + "name": "notinproperties", + "properties": {}, + "container_relations": { + "total": 0, + }, + "data_product_relations": {"total": 0, "relationships": []}, + "schemaMetadata": {"fields": []}, + } + } + base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + dataset = datahub_client.get_table_details(urn) + + assert dataset == TableMetadata( + name="notinproperties", + fully_qualified_name="notinproperties", + description="", + column_details=[], + retention_period_in_days=None, + source_dataset_name="", + where_to_access_dataset="", + data_sensitivity_level=SecurityClassification.OFFICIAL, + tags=[], + major_version=1, + relationships={}, + domain="", + last_updated=None, ) def test_get_chart_details(self, datahub_client, base_mock_graph): diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py index a229932a..dadb57ff 100644 --- a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -1,5 +1,8 @@ +from datetime import datetime, timezone + from data_platform_catalogue.client.datahub.graphql_helpers import ( parse_columns, + parse_created_and_modified, parse_relations, ) from data_platform_catalogue.entities import RelatedEntity, RelationshipType @@ -48,7 +51,7 @@ def test_parse_columns_with_primary_key_and_foreign_key(): assert parse_columns(entity) == [ { "name": "urn", - "type": "STRING", + "type": "string", "isPrimaryKey": True, "foreignKeys": [], "nullable": False, @@ -56,7 +59,7 @@ def test_parse_columns_with_primary_key_and_foreign_key(): }, { "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", - "type": "STRUCT", + "type": "upstreamLineage", "description": "Upstream lineage of a dataset", "nullable": False, "isPrimaryKey": False, @@ -100,7 +103,7 @@ def test_parse_columns_with_no_keys(): assert parse_columns(entity) == [ { "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", - "type": "STRUCT", + "type": "upstreamLineage", "description": "Upstream lineage of a dataset", "nullable": False, "isPrimaryKey": False, @@ -108,7 +111,7 @@ def test_parse_columns_with_no_keys(): }, { "name": "urn", - "type": "STRING", + "type": "string", "isPrimaryKey": False, "foreignKeys": [], "nullable": False, @@ -150,3 +153,12 @@ def test_parse_relations_blank(): relations = {"relationships": {"total": 0, "relationships": []}} result = parse_relations(RelationshipType.PARENT, relations["relationships"]) assert result == {RelationshipType.PARENT: []} + + +def test_parse_created_and_modified(): + properties = {"created": 1710426920000, "lastModified": 1710426921000} + + created, modified = parse_created_and_modified(properties) + + assert created == datetime(2024, 3, 14, 14, 35, 20, tzinfo=timezone.utc) + assert modified == datetime(2024, 3, 14, 14, 35, 21, tzinfo=timezone.utc) diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 551cc0ca..6c9f57eb 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from unittest.mock import MagicMock import pytest @@ -266,7 +266,9 @@ def test_full_page(mock_graph, searcher): }, }, tags=[], - last_updated=datetime(2024, 1, 23, 6, 15, 2, 353000), + last_updated=datetime( + 2024, 1, 23, 6, 15, 2, 353000, tzinfo=timezone.utc + ), ), SearchResult( id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", From f702593e872bd1c564d93296cace7f748ab2b845 Mon Sep 17 00:00:00 2001 From: Matt <38562764+LavMatt@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:23:09 +0100 Subject: [PATCH 44/64] update lastModified property in data-platform-catalogue (#4024) * update 'lastModified` property * update poetry and changelog * add a space --- lib/datahub-client/CHANGELOG.md | 8 ++++++++ .../client/datahub/graphql/search.graphql | 5 ++++- lib/datahub-client/pyproject.toml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index be5ec4cd..37e84a66 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.24.0] 2024-04-11 + +### Changed + +- `search.graphql` query to be compatible with Datahub v0.12.1, where the property + `lastModified` has changed type from `Long` to `AuditStamp` a nested structure + which contains a time (long) and an actor (string). + ## [0.23.0] 2024-03-26 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql index 05b887d9..cbd17b69 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql @@ -148,7 +148,10 @@ query Search( value } created - lastModified + lastModified { + time + actor + } } editableProperties { description diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 683215ad..fddd3c2b 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.23.0" +version = "0.24.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From 6f319613b048dd3667721d712949d54adcfcd905 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:31:33 +0100 Subject: [PATCH 45/64] Accounted for lastModified changing to nested structure (#4063) * Accounted for lastModified changing to nested structure * Bumped project version --- lib/datahub-client/CHANGELOG.md | 7 +++++++ .../client/datahub/graphql_helpers.py | 2 +- lib/datahub-client/pyproject.toml | 2 +- .../tests/client/datahub/test_datahub_client.py | 2 +- .../tests/client/datahub/test_graphql_helpers.py | 5 ++++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 37e84a66..588c3075 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.24.1] 2024-04-11 + +### Changed + +- `lastModified` should be handled as a dict and not as an int in the code. + These changes fix a bug where a dict was treated as an int. + ## [0.24.0] 2024-04-11 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py index 10dae7d8..ee25ccdc 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py @@ -36,7 +36,7 @@ def parse_created_and_modified( properties: dict[str, Any] ) -> Tuple[datetime | None, datetime | None]: created = properties.get("created") - modified = properties.get("lastModified") + modified = properties.get("lastModified", {}).get("time") if created is not None: created = datetime.fromtimestamp(created / 1000, timezone.utc) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index fddd3c2b..7e51e0ca 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.24.0" +version = "0.24.1" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 84c671a2..2ea4e91e 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -303,7 +303,7 @@ def test_get_dataset( "customProperties": [ {"key": "sensitivityLevel", "value": "OFFICIAL-SENSITIVE"} ], - "lastModified": 1709619407814, + "lastModified": {"time": 1709619407814}, }, "editableProperties": None, "tags": { diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py index dadb57ff..643d3f62 100644 --- a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -156,7 +156,10 @@ def test_parse_relations_blank(): def test_parse_created_and_modified(): - properties = {"created": 1710426920000, "lastModified": 1710426921000} + properties = { + "created": 1710426920000, + "lastModified": {"time": 1710426921000, "actor": "Shakira"}, + } created, modified = parse_created_and_modified(properties) From 4caacac0825d1ca79774b980792c33a92aa47de6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:59:06 +0100 Subject: [PATCH 46/64] Bump dnspython from 2.4.2 to 2.6.1 in /python-libraries/data-platform-catalogue (#4064) Bump dnspython in /python-libraries/data-platform-catalogue Bumps [dnspython](https://github.com/rthalley/dnspython) from 2.4.2 to 2.6.1. - [Release notes](https://github.com/rthalley/dnspython/releases) - [Changelog](https://github.com/rthalley/dnspython/blob/main/doc/whatsnew.rst) - [Commits](https://github.com/rthalley/dnspython/compare/v2.4.2...v2.6.1) --- updated-dependencies: - dependency-name: dnspython dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- lib/datahub-client/poetry.lock | 55 +++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index ccaa195d..89eb2823 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "acryl-datahub" @@ -774,22 +774,23 @@ toml = ["tomli (>=1.2.1)"] [[package]] name = "dnspython" -version = "2.4.2" +version = "2.6.1" description = "DNS toolkit" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] [[package]] name = "docker" @@ -1493,6 +1494,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2148,6 +2159,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2673,30 +2685,51 @@ description = "Database Abstraction Library" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ + {file = "SQLAlchemy-1.4.50-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:54138aa80d2dedd364f4e8220eef284c364d3270aaef621570aa2bd99902e2e8"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"}, {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-win32.whl", hash = "sha256:6c78e3fb4a58e900ec433b6b5f4efe1a0bf81bbb366ae7761c6e0051dd310ee3"}, + {file = "SQLAlchemy-1.4.50-cp310-cp310-win_amd64.whl", hash = "sha256:d55f7a33e8631e15af1b9e67c9387c894fedf6deb1a19f94be8731263c51d515"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:324b1fdd50e960a93a231abb11d7e0f227989a371e3b9bd4f1259920f15d0304"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"}, {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-win32.whl", hash = "sha256:8bdab03ff34fc91bfab005e96f672ae207d87e0ac7ee716d74e87e7046079d8b"}, + {file = "SQLAlchemy-1.4.50-cp311-cp311-win_amd64.whl", hash = "sha256:52e01d60b06f03b0a5fc303c8aada405729cbc91a56a64cead8cb7c0b9b13c1a"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"}, {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7"}, + {file = "SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fb9adc4c6752d62c6078c107d23327aa3023ef737938d0135ece8ffb67d07030"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"}, {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-win32.whl", hash = "sha256:1b9c4359d3198f341480e57494471201e736de459452caaacf6faa1aca852bd8"}, + {file = "SQLAlchemy-1.4.50-cp36-cp36m-win_amd64.whl", hash = "sha256:35e4520f7c33c77f2636a1e860e4f8cafaac84b0b44abe5de4c6c8890b6aaa6d"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f5b1fb2943d13aba17795a770d22a2ec2214fc65cff46c487790192dda3a3ee7"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"}, {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-win32.whl", hash = "sha256:7b4396452273aedda447e5aebe68077aa7516abf3b3f48408793e771d696f397"}, + {file = "SQLAlchemy-1.4.50-cp37-cp37m-win_amd64.whl", hash = "sha256:752f9df3dddbacb5f42d8405b2d5885675a93501eb5f86b88f2e47a839cf6337"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:35c7ed095a4b17dbc8813a2bfb38b5998318439da8e6db10a804df855e3a9e3a"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"}, {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-win32.whl", hash = "sha256:8a219688297ee5e887a93ce4679c87a60da4a5ce62b7cb4ee03d47e9e767f558"}, + {file = "SQLAlchemy-1.4.50-cp38-cp38-win_amd64.whl", hash = "sha256:a648770db002452703b729bdcf7d194e904aa4092b9a4d6ab185b48d13252f63"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4be4da121d297ce81e1ba745a0a0521c6cf8704634d7b520e350dce5964c71ac"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"}, {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-win32.whl", hash = "sha256:a7c9b9dca64036008962dd6b0d9fdab2dfdbf96c82f74dbd5d86006d8d24a30f"}, + {file = "SQLAlchemy-1.4.50-cp39-cp39-win_amd64.whl", hash = "sha256:df200762efbd672f7621b253721644642ff04a6ff957236e0e2fe56d9ca34d2c"}, {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"}, ] From 73854168f077a2db86ecfbc9594e7dcdb8c8c7e4 Mon Sep 17 00:00:00 2001 From: Tom Webber <80110358+tom-webber@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:41:16 +0100 Subject: [PATCH 47/64] update instances of `lastModified` property in data-platform-catalogue (#4078) * update instances of `lastModified` property in data-platform-catalogue * update changelog text --- lib/datahub-client/CHANGELOG.md | 7 +++++++ .../datahub/graphql/getChartDetails.graphql | 1 + .../datahub/graphql/getDatasetDetails.graphql | 5 ++++- .../graphql/listDataProductAssets.graphql | 20 +++++++++++++++---- lib/datahub-client/pyproject.toml | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 588c3075..431a693a 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.24.2] 2024-04-16 + +### Changed + +- `lastModified` syntax updated for getDatasetDetails, getChartDetails, + listDataProductAssets, as per `0.24.0` + ## [0.24.1] 2024-04-11 ### Changed diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql index 65874bfa..4409888c 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql @@ -35,6 +35,7 @@ query getChartDetails($urn: String!) { } lastModified { time + actor } } } diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql index dd5b9409..9f40e2aa 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql @@ -77,7 +77,10 @@ query getDatasetDetails($urn: String!) { value } created - lastModified + lastModified { + time + actor + } } editableProperties { description diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql index d1000032..6146c282 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql +++ b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql @@ -1,7 +1,12 @@ -query dataProductDetails($urn: String!, $query: String!, $count: Int!, $start: Int!) { +query dataProductDetails( + $urn: String! + $query: String! + $count: Int! + $start: Int! +) { listDataProductAssets( urn: $urn - input: {query: $query, start: $start, count: $count} + input: { query: $query, start: $start, count: $count } ) { start count @@ -16,7 +21,11 @@ query dataProductDetails($urn: String!, $query: String!, $count: Int!, $start: I name } relationships( - input: {types: ["DataProductContains"], direction: INCOMING, count: 10} + input: { + types: ["DataProductContains"] + direction: INCOMING + count: 10 + } ) { total relationships { @@ -60,7 +69,10 @@ query dataProductDetails($urn: String!, $query: String!, $count: Int!, $start: I value } created - lastModified + lastModified { + time + actor + } } editableProperties { description diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index 7e51e0ca..eae504b5 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.24.1" +version = "0.24.2" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" From c654278d8f6ad7fe0ea6f79166d1dc0715dea841 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:39:28 +0100 Subject: [PATCH 48/64] Catalogue library refactor (#4115) ### Added - Pydantic entity types - `DataHubCatalogueClient`.`upsert_chart` ### Changes - Exceptions thrown by DatahubClient are now imported from `data_platform_catalogue.client.exceptions` - Upsert methods of `DatahubClient` simplified to `upsert_table`, `upsert_database` etc - Renamed `last_updated` to datahub-friendly `last_modified`. - Renamed `first_created` to datahub-friendly `created`. - Use `urn` rather than `id` in datahub related contexts. ### Removed - `BaseCatalogueClient` class - References to data products in methods and classes - `DataHubCatalogueClient`.`upsert_data_product` - `DataHubCatalogueClient`.`upsert_athena_table` - `DataHubCatalogueClient`.`upsert_athena_database` - `DataHubCatalogueClient`.`list_data_product_assets` - `DatabaseStatus` - `SecurityClassification` --- lib/datahub-client/.gitignore | 1 + lib/datahub-client/CHANGELOG.md | 32 +- lib/datahub-client/README.md | 11 - .../data_platform_catalogue/__init__.py | 8 - .../data_platform_catalogue/client/README.md | 133 ++ .../client/__init__.py | 3 - .../data_platform_catalogue/client/base.py | 135 -- .../client/datahub/__init__.py | 1 - .../client/datahub/datahub_client.py | 717 -------- .../graphql/listDataProductAssets.graphql | 106 -- .../client/datahub_client.py | 537 ++++++ .../client/exceptions.py | 29 + .../client/{datahub => }/graphql/__init__.py | 0 .../{datahub => }/graphql/facets.graphql | 0 .../graphql/getChartDetails.graphql | 0 .../graphql/getDatasetDetails.graphql | 0 .../graphql/getGlossaryTerms.graphql | 0 .../graphql/listContainerEntities.graphql | 0 .../{datahub => }/graphql/search.graphql | 0 .../client/{datahub => }/graphql_helpers.py | 119 +- .../client/{datahub => }/search.py | 261 ++- .../data_platform_catalogue/entities.py | 412 ++--- .../data_platform_catalogue/search_types.py | 11 +- lib/datahub-client/poetry.lock | 1471 ++--------------- lib/datahub-client/pyproject.toml | 17 +- .../client/datahub/test_datahub_client.py | 617 +++---- .../client/datahub/test_graphql_helpers.py | 147 +- .../tests/client/datahub/test_search.py | 445 ++--- lib/datahub-client/tests/conftest.py | 4 +- .../tests/snapshots/datahub_create_table.json | 77 - .../datahub_create_table_with_metadata.json | 162 -- ...tahub_create_two_tables_with_metadata.json | 259 --- ...h_metadata.json => test_upsert_table.json} | 70 +- ...on => test_upsert_table_and_database.json} | 107 +- lib/datahub-client/tests/test_entities.py | 94 -- .../tests/test_helpers/graph_helpers.py | 11 +- .../test_integration_with_datahub_server.py | 495 +++--- .../golden_data_product_in.json | 59 - 38 files changed, 2266 insertions(+), 4285 deletions(-) delete mode 100644 lib/datahub-client/data_platform_catalogue/__init__.py create mode 100644 lib/datahub-client/data_platform_catalogue/client/README.md delete mode 100644 lib/datahub-client/data_platform_catalogue/client/base.py delete mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py delete mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py delete mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql create mode 100644 lib/datahub-client/data_platform_catalogue/client/datahub_client.py create mode 100644 lib/datahub-client/data_platform_catalogue/client/exceptions.py rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/__init__.py (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/facets.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/getChartDetails.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/getDatasetDetails.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/getGlossaryTerms.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/listContainerEntities.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql/search.graphql (100%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/graphql_helpers.py (58%) rename lib/datahub-client/data_platform_catalogue/client/{datahub => }/search.py (60%) delete mode 100644 lib/datahub-client/tests/snapshots/datahub_create_table.json delete mode 100644 lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json delete mode 100644 lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json rename lib/datahub-client/tests/snapshots/{datahub_create_athena_table_with_metadata.json => test_upsert_table.json} (53%) rename lib/datahub-client/tests/snapshots/{datahub_create_athena_table.json => test_upsert_table_and_database.json} (58%) delete mode 100644 lib/datahub-client/tests/test_entities.py delete mode 100644 lib/datahub-client/tests/test_resources/golden_data_product_in.json diff --git a/lib/datahub-client/.gitignore b/lib/datahub-client/.gitignore index b03b80e2..127ff25c 100644 --- a/lib/datahub-client/.gitignore +++ b/lib/datahub-client/.gitignore @@ -7,3 +7,4 @@ env/ *.code-workspace dist/ __pycache__ +.idea/ diff --git a/lib/datahub-client/CHANGELOG.md b/lib/datahub-client/CHANGELOG.md index 431a693a..8ca25b67 100644 --- a/lib/datahub-client/CHANGELOG.md +++ b/lib/datahub-client/CHANGELOG.md @@ -7,6 +7,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] 2024-04-23 + +Large refactor of DatahubClient with breaking changes. + +### Added + +- Pydantic entity types +- `DataHubCatalogueClient`.`upsert_chart` + +### Changes + +- Exceptions thrown by DatahubClient are now imported from + `data_platform_catalogue.client.exceptions` +- Upsert methods of `DatahubClient` simplified to `upsert_table`, + `upsert_database` etc +- Renamed `last_updated` to datahub-friendly `last_modified`. +- Renamed `first_created` to datahub-friendly `created`. +- Use `urn` rather than `id` in datahub related contexts. + +### Removed + +- `BaseCatalogueClient` class +- References to data products in methods and classes +- `DataHubCatalogueClient`.`upsert_data_product` +- `DataHubCatalogueClient`.`upsert_athena_table` +- `DataHubCatalogueClient`.`upsert_athena_database` +- `DataHubCatalogueClient`.`list_data_product_assets` +- `DatabaseStatus` +- `SecurityClassification` + ## [0.24.2] 2024-04-16 ### Changed @@ -56,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `_parse_relations` function to `graphql_helpers` - `_parse_types_and_sub_types` method to `search.SearchClient` - `_get_fully_qualified_name` method to `search.SearchClient` -- `relationships`, `last_updated`, `owner`, `owner_email` to `entities.TableMetadata` +- `relationships`, `last_modified`, `owner`, `owner_email` to `entities.TableMetadata` ### Changed diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index ed3202dc..a06df70e 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -19,10 +19,6 @@ pip install ministryofjustice-data-platform-catalogue ## Terminology - **Data assets** - Any databases, tables, or schemas within the metadata graph -- **Data products** - Groupings of data assets that are published for - reuse across MOJ. In the data platform, the concepts of database and data - product are similar, but they may be represented as different entities in the - catalogue. - **Domains** - allow metadata to be grouped into different service areas that have their own governance, like HMCTS, HMPPS, OPG, etc. @@ -132,13 +128,6 @@ URNs: ### DataHub -- Each data product within the MOJ data platform is created as a data product entity - Each table is created as a dataset in DataHub - Tables that reside in the same athena database (data_product_v1) should be placed within the same DataHub container. - -## OpenMetadata - -- Each MOJ data product is mapped to a database in the OpenMetadata catalogue -- We populate the schema level in openmetdata with a generic entry of `Tables` -- Each table is mapped to a table in openmetadata diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py deleted file mode 100644 index 4155a3e7..00000000 --- a/lib/datahub-client/data_platform_catalogue/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .client import CatalogueError, ReferencedEntityMissing # noqa: F401 -from .entities import ( # noqa: F401 - CatalogueMetadata, - DatabaseMetadata, - DataLocation, - DataProductMetadata, - TableMetadata, -) diff --git a/lib/datahub-client/data_platform_catalogue/client/README.md b/lib/datahub-client/data_platform_catalogue/client/README.md new file mode 100644 index 00000000..8a3d715c --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/README.md @@ -0,0 +1,133 @@ +# Data platform catalogue + +This library is part of the Ministry of Justice data platform. + +It publishes object metadata to a data catalogue, so that the +metadata can be made discoverable by consumers. + +Broadly speaking, a catalogue stores a _metadata graph_, consisting of +_data assets_. Data assets could be **tables**, **schemas** or **databases**. + +## How to install + +To install the package using `pip`, run: + +```shell +pip install ministryofjustice-data-platform-catalogue +``` + +## Terminology + +- **Data assets** - Any databases, tables, or schemas within the metadata graph +- **Domains** - allow metadata to be grouped into different service areas that have + their own governance, like HMCTS, HMPPS, OPG, etc. + +## Example usage + +```python +from data_platform_catalogue import ( + DataHubCatalogueClient, + BaseCatalogueClient, DataLocation, CatalogueMetadata, + DataProductMetadata, TableMetadata, + CatalogueError +) + +client: BaseCatalogueClient = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + +data_product = DataProductMetadata( + name = "my_data_product", + description = "bla bla", + version = "v1.0.0", + owner = "7804c127-d677-4900-82f9-83517e51bb94", + email = "justice@justice.gov.uk", + retention_period_in_days = 365, + domain = "LAA", + subdomain = "Legal Aid", + dpia_required = False +) + +table = TableMetadata( + name = "my_table", + description = "bla bla", + column_details=[ + {"name": "foo", "type": "string", "description": "a"}, + {"name": "bar", "type": "int", "description": "b"}, + ], + retention_period_in_days = 365, + major_version = 1 +) + +try: + table_fqn = client.upsert_table( + table=table, + data_product_metadata=data_product, + location=DataLocation("test_data_product_v1"), + ) +except CatalogueError: + print("oh no") +``` + +## Search example + +```python +response = client.search() + +# Total results across all pages +print(response.total_results) + +# Iterate over search results +for item in response.page_results: + print(item) + +# Iterate over facet options +for option in response.facets.options('domains'): + print(option.label) + print(option.value) + print(option.count) + +# Include a filter and sort +client.search( + filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])], + sort=SortOption(field="name", ascending=False) +) +``` + +## Search filters + +### Datahub + +Basic filters: + +- urn +- customProperties +- browsePaths / browsePathsV2 +- deprecated (boolean) +- removed (boolean) +- typeNames +- name, qualifiedName +- description, hasDescription + +Timestamps: + +- lastOperationTime (datetime) +- createdAt (timestamp) +- lastModifiedAt (timestamp) + +URNs: + +- platform / platformInstance +- tags, hasTags +- glossaryTerms, hasGlossaryTerms +- domains, hasDomain +- siblings +- owners, hasOwners +- roles, hasRoles +- container + +## Catalogue Implementations + +### DataHub + +- Each table is created as a dataset in DataHub +- Tables that reside in the same athena database (data_product_v1) should + be placed within the same DataHub container. diff --git a/lib/datahub-client/data_platform_catalogue/client/__init__.py b/lib/datahub-client/data_platform_catalogue/client/__init__.py index 44d4e8c7..e69de29b 100644 --- a/lib/datahub-client/data_platform_catalogue/client/__init__.py +++ b/lib/datahub-client/data_platform_catalogue/client/__init__.py @@ -1,3 +0,0 @@ -from .base import BaseCatalogueClient # noqa: F401 -from .base import CatalogueError # noqa: F401 -from .base import ReferencedEntityMissing # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/base.py b/lib/datahub-client/data_platform_catalogue/client/base.py deleted file mode 100644 index 6afc6c1a..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/base.py +++ /dev/null @@ -1,135 +0,0 @@ -import logging -from abc import ABC, abstractmethod -from typing import Sequence - -from ..entities import ( - CatalogueMetadata, - ChartMetadata, - DataLocation, - DataProductMetadata, - TableMetadata, -) -from ..search_types import ( - MultiSelectFilter, - ResultType, - SearchFacets, - SearchResponse, - SortOption, -) - -logger = logging.getLogger(__name__) - - -class CatalogueError(Exception): - """ - Base class for all errors. - """ - - -class ReferencedEntityMissing(CatalogueError): - """ - A referenced entity (such as a user or tag) does not yet exist when - attempting to create a new metadata resource in the catalogue. - """ - - -class BaseCatalogueClient(ABC): - @abstractmethod - def upsert_database_service( - self, platform: str = "glue", display_name: str = "Data platform" - ) -> str: - pass - - @abstractmethod - def upsert_database( - self, - metadata: CatalogueMetadata | DataProductMetadata, - location: DataLocation, - ) -> str: - pass - - @abstractmethod - def upsert_schema( - self, metadata: DataProductMetadata, location: DataLocation - ) -> str: - pass - - @abstractmethod - def upsert_table( - self, - metadata: TableMetadata, - location: DataLocation, - data_product_metadata: DataProductMetadata | None = None, - ) -> str: - pass - - def search( - self, - query: str = "*", - count: int = 20, - page: str | None = None, - result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, - ResultType.TABLE, - ResultType.CHART, - ResultType.DATABASE, - ), - filters: Sequence[MultiSelectFilter] = (), - sort: SortOption | None = None, - ) -> SearchResponse: - """ - Wraps the catalogue's search function. - """ - raise NotImplementedError - - def search_facets( - self, - query: str = "*", - result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, - ResultType.TABLE, - ), - filters: Sequence[MultiSelectFilter] = (), - ) -> SearchFacets: - """ - Returns facets that can be used to filter the search results. - """ - raise NotImplementedError - - def list_data_products(self) -> SearchResponse: - return self.search(count=500, result_types=[ResultType.DATA_PRODUCT]) - - @abstractmethod - def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: - """ - returns a list of data product children - """ - pass - - @abstractmethod - def get_glossary_terms(self, count: int) -> SearchResponse: - """ - returns a searchresponse of glossary terms - """ - pass - - @abstractmethod - def get_table_details(self, urn) -> TableMetadata: - """ - returns the schema and detailed information about a table - """ - pass - - @abstractmethod - def get_chart_details(self, urn) -> ChartMetadata: - """ - returns detailed information about a chart - """ - pass - - @abstractmethod - def list_database_tables(self, urn: str, count: int) -> SearchResponse: - """ - returns a list of entities within a datahub container - """ - pass diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py b/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py deleted file mode 100644 index 8b525dc1..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .datahub_client import DataHubCatalogueClient # noqa: F401 diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py deleted file mode 100644 index 8a639f95..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/datahub_client.py +++ /dev/null @@ -1,717 +0,0 @@ -from importlib.resources import files -from typing import Sequence - -import datahub.emitter.mce_builder as mce_builder -import datahub.metadata.schema_classes as schema_classes -from datahub.configuration.common import GraphError -from datahub.emitter.mce_builder import make_data_platform_urn -from datahub.emitter.mcp import MetadataChangeProposalWrapper -from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph -from datahub.ingestion.source.common.subtypes import ( - DatasetContainerSubTypes, - DatasetSubTypes, -) -from datahub.metadata.com.linkedin.pegasus2avro.common import DataPlatformInstance -from datahub.metadata.schema_classes import ( - ChangeTypeClass, - ContainerClass, - ContainerPropertiesClass, - DataProductAssociationClass, - DataProductPropertiesClass, - DatasetPropertiesClass, - DomainPropertiesClass, - DomainsClass, - OtherSchemaClass, - SchemaFieldClass, - SchemaFieldDataTypeClass, - SchemaMetadataClass, - SubTypesClass, -) - -from ...entities import ( - CatalogueMetadata, - ChartMetadata, - DatabaseMetadata, - DataLocation, - DataProductMetadata, - RelationshipType, - TableMetadata, -) -from ...search_types import ( - MultiSelectFilter, - ResultType, - SearchFacets, - SearchResponse, - SortOption, -) -from ..base import BaseCatalogueClient, CatalogueError, logger -from .graphql_helpers import ( - parse_columns, - parse_created_and_modified, - parse_domain, - parse_owner, - parse_properties, - parse_relations, - parse_tags, -) -from .search import SearchClient - -DATAHUB_DATA_TYPE_MAPPING = { - "boolean": schema_classes.BooleanTypeClass(), - "tinyint": schema_classes.NumberTypeClass(), - "smallint": schema_classes.NumberTypeClass(), - "int": schema_classes.NumberTypeClass(), - "integer": schema_classes.NumberTypeClass(), - "bigint": schema_classes.NumberTypeClass(), - "double": schema_classes.NumberTypeClass(), - "float": schema_classes.NumberTypeClass(), - "decimal": schema_classes.NumberTypeClass(), - "char": schema_classes.StringTypeClass(), - "varchar": schema_classes.StringTypeClass(), - "string": schema_classes.StringTypeClass(), - "date": schema_classes.DateTypeClass(), - "timestamp": schema_classes.TimeTypeClass(), -} - - -class InvalidDomain(Exception): - """ - Exception thrown when a domain does not exist - """ - - -class MissingDatabaseMetadata(Exception): - """ - Exception thrown when a database is attempted to be ingested without - a given metadata specification - """ - - -class DataHubCatalogueClient(BaseCatalogueClient): - """Client for pushing metadata to the DataHub catalogue. - - Tables in the DataHub catalogue are arranged into the following hierarchy: - DataPlatform -> Dataset - - This client uses the General Metadata Service (GMS, https://datahubproject.io/docs/what/gms/) - of DataHub to create and update metadata within DataHub. This is implemented in the - python SDK as the 'python emitter' - https://datahubproject.io/docs/metadata-ingestion/as-a-library. - - If there is a problem communicating with the catalogue, methods will raise an - instance of CatalogueError. - """ - - def __init__(self, jwt_token, api_url: str, graph=None): - """Create a connection to the DataHub GMS endpoint for class methods to use. - - Args: - jwt_token: client token for interacting with the provided DataHub instance. - api_url (str, optional): GMS endpoint for the DataHub instance for the client object. - """ - if api_url.endswith("/"): - api_url = api_url[:-1] - if api_url.endswith("/api/gms") or api_url.endswith(":8080"): - self.gms_endpoint = api_url - elif api_url.endswith("/api"): - self.gms_endpoint = api_url + "/gms" - else: - raise CatalogueError("api_url is incorrectly formatted") - - self.server_config = DatahubClientConfig( - server=self.gms_endpoint, token=jwt_token - ) - self.graph = graph or DataHubGraph(self.server_config) - self.search_client = SearchClient(self.graph) - - self.dataset_query = ( - files("data_platform_catalogue.client.datahub.graphql") - .joinpath("getDatasetDetails.graphql") - .read_text() - ) - self.chart_query = ( - files("data_platform_catalogue.client.datahub.graphql") - .joinpath("getChartDetails.graphql") - .read_text() - ) - - def check_entity_exists_by_urn(self, urn: str | None): - if urn is not None: - exists = self.graph.exists(entity_urn=urn) - else: - exists = False - - return exists - - def upsert_database_service(self, platform: str = "glue", *args, **kwargs) -> str: - """ - Define a DataHub 'Data Platform'. This is a type of connection, e.g. 'hive' or 'glue'. - - Returns the fully qualified name of the metadata object in the catalogue. - """ - - raise NotImplementedError - - def upsert_database( - self, metadata: CatalogueMetadata | DataProductMetadata, location: DataLocation - ) -> str: - """ - Define a database. Not implemented for DataHub, which uses Data Platforms + Datasets only. - """ - raise NotImplementedError - - def upsert_schema( - self, metadata: DataProductMetadata, location: DataLocation - ) -> str: - """ - Define a database. Not implemented for DataHub, which uses Data Platforms + Datasets only. - """ - raise NotImplementedError - - def create_domain( - self, domain: str, description: str = "", parentDomain: str | None = None - ) -> str: - """Create a Domain, a logical collection of Data assets (Data Products). - - Args: - metadata (DataProductMetadata): _description_ - """ - domain_properties = DomainPropertiesClass( - name=domain, description=description, parentDomain=parentDomain - ) - - domain_urn = mce_builder.make_domain_urn(domain=domain) - - metadata_event = MetadataChangeProposalWrapper( - entityType="domain", - changeType=ChangeTypeClass.UPSERT, - entityUrn=domain_urn, - aspect=domain_properties, - ) - self.graph.emit(metadata_event) - - return domain_urn - - def upsert_athena_database(self, metadata: DatabaseMetadata) -> str: - """ - Define a database. Must belong to a domain - """ - metadata_dict = dict(metadata.__dict__) - metadata_dict.pop("version") - metadata_dict.pop("owner") - metadata_dict.pop("tags") - - name = metadata_dict.pop("name") - description = metadata_dict.pop("description") - - database_urn = "urn:li:container:" + "".join(name.split()) - - domain = metadata_dict.pop("domain") - domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) - - # domains are to be controlled and limited so we dont want to create one - # when it doesn't exist - domain_exists = self.check_entity_exists_by_urn(domain_urn) - if not domain_exists: - raise InvalidDomain( - f"{domain} does not exist in datahub - please align data to an existing domain" - ) - - database_domain = DomainsClass(domains=[domain_urn]) - - if domain_urn is not None: - metadata_event = MetadataChangeProposalWrapper( - entityType="container", - changeType=ChangeTypeClass.UPSERT, - entityUrn=database_urn, - aspect=database_domain, - ) - self.graph.emit(metadata_event) - logger.info(f"Database {name} associated with domain {domain}") - - database_properties = ContainerPropertiesClass( - customProperties={key: str(val) for key, val in metadata_dict.items()}, - description=description, - name=name, - ) - metadata_event = MetadataChangeProposalWrapper( - entityType="container", - changeType=ChangeTypeClass.UPSERT, - entityUrn=database_urn, - aspect=database_properties, - ) - self.graph.emit(metadata_event) - logger.info(f"Properties updated for Database {name} ") - - # set platform to athena - metadata_event = MetadataChangeProposalWrapper( - entityUrn=database_urn, - aspect=DataPlatformInstance( - platform="urn:li:dataPlatform:athena", - ), - ) - self.graph.emit(metadata_event) - logger.info(f"Platform updated for Database {name} ") - - # container type update - metadata_event = MetadataChangeProposalWrapper( - entityUrn=database_urn, - aspect=SubTypesClass(typeNames=[DatasetContainerSubTypes.DATABASE]), - ) - self.graph.emit(metadata_event) - logger.info(f"Type updated for Database {name} ") - - return database_urn - - def upsert_athena_table( - self, - metadata: TableMetadata, - database_metadata: DatabaseMetadata | None = None, - ) -> str: - if not metadata.parent_entity_name: - raise ValueError("parent_entity_name needs to be set in TableMetadata") - - fully_qualified_name = f"{metadata.parent_entity_name}.{metadata.name}" - - dataset_urn = mce_builder.make_dataset_urn( - platform="athena", name=fully_qualified_name, env="PROD" - ) - # jscpd:ignore-start - dataset_properties = DatasetPropertiesClass( - name=metadata.name, - qualifiedName=fully_qualified_name, - description=metadata.description, - customProperties={ - "sourceDatasetName": metadata.source_dataset_name, - "whereToAccessDataset": metadata.where_to_access_dataset, - "sensitivityLevel": metadata.data_sensitivity_level.name, - **( - {"rowCount": str(metadata.row_count)} - if metadata.row_count is not None - else {} - ), - }, - ) - - metadata_event = MetadataChangeProposalWrapper( - entityUrn=dataset_urn, - aspect=dataset_properties, - ) - self.graph.emit(metadata_event) - - dataset_schema_properties = SchemaMetadataClass( - schemaName=metadata.name, - platform="urn:li:dataPlatform:athena", - version=metadata.major_version, - hash="", - platformSchema=OtherSchemaClass(rawSchema=""), - fields=[ - SchemaFieldClass( - fieldPath=f"{column['name']}", - type=SchemaFieldDataTypeClass( - type=DATAHUB_DATA_TYPE_MAPPING[column["type"]] - ), - nativeDataType=column["type"], - description=column["description"], - ) - for column in metadata.column_details - ], - ) - - metadata_event = MetadataChangeProposalWrapper( - entityType="dataset", - changeType=ChangeTypeClass.UPSERT, - entityUrn=dataset_urn, - aspect=dataset_schema_properties, - ) - self.graph.emit(metadata_event) - - # set dataset type to table - metadata_event = MetadataChangeProposalWrapper( - entityUrn=dataset_urn, - aspect=SubTypesClass(typeNames=[DatasetSubTypes.TABLE]), - ) - - self.graph.emit(metadata_event) - - if metadata.tags: - tags_to_add = mce_builder.make_global_tag_aspect_with_tag_list( - tags=metadata.tags - ) - event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( - entityType="dataset", - changeType=ChangeTypeClass.UPSERT, - entityUrn=dataset_urn, - aspect=tags_to_add, - ) - self.graph.emit(event) - # jscpd:ignore-end - - database_urn = "urn:li:container:" + "".join( - metadata.parent_entity_name.split() - ) - - database_exists = self.check_entity_exists_by_urn(urn=database_urn) - - if not database_exists and database_metadata: - database_urn = self.upsert_athena_database(metadata=database_metadata) - domain_urn = self.graph.get_domain_urn_by_name( - domain_name=database_metadata.domain - ) - elif not database_exists and not database_metadata: - raise MissingDatabaseMetadata( - "DatabaseMetadata object needs to be passed in to create a database" - ) - - # add dataset to database - metadata_event = MetadataChangeProposalWrapper( - entityUrn=dataset_urn, - aspect=ContainerClass(container=database_urn), - ) - - self.graph.emit(metadata_event) - - # add domain to table - tables inherit the domain of parent database if not given - if metadata.domain: - domain_urn = self.graph.get_domain_urn_by_name(domain_name=metadata.domain) - else: - domain_urn = self.graph.get_aspect( - entity_urn=database_urn, aspect_type=DomainsClass - ).domains[0] - - domain_exists = self.check_entity_exists_by_urn(domain_urn) - if not domain_exists: - raise InvalidDomain( - f"{metadata.domain} does not exist in datahub - please align data to an existing domain" - ) - - if domain_urn is not None: - table_domain = DomainsClass(domains=[domain_urn]) - metadata_event = MetadataChangeProposalWrapper( - entityType="dataset", - changeType=ChangeTypeClass.UPSERT, - entityUrn=dataset_urn, - aspect=table_domain, - ) - - self.graph.emit(metadata_event) - - return dataset_urn - - def upsert_data_product(self, metadata: DataProductMetadata): - """ - Define a data product. Must belong to a domain - """ - metadata_dict = dict(metadata.__dict__) - metadata_dict.pop("version") - metadata_dict.pop("owner") - metadata_dict.pop("tags") - - name = metadata_dict.pop("name") - description = metadata_dict.pop("description") - - data_product_urn = "urn:li:dataProduct:" + "".join(name.split()) - - domain = metadata_dict.pop("domain") - domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain) - - if domain_urn is None: - logger.info(f"creating new domain {domain} for {name}") - domain_urn = self.create_domain(domain=domain) - - subdomain_urn = None - if metadata.subdomain: - subdomain = metadata_dict.pop("subdomain") - subdomain_urn = self.graph.get_domain_urn_by_name(domain_name=subdomain) - - if subdomain_urn is None: - logger.info(f"creating new subdomain {domain} for {name}") - domain_urn = self.create_domain( - domain=subdomain, parentDomain=domain_urn - ) - - data_product_domain = DomainsClass( - domains=[subdomain_urn if subdomain_urn else domain_urn] - ) - metadata_event = MetadataChangeProposalWrapper( - entityType="dataproduct", - changeType=ChangeTypeClass.UPSERT, - entityUrn=data_product_urn, - aspect=data_product_domain, - ) - self.graph.emit(metadata_event) - logger.info(f"Data Product {name} associated with domain {domain}") - - data_product_properties = DataProductPropertiesClass( - customProperties={key: str(val) for key, val in metadata_dict.items()}, - description=description, - name=name, - ) - metadata_event = MetadataChangeProposalWrapper( - entityType="dataproduct", - changeType=ChangeTypeClass.UPSERT, - entityUrn=data_product_urn, - aspect=data_product_properties, - ) - self.graph.emit(metadata_event) - logger.info(f"Properties updated for Data Product {name} ") - - return data_product_urn - - def upsert_table( - self, - metadata: TableMetadata, - location: DataLocation, - data_product_metadata: DataProductMetadata | None = None, - ) -> str: - """ - Define a table (a 'dataset' in DataHub parlance), a 'collection of data' - (https://datahubproject.io/docs/metadata-modeling/metadata-model#the-core-entities). - - There can be many tables per Data Product. - - Columns are expected to be a list of dicts in the format - {"name": "column1", "type": "string", "description": "just an example"} - - This method creates a schemaMetadata aspect object from the metadata object - (https://datahubproject.io/docs/generated/metamodel/entities/dataset/#schemametadata) - together with generating a unique reference name (URN) for the dataset used by DataHub. - These are then emitted to the rest.li api as a dataset creation/update event proposal. - - If tags are present in the metadata object, a second request is made to update the dataset - with these tags as a separate aspect. - - Args: - metadata (TableMetadata): metadata object. - platform (str, optional): DataHub data platform type. Defaults to "glue". - version (int, optional): Defaults to 1. - - Returns: - dataset_urn: the dataset URN - """ - if location.fully_qualified_name: - name = f"{location.fully_qualified_name}.{metadata.name}" - else: - name = metadata.name - - dataset_urn = mce_builder.make_dataset_urn( - platform=location.platform_id, name=name, env="PROD" - ) - - dataset_properties = DatasetPropertiesClass( - name=metadata.name, - qualifiedName=name, - description=metadata.description, - customProperties={ - "sourceDatasetName": metadata.source_dataset_name, - "whereToAccessDataset": metadata.where_to_access_dataset, - "sensitivityLevel": metadata.data_sensitivity_level.name, - "rowCount": "1177", - }, - ) - - metadata_event = MetadataChangeProposalWrapper( - entityUrn=dataset_urn, - aspect=dataset_properties, - ) - self.graph.emit(metadata_event) - - dataset_schema_properties = SchemaMetadataClass( - schemaName=metadata.name, - platform=make_data_platform_urn(platform=location.platform_id), - version=metadata.major_version, - hash="", - platformSchema=OtherSchemaClass(rawSchema=""), - fields=[ - SchemaFieldClass( - fieldPath=f"{column['name']}", - type=SchemaFieldDataTypeClass( - type=DATAHUB_DATA_TYPE_MAPPING[column["type"]] - ), - nativeDataType=column["type"], - description=column["description"], - ) - for column in metadata.column_details - ], - ) - - metadata_event = MetadataChangeProposalWrapper( - entityType="dataset", - changeType=ChangeTypeClass.UPSERT, - entityUrn=dataset_urn, - aspect=dataset_schema_properties, - ) - self.graph.emit(metadata_event) - - if metadata.tags: - tags_to_add = mce_builder.make_global_tag_aspect_with_tag_list( - tags=metadata.tags - ) - event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( - entityType="dataset", - changeType=ChangeTypeClass.UPSERT, - entityUrn=dataset_urn, - aspect=tags_to_add, - ) - self.graph.emit(event) - - if data_product_metadata is not None: - data_product_urn = "urn:li:dataProduct:" + "".join( - data_product_metadata.name.split() - ) - data_product_exists = self.graph.exists(entity_urn=data_product_urn) - - if not data_product_exists: - data_product_urn = self.upsert_data_product( - metadata=data_product_metadata - ) - - data_product_existing_properties = self.graph.get_aspect( - entity_urn=data_product_urn, aspect_type=DataProductPropertiesClass - ) - - data_product_association = DataProductAssociationClass( - destinationUrn=dataset_urn, sourceUrn=data_product_urn - ) - - if ( - data_product_existing_properties is not None - and data_product_existing_properties.assets is not None - ): - assets = data_product_existing_properties.assets[::] - if data_product_association not in assets: - assets.append(data_product_association) - else: - assets = [data_product_association] - - data_product_properties = DataProductPropertiesClass( - description=data_product_existing_properties.description, - name=data_product_existing_properties.name, - customProperties=data_product_existing_properties.customProperties, - assets=assets, - ) - - metadata_event = MetadataChangeProposalWrapper( - entityType="dataproduct", - changeType=ChangeTypeClass.UPSERT, - entityUrn=data_product_urn, - aspect=data_product_properties, - ) - self.graph.emit(metadata_event) - - return dataset_urn - - def search( - self, - query: str = "*", - count: int = 20, - page: str | None = None, - result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, - ResultType.TABLE, - ResultType.CHART, - ResultType.DATABASE, - ), - filters: Sequence[MultiSelectFilter] = (), - sort: SortOption | None = None, - ) -> SearchResponse: - """ - Wraps the catalogue's search function. - """ - return self.search_client.search( - query=query, - count=count, - page=page, - result_types=result_types, - filters=filters, - sort=sort, - ) - - def search_facets( - self, - query: str = "*", - result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, - ResultType.TABLE, - ResultType.CHART, - ), - filters: Sequence[MultiSelectFilter] = (), - ) -> SearchFacets: - """ - Returns facets that can be used to filter the search results. - """ - return self.search_client.search_facets( - query=query, result_types=result_types, filters=filters - ) - - def list_data_product_assets(self, urn, count, start=0) -> SearchResponse: - """ - returns a list of data product children - """ - return self.search_client.list_data_product_assets( - urn=urn, count=count, start=start - ) - - def get_glossary_terms(self, count: int = 1000) -> SearchResponse: - """Wraps the client's glossary terms query""" - return self.search_client.get_glossary_terms(count) - - def get_table_details(self, urn) -> TableMetadata: - try: - response = self.graph.execute_graphql(self.dataset_query, {"urn": urn})[ - "dataset" - ] - properties, custom_properties = parse_properties(response) - columns = parse_columns(response) - domain = parse_domain(response) - owner, owner_email = parse_owner(response) - tags = parse_tags(response) - name = properties.get("name", response.get("name")) - created, modified = parse_created_and_modified(properties) - - # A dataset can't have both a container and data product parent, but if we did - # start to use in that we'd need to change this - if response["container_relations"]["total"] > 0: - relations = parse_relations( - RelationshipType.PARENT, response["container_relations"] - ) - elif response["data_product_relations"]["total"] > 0: - relations = parse_relations( - RelationshipType.PARENT, response["data_product_relations"] - ) - else: - relations = {} - return TableMetadata( - name=name, - fully_qualified_name=properties.get("qualifiedName") or name, - description=properties.get("description", ""), - column_details=columns, - retention_period_in_days=custom_properties.get("retentionPeriodInDays"), - relationships=relations, - domain=domain["domain_name"], - tags=tags, - owner=owner, - owner_email=owner_email, - first_created=created, - last_updated=modified, - ) - except GraphError as e: - raise Exception("Unable to execute getDataset query") from e - - def get_chart_details(self, urn) -> ChartMetadata: - try: - response = self.graph.execute_graphql(self.chart_query, {"urn": urn})[ - "chart" - ] - properties, custom_properties = parse_properties(response) - - return ChartMetadata( - name=properties["name"], - description=properties.get("description", ""), - external_url=properties["externalUrl"], - ) - except GraphError as e: - raise Exception("Unable to execute getDataset query") from e - - def list_database_tables(self, urn: str, count: int) -> SearchResponse: - """Wraps the client's listDatabaseEntities query""" - return self.search_client.list_database_tables(urn=urn, count=count) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql b/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql deleted file mode 100644 index 6146c282..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listDataProductAssets.graphql +++ /dev/null @@ -1,106 +0,0 @@ -query dataProductDetails( - $urn: String! - $query: String! - $count: Int! - $start: Int! -) { - listDataProductAssets( - urn: $urn - input: { query: $query, start: $start, count: $count } - ) { - start - count - total - searchResults { - entity { - type - ... on Dataset { - urn - type - platform { - name - } - relationships( - input: { - types: ["DataProductContains"] - direction: INCOMING - count: 10 - } - ) { - total - relationships { - entity { - urn - ... on DataProduct { - properties { - name - } - } - } - } - } - ownership { - owners { - owner { - ... on CorpUser { - urn - properties { - fullName - email - } - } - ... on CorpGroup { - urn - properties { - displayName - email - } - } - } - } - } - name - properties { - name - qualifiedName - description - customProperties { - key - value - } - created - lastModified { - time - actor - } - } - editableProperties { - description - } - tags { - tags { - tag { - urn - properties { - name - description - } - } - } - } - lastIngested - domain { - domain { - urn - id - properties { - name - description - } - } - } - } - } - } - } -} diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py new file mode 100644 index 00000000..3d41522f --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py @@ -0,0 +1,537 @@ +import json +import logging +from importlib.resources import files +from typing import Sequence + +from data_platform_catalogue.client.exceptions import ( + AspectDoesNotExist, + CatalogueError, + EntityDoesNotExist, + InvalidDomain, + ReferencedEntityMissing, +) +from data_platform_catalogue.client.graphql_helpers import ( + parse_columns, + parse_created_and_modified, + parse_domain, + parse_owner, + parse_properties, + parse_relations, + parse_tags, +) +from data_platform_catalogue.client.search import SearchClient +from data_platform_catalogue.entities import ( + Chart, + CustomEntityProperties, + Database, + EntityRef, + Governance, + OwnerRef, + RelationshipType, + Table, +) +from data_platform_catalogue.search_types import ( + MultiSelectFilter, + ResultType, + SearchFacets, + SearchResponse, + SortOption, +) +from datahub.emitter import mce_builder +from datahub.emitter.mcp import MetadataChangeProposalWrapper +from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph +from datahub.ingestion.source.common.subtypes import ( + DatasetContainerSubTypes, + DatasetSubTypes, +) +from datahub.metadata import schema_classes +from datahub.metadata.com.linkedin.pegasus2avro.common import DataPlatformInstance +from datahub.metadata.schema_classes import ( + ChangeTypeClass, + ContainerClass, + ContainerPropertiesClass, + DatasetPropertiesClass, + DomainPropertiesClass, + DomainsClass, + OtherSchemaClass, + SchemaFieldClass, + SchemaFieldDataTypeClass, + SchemaMetadataClass, + SubTypesClass, +) + +logger = logging.getLogger(__name__) + + +DATAHUB_DATA_TYPE_MAPPING = { + "boolean": schema_classes.BooleanTypeClass(), + "tinyint": schema_classes.NumberTypeClass(), + "smallint": schema_classes.NumberTypeClass(), + "int": schema_classes.NumberTypeClass(), + "integer": schema_classes.NumberTypeClass(), + "bigint": schema_classes.NumberTypeClass(), + "double": schema_classes.NumberTypeClass(), + "float": schema_classes.NumberTypeClass(), + "decimal": schema_classes.NumberTypeClass(), + "char": schema_classes.StringTypeClass(), + "varchar": schema_classes.StringTypeClass(), + "string": schema_classes.StringTypeClass(), + "date": schema_classes.DateTypeClass(), + "timestamp": schema_classes.TimeTypeClass(), +} + + +class DataHubCatalogueClient: + """Client for pushing metadata to the DataHub catalogue. + + Tables in the DataHub catalogue are arranged into the following hierarchy: + DataPlatform -> Dataset + + This client uses the General Metadata Service (GMS, https://datahubproject.io/docs/what/gms/) + of DataHub to create and update metadata within DataHub. This is implemented in the + python SDK as the 'python emitter' - https://datahubproject.io/docs/metadata-ingestion/as-a-library. + + If there is a problem communicating with the catalogue, methods will raise an + instance of CatalogueError. + """ + + def __init__(self, jwt_token, api_url: str, graph=None): + """Create a connection to the DataHub GMS endpoint for class methods to use. + + Args: + jwt_token: client token for interacting with the provided DataHub instance. + api_url (str, optional): GMS endpoint for the DataHub instance for the client object. + """ + if api_url.endswith("/"): + api_url = api_url[:-1] + if api_url.endswith("/api/gms") or api_url.endswith(":8080"): + self.gms_endpoint = api_url + elif api_url.endswith("/api"): + self.gms_endpoint = api_url + "/gms" + else: + raise CatalogueError("api_url is incorrectly formatted") + + self.server_config = DatahubClientConfig( + server=self.gms_endpoint, token=jwt_token + ) + self.graph = graph or DataHubGraph(self.server_config) + self.search_client = SearchClient(self.graph) + + self.dataset_query = ( + files("data_platform_catalogue.client.graphql") + .joinpath("getDatasetDetails.graphql") + .read_text() + ) + self.chart_query = ( + files("data_platform_catalogue.client.graphql") + .joinpath("getChartDetails.graphql") + .read_text() + ) + + def check_entity_exists_by_urn(self, urn: str | None): + if urn is not None: + exists = self.graph.exists(entity_urn=urn) + else: + exists = False + + return exists + + def create_domain( + self, domain: str, description: str = "", parent_domain: str | None = None + ) -> str: + """Create a Domain, a logical collection of entities + + Args: + domain (str): name of the new Domain + description (str, optional): Description of the new Domain. Defaults to "". + parent_domain (str | None, optional): Declared child relationship to existing Domains. + Defaults to None. + + Returns: + str: urn of the created Domain + """ + domain_properties = DomainPropertiesClass( + name=domain, description=description, parentDomain=parent_domain + ) + + domain_urn = mce_builder.make_domain_urn(domain=domain) + + metadata_event = MetadataChangeProposalWrapper( + entityType="domain", + changeType=ChangeTypeClass.UPSERT, + entityUrn=domain_urn, + aspect=domain_properties, + ) + self.graph.emit(metadata_event) + + return domain_urn + + def search( + self, + query: str = "*", + count: int = 20, + page: str | None = None, + result_types: Sequence[ResultType] = ( + ResultType.TABLE, + ResultType.CHART, + ResultType.DATABASE, + ), + filters: Sequence[MultiSelectFilter] = (), + sort: SortOption | None = None, + ) -> SearchResponse: + """ + Wraps the catalogue's search function. + """ + return self.search_client.search( + query=query, + count=count, + page=page, + result_types=result_types, + filters=filters, + sort=sort, + ) + + def search_facets( + self, + query: str = "*", + result_types: Sequence[ResultType] = ( + ResultType.TABLE, + ResultType.CHART, + ), + filters: Sequence[MultiSelectFilter] = (), + ) -> SearchFacets: + """ + Returns facets that can be used to filter the search results. + """ + return self.search_client.search_facets( + query=query, result_types=result_types, filters=filters + ) + + def get_glossary_terms(self, count: int = 1000) -> SearchResponse: + """Wraps the client's glossary terms query""" + return self.search_client.get_glossary_terms(count) + + def get_table_details(self, urn) -> Table: + if self.check_entity_exists_by_urn(urn): + response = self.graph.execute_graphql(self.dataset_query, {"urn": urn})[ + "dataset" + ] + platform_name = response["platform"]["name"] + properties, custom_properties = parse_properties(response) + columns = parse_columns(response) + domain = parse_domain(response) + owner = parse_owner(response) + tags = parse_tags(response) + name = properties.get("name", response.get("name")) + created, modified = parse_created_and_modified(properties) + + # A dataset can't have multiple parents, but if we did + # start to use in that we'd need to change this + if response["container_relations"]["total"] > 0: + relations = parse_relations( + RelationshipType.PARENT, response["container_relations"] + ) + else: + relations = {} + return Table( + urn=None, + display_name=properties.get("qualifiedName") or name, + name=name, + fully_qualified_name=properties.get("qualifiedName") or name, + description=properties.get("description", ""), + relationships=relations, + domain=domain, + governance=Governance( + data_owner=owner, + data_stewards=[owner], + ), + tags=tags, + last_modified=modified, + created=created, + column_details=columns, + custom_properties=custom_properties, + platform=EntityRef(display_name=platform_name, urn=platform_name), + ) + raise EntityDoesNotExist(f"Table with urn: {urn} does not exist") + + def get_chart_details(self, urn) -> Chart: + if self.check_entity_exists_by_urn(urn): + response = self.graph.execute_graphql(self.chart_query, {"urn": urn})[ + "chart" + ] + platform_name = response["platform"]["name"] + properties, custom_properties = parse_properties(response) + domain = parse_domain(response) + owner = parse_owner(response) + tags = parse_tags(response) + + return Chart( + urn=urn, + external_url=properties.get("externalUrl", ""), + description=properties.get("description", ""), + display_name=properties.get("displayName", properties.get("name")), + name=properties["name"], + fully_qualified_name=properties.get( + "qualifiedName", properties.get("name") + ), + domain=domain, + governance=Governance( + data_owner=owner, + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=tags, + platform=EntityRef(display_name=platform_name, urn=platform_name), + custom_properties=custom_properties, + ) + + raise EntityDoesNotExist(f"Chart with urn: {urn} does not exist") + + # to expand on and replace `list_database_tables` will need new graphql query i expect + # but will be more equivelent to the get chart and table details methods. + def get_database_details(self, urn: str) -> Database: + raise NotImplementedError + + def upsert_table(self, table: Table) -> str: + """Define a table. Must belong to a domain.""" + parent_relationships = table.relationships.get(RelationshipType.PARENT, []) + if not parent_relationships: + raise ValueError("A parent entity needs to be included in relationships") + + parent_name = table.relationships[RelationshipType.PARENT][0].display_name + fully_qualified_name = generate_fqn( + parent_name=parent_name, dataset_name=table.name + ) + + dataset_urn = mce_builder.make_dataset_urn( + platform=table.platform.display_name, + name=fully_qualified_name, + env="PROD", + ) + # jscpd:ignore-start + dataset_properties = DatasetPropertiesClass( + name=table.name, + qualifiedName=fully_qualified_name, + description=table.description, + customProperties=self._get_custom_property_key_value_pairs( + table.custom_properties + ), + ) + + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=dataset_properties, + ) + self.graph.emit(metadata_event) + + dataset_schema_properties = SchemaMetadataClass( + schemaName=table.name, + platform=table.platform.urn, + version=1, + hash="", + platformSchema=OtherSchemaClass(rawSchema=""), + fields=[ + SchemaFieldClass( + fieldPath=f"{column.name}", + type=SchemaFieldDataTypeClass( + type=DATAHUB_DATA_TYPE_MAPPING[column.type] # type: ignore + ), + nativeDataType=column.type, + description=column.description, + ) + for column in table.column_details + ], + ) + + metadata_event = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=dataset_schema_properties, + ) + self.graph.emit(metadata_event) + + # set dataset type to table + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=SubTypesClass(typeNames=[DatasetSubTypes.TABLE]), + ) + + self.graph.emit(metadata_event) + + if table.tags: + tags_to_add = mce_builder.make_global_tag_aspect_with_tag_list( + tags=[str(tag.display_name) for tag in table.tags] + ) + event: MetadataChangeProposalWrapper = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=tags_to_add, + ) + self.graph.emit(event) + # jscpd:ignore-end + + database_urn = table.relationships[RelationshipType.PARENT][0].urn + + database_exists = self.check_entity_exists_by_urn(urn=database_urn) + + if not database_exists: + raise ReferencedEntityMissing( + f"Database referenced by urn {database_urn} does not exist" + ) + + # add dataset to database + metadata_event = MetadataChangeProposalWrapper( + entityUrn=dataset_urn, + aspect=ContainerClass(container=database_urn), + ) + + self.graph.emit(metadata_event) + + # add domain to table - tables inherit the domain of parent database if not given + if table.domain: + domain_urn = self.graph.get_domain_urn_by_name( + domain_name=table.domain.display_name + ) + else: + domain_aspect = self.graph.get_aspect( + entity_urn=database_urn, aspect_type=DomainsClass + ) + if not domain_aspect: + raise AspectDoesNotExist( + f"Aspect `domains` does not exist for entity with urn {database_urn}" + ) + domain_urn = domain_aspect.domains[0] + + domain_exists = self.check_entity_exists_by_urn(domain_urn) + if not domain_exists: + raise InvalidDomain( + f"{table.domain} does not exist in datahub - please align data to an existing domain" + ) + + if domain_urn is not None: + table_domain = DomainsClass(domains=[domain_urn]) + metadata_event = MetadataChangeProposalWrapper( + entityType="dataset", + changeType=ChangeTypeClass.UPSERT, + entityUrn=dataset_urn, + aspect=table_domain, + ) + + self.graph.emit(metadata_event) + + return dataset_urn + + def upsert_chart(self, chart: Chart) -> str: + raise NotImplementedError + + def upsert_database(self, database: Database): + """ + Define a database. Must belong to a domain + """ + name = database.name + description = database.description + + database_urn = "urn:li:container:" + "".join(name.split()) + + domain = database.domain + domain_urn = self.graph.get_domain_urn_by_name(domain_name=domain.display_name) + + # domains are to be controlled and limited so we dont want to create one + # when it doesn't exist + if not self.check_entity_exists_by_urn(domain_urn): + raise InvalidDomain( + f"{domain} does not exist in datahub - please align data to an existing domain" + ) + + database_domain = DomainsClass(domains=[domain_urn]) # type: ignore + + metadata_event = MetadataChangeProposalWrapper( + entityType="container", + changeType=ChangeTypeClass.UPSERT, + entityUrn=database_urn, + aspect=database_domain, + ) + self.graph.emit(metadata_event) + logger.info(f"Database {name} associated with domain {domain}") + + database_properties = ContainerPropertiesClass( + customProperties=self._get_custom_property_key_value_pairs( + database.custom_properties + ), + description=description, + name=name, + ) + metadata_event = MetadataChangeProposalWrapper( + entityType="container", + changeType=ChangeTypeClass.UPSERT, + entityUrn=database_urn, + aspect=database_properties, + ) + self.graph.emit(metadata_event) + logger.info(f"Properties updated for Database {name} ") + + # set platform + metadata_event = MetadataChangeProposalWrapper( + entityUrn=database_urn, + aspect=DataPlatformInstance( + platform=database.platform.urn, + ), + ) + self.graph.emit(metadata_event) + logger.info(f"Platform updated for Database {name} ") + + # container type update + metadata_event = MetadataChangeProposalWrapper( + entityUrn=database_urn, + aspect=SubTypesClass(typeNames=[DatasetContainerSubTypes.DATABASE]), + ) + self.graph.emit(metadata_event) + logger.info(f"Type updated for Database {name} ") + + return database_urn + + def list_database_tables(self, urn: str, count: int) -> SearchResponse: + """Wraps the client's listDatabaseEntities query""" + return self.search_client.list_database_tables(urn=urn, count=count) + + def _get_custom_property_key_value_pairs( + self, + custom_properties: CustomEntityProperties, + ) -> dict: + """ + get each custom property as an unnested key/value pair. + we cannot push nested structures to datahub custom properties + """ + custom_properties_dict = json.loads( + custom_properties.model_dump_json(), parse_int=str + ) + custom_properties_unnested = self._flatten_dict(custom_properties_dict) + custom_properties_unnested_all_string_values = { + key: str(value) + # if value is not None else value + for key, value in custom_properties_unnested.items() + } + + return custom_properties_unnested_all_string_values + + def _flatten_dict(self, d, custom_properties=None): + if custom_properties is None: + custom_properties = {} + for key, value in d.items(): + if isinstance(value, dict): + self._flatten_dict(dict(value.items()), custom_properties) + else: + custom_properties[key] = value + return custom_properties + + +def generate_fqn(parent_name, dataset_name) -> str: + """ + Generate a fully qualified name for a dataset + """ + return f"{parent_name}.{dataset_name}" diff --git a/lib/datahub-client/data_platform_catalogue/client/exceptions.py b/lib/datahub-client/data_platform_catalogue/client/exceptions.py new file mode 100644 index 00000000..c58ab218 --- /dev/null +++ b/lib/datahub-client/data_platform_catalogue/client/exceptions.py @@ -0,0 +1,29 @@ +class CatalogueError(Exception): + """ + Base class for catalogue-related errors. + """ + + +class ReferencedEntityMissing(CatalogueError): + """ + A referenced entity (such as a user or tag) does not yet exist when + attempting to create a new metadata resource in the catalogue. + """ + + +class InvalidDomain(CatalogueError): + """ + Domain does not exist + """ + + +class MissingDatabaseMetadata(Exception): + """""" + + +class EntityDoesNotExist(CatalogueError): + """""" + + +class AspectDoesNotExist(CatalogueError): + """""" diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/__init__.py b/lib/datahub-client/data_platform_catalogue/client/graphql/__init__.py similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/__init__.py rename to lib/datahub-client/data_platform_catalogue/client/graphql/__init__.py diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/facets.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/facets.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/facets.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/getChartDetails.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getChartDetails.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/getChartDetails.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/getDatasetDetails.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getDatasetDetails.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/getDatasetDetails.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/getGlossaryTerms.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/getGlossaryTerms.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/getGlossaryTerms.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/listContainerEntities.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/listContainerEntities.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/listContainerEntities.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql b/lib/datahub-client/data_platform_catalogue/client/graphql/search.graphql similarity index 100% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql/search.graphql rename to lib/datahub-client/data_platform_catalogue/client/graphql/search.graphql diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py similarity index 58% rename from lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py rename to lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py index ee25ccdc..11ee28fc 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py @@ -2,27 +2,42 @@ from datetime import datetime, timezone from typing import Any, Tuple -from data_platform_catalogue.entities import RelatedEntity, RelationshipType - - -def parse_owner(entity: dict[str, Any]): +from data_platform_catalogue.entities import ( + AccessInformation, + Column, + ColumnRef, + CustomEntityProperties, + DataSummary, + DomainRef, + EntityRef, + OwnerRef, + RelationshipType, + TagRef, + UsageRestrictions, +) + + +def parse_owner(entity: dict[str, Any]) -> OwnerRef: """ Parse ownership information, if it is set. + If no owner information exists, return an OwnerRef populated with blank strings """ ownership = entity.get("ownership") or {} owners = [i["owner"] for i in ownership.get("owners", [])] if owners: properties = owners[0].get("properties") or {} - owner_email = properties.get("email", "") - owner_name = properties.get("fullName", properties.get("displayName", "")) + owner_details = OwnerRef( + display_name=properties.get("displayName", properties.get("fullName", "")), + email=properties.get("email", ""), + urn=owners[0].get("urn", ""), + ) else: - owner_email = "" - owner_name = "" + owner_details = OwnerRef(display_name="", email="", urn="") - return owner_email, owner_name + return owner_details -def parse_last_updated(entity: dict[str, Any]) -> datetime | None: +def parse_last_modified(entity: dict[str, Any]) -> datetime | None: """ Parse the last updated timestamp, if available """ @@ -46,7 +61,7 @@ def parse_created_and_modified( return created, modified -def parse_tags(entity: dict[str, Any]) -> list[str]: +def parse_tags(entity: dict[str, Any]) -> list[TagRef]: """ Parse tag information into a flat list of strings for displaying as part of the search result. @@ -54,39 +69,57 @@ def parse_tags(entity: dict[str, Any]) -> list[str]: outer_tags = entity.get("tags") or {} tags = [] for tag in outer_tags.get("tags", []): - properties = tag["tag"]["properties"] + properties = tag.get("tag", {}).get("properties", {}) if properties: - tags.append(properties["name"]) + tags.append( + TagRef( + display_name=properties.get("name", ""), + urn=tag.get("tag", {}).get("urn", ""), + ) + ) return tags -def parse_properties(entity: dict[str, Any]) -> Tuple[dict[str, Any], dict[str, Any]]: +def parse_properties( + entity: dict[str, Any] +) -> Tuple[dict[str, Any], CustomEntityProperties]: """ Parse properties and editableProperties into a single dictionary. """ properties = entity["properties"] or {} editable_properties = entity.get("editableProperties") or {} properties.update(editable_properties) - custom_properties = { + custom_properties_dict = { i["key"]: i["value"] for i in properties.get("customProperties", []) } + properties.pop("customProperties", None) + access_information = AccessInformation.model_validate(custom_properties_dict) + usage_restrictions = UsageRestrictions.model_validate(custom_properties_dict) + data_summary = DataSummary.model_validate(custom_properties_dict) + + custom_properties = CustomEntityProperties( + access_information=access_information, + usage_restrictions=usage_restrictions, + data_summary=data_summary, + ) + return properties, custom_properties -def parse_domain(entity: dict[str, Any]): - metadata = {} +def parse_domain(entity: dict[str, Any]) -> DomainRef: domain = entity.get("domain") or {} inner_domain = domain.get("domain") or {} - metadata["domain_id"] = inner_domain.get("urn", "") + domain_id = inner_domain.get("urn", "") if inner_domain: domain_properties, _ = parse_properties(inner_domain) - metadata["domain_name"] = domain_properties.get("name", "") + display_name = domain_properties.get("name", "") else: - metadata["domain_name"] = "" - return metadata + display_name = "" + return DomainRef(display_name=display_name, urn=domain_id) -def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: + +def parse_columns(entity: dict[str, Any]) -> list[Column]: """ Parse the schema metadata from Datahub into a flattened list of column information. @@ -119,14 +152,15 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: source_path = foreign_key["sourceFields"][0]["fieldPath"] foreign_path = foreign_key["foreignFields"][0]["fieldPath"] - foreign_table_id = foreign_key["foreignDataset"]["urn"] - foreign_table_name = foreign_key["foreignDataset"]["properties"]["name"] + + foreign_table = EntityRef( + urn=foreign_key["foreignDataset"]["urn"], + display_name=foreign_key["foreignDataset"]["properties"]["name"], + ) + + display_name = foreign_path.split(".")[-1] foreign_keys[source_path].append( - { - "tableId": foreign_table_id, - "fieldName": foreign_path, - "tableName": foreign_table_name, - } + ColumnRef(name=foreign_path, display_name=display_name, table=foreign_table) ) for field in schema_metadata.get("fields", ()): @@ -136,25 +170,28 @@ def parse_columns(entity: dict[str, Any]) -> list[dict[str, Any]]: # This is an oversimplification: in the case of a composite # primary key, we report that each component field is primary. is_primary_key = field["fieldPath"] in primary_keys + field_path = field["fieldPath"] + display_name = field_path.split(".")[-1] result.append( - { - "name": field["fieldPath"], - "description": field["description"], - "type": field.get("nativeDataType", field["type"]), - "nullable": field["nullable"], - "isPrimaryKey": is_primary_key, - "foreignKeys": foreign_keys_for_field, - } + Column( + name=field_path, + display_name=display_name, + description=field["description"], + type=field.get("nativeDataType", field["type"]), + nullable=field["nullable"], + is_primary_key=is_primary_key, + foreign_keys=foreign_keys_for_field, + ) ) # Sort primary keys first, then sort alphabetically - return sorted(result, key=lambda c: (0 if c["isPrimaryKey"] else 1, c["name"])) + return sorted(result, key=lambda c: (0 if c.is_primary_key else 1, c.name)) def parse_relations( relationship_type: RelationshipType, relations_dict: dict -) -> dict[RelationshipType, list[RelatedEntity]]: +) -> dict[RelationshipType, list[EntityRef]]: """ parse the relationships results returned from a graphql querys """ @@ -163,7 +200,9 @@ def parse_relations( # total_relations = relations_dict.get("total", 0) parent_entities = relations_dict.get("relationships", []) related_entities = [ - RelatedEntity(id=i["entity"]["urn"], name=i["entity"]["properties"]["name"]) + EntityRef( + urn=i["entity"]["urn"], display_name=i["entity"]["properties"]["name"] + ) for i in parent_entities ] diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py b/lib/datahub-client/data_platform_catalogue/client/search.py similarity index 60% rename from lib/datahub-client/data_platform_catalogue/client/datahub/search.py rename to lib/datahub-client/data_platform_catalogue/client/search.py index 57428018..a2488392 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/search.py @@ -3,28 +3,30 @@ from importlib.resources import files from typing import Any, Sequence -from data_platform_catalogue.entities import RelationshipType -from datahub.configuration.common import GraphError -from datahub.ingestion.graph.client import DataHubGraph - -from ...search_types import ( - FacetOption, - MultiSelectFilter, - ResultType, - SearchFacets, - SearchResponse, - SearchResult, - SortOption, +from data_platform_catalogue.client.exceptions import ( # pylint: disable=E0611 + CatalogueError, ) -from .graphql_helpers import ( +from data_platform_catalogue.client.graphql_helpers import ( parse_created_and_modified, parse_domain, - parse_last_updated, + parse_last_modified, parse_owner, parse_properties, parse_relations, parse_tags, ) +from data_platform_catalogue.entities import RelationshipType # pylint: disable=E0611 +from data_platform_catalogue.search_types import ( + FacetOption, + MultiSelectFilter, + ResultType, + SearchFacets, + SearchResponse, + SearchResult, + SortOption, +) +from datahub.configuration.common import GraphError # pylint: disable=E0611 +from datahub.ingestion.graph.client import DataHubGraph # pylint: disable=E0611 logger = logging.getLogger(__name__) @@ -33,29 +35,23 @@ class SearchClient: def __init__(self, graph: DataHubGraph): self.graph = graph self.search_query = ( - files("data_platform_catalogue.client.datahub.graphql") + files("data_platform_catalogue.client.graphql") .joinpath("search.graphql") .read_text() ) self.facets_query = ( - files("data_platform_catalogue.client.datahub.graphql") + files("data_platform_catalogue.client.graphql") .joinpath("facets.graphql") .read_text() ) - - self.data_product_asset_query = ( - files("data_platform_catalogue.client.datahub.graphql") - .joinpath("listDataProductAssets.graphql") - .read_text() - ) self.get_glossary_terms_query = ( - files("data_platform_catalogue.client.datahub.graphql") + files("data_platform_catalogue.client.graphql") .joinpath("getGlossaryTerms.graphql") .read_text() ) self.get_database_tables_query = ( - files("data_platform_catalogue.client.datahub.graphql") + files("data_platform_catalogue.client.graphql") .joinpath("listContainerEntities.graphql") .read_text() ) @@ -66,7 +62,6 @@ def search( count: int = 20, page: str | None = None, result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, ResultType.TABLE, ResultType.CHART, ResultType.DATABASE, @@ -77,10 +72,8 @@ def search( """ Wraps the catalogue's search function. """ - if page is None: - start = 0 - else: - start = int(page) * count + + start = 0 if page is None else int(page) * count types = self._map_result_types(result_types) formatted_filters = self._map_filters(filters) @@ -99,33 +92,43 @@ def search( try: response = self.graph.execute_graphql(self.search_query, variables) except GraphError as e: - raise Exception("Unable to execute search query") from e + raise CatalogueError("Unable to execute search query") from e - page_results = [] response = response["searchAcrossEntities"] - facets = self._parse_facets(response.get("facets", [])) + if response["total"] == 0: + return SearchResponse(total_results=0, page_results=[]) logger.debug(json.dumps(response, indent=2)) + page_results = self._parse_search_results(response) + + return SearchResponse( + total_results=response["total"], + page_results=page_results, + facets=self._parse_facets(response.get("facets", [])), + ) + + def _parse_search_results(self, response): + page_results = [] for result in response["searchResults"]: entity = result["entity"] entity_type = entity["type"] matched_fields = self._get_matched_fields(result=result) - if entity_type == "DATA_PRODUCT": - page_results.append(self._parse_data_product(entity, matched_fields)) - elif entity_type == "DATASET": - page_results.append(self._parse_dataset(entity, matched_fields)) + if entity_type == "DATASET": + page_results.append( + self._parse_result(entity, matched_fields, ResultType.TABLE) + ) elif entity_type == "CHART": - page_results.append(self._parse_chart(entity, matched_fields)) + page_results.append( + self._parse_result(entity, matched_fields, ResultType.CHART) + ) elif entity_type == "CONTAINER": page_results.append(self._parse_container(entity, matched_fields)) else: raise ValueError(f"Unexpected entity type: {entity_type}") - return SearchResponse( - total_results=response["total"], page_results=page_results, facets=facets - ) + return page_results @staticmethod def _get_matched_fields(result: dict) -> dict: @@ -142,10 +145,7 @@ def _get_matched_fields(result: dict) -> dict: def search_facets( self, query: str = "*", - result_types: Sequence[ResultType] = ( - ResultType.DATA_PRODUCT, - ResultType.TABLE, - ), + result_types: Sequence[ResultType] = (ResultType.TABLE,), filters: Sequence[MultiSelectFilter] = (), ) -> SearchFacets: """ @@ -164,39 +164,11 @@ def search_facets( try: response = self.graph.execute_graphql(self.facets_query, variables) except GraphError as e: - raise Exception("Unable to execute facets query") from e + raise CatalogueError("Unable to execute facets query") from e response = response["aggregateAcrossEntities"] return self._parse_facets(response.get("facets", [])) - def list_data_product_assets( - self, urn: str, count: int, start: int = 0 - ) -> SearchResponse: - """ - returns a SearchResponse containing all assets in given data product (by urn) - """ - variables = { - "urn": urn, - "query": "*", - "start": start, - "count": count, - } - try: - response = self.graph.execute_graphql( - self.data_product_asset_query, variables - ) - except GraphError as e: - raise Exception("Unable to execute listDataProductAssets query") from e - page_results = [] - page_results = self._get_data_collection_page_results( - response, "listDataProductAssets" - ) - - return SearchResponse( - total_results=response["listDataProductAssets"]["total"], - page_results=page_results, - ) - def list_database_tables( self, urn: str, count: int, start: int = 0 ) -> SearchResponse: @@ -211,7 +183,7 @@ def list_database_tables( self.get_database_tables_query, variables ) except GraphError as e: - raise Exception("Unable to execute listDatabaseEntities query") from e + raise CatalogueError("Unable to execute listDatabaseEntities query") from e page_results = self._get_data_collection_page_results( response["container"], "entities" @@ -232,7 +204,9 @@ def _get_data_collection_page_results(self, response, key_for_results: str): entity_type = entity["type"] matched_fields: dict = {} if entity_type == "DATASET": - page_results.append(self._parse_dataset(entity, matched_fields)) + page_results.append( + self._parse_result(entity, matched_fields, ResultType.TABLE) + ) else: raise ValueError(f"Unexpected entity type: {entity_type}") return page_results @@ -242,8 +216,6 @@ def _map_result_types(self, result_types: Sequence[ResultType]): Map result types to Datahub EntityTypes """ types = [] - if ResultType.DATA_PRODUCT in result_types: - types.append("DATA_PRODUCT") if ResultType.TABLE in result_types: types.append("DATASET") if ResultType.GLOSSARY_TERM in result_types: @@ -256,97 +228,59 @@ def _map_result_types(self, result_types: Sequence[ResultType]): return types def _map_filters(self, filters: Sequence[MultiSelectFilter]): - result = [] - for filter in filters: - result.append( - {"field": filter.filter_name, "values": filter.included_values} - ) + result = [ + {"field": filter.filter_name, "values": filter.included_values} + for filter in filters + ] return result - def _parse_dataset(self, entity: dict[str, Any], matches) -> SearchResult: + def _parse_result( + self, entity: dict[str, Any], matches, result_type: ResultType + ) -> SearchResult: """ Map a dataset entity to a SearchResult """ - owner_email, owner_name = parse_owner(entity) + owner = parse_owner(entity) properties, custom_properties = parse_properties(entity) tags = parse_tags(entity) - last_updated = parse_last_updated(entity) - name = entity["name"] + last_modified = parse_last_modified(entity) + name = entity.get("name") relations = parse_relations( RelationshipType.PARENT, entity.get("relationships", {}) ) + domain = parse_domain(entity) metadata = { - "owner": owner_name, - "owner_email": owner_email, + "owner": owner.display_name, + "owner_email": owner.email, "total_parents": entity.get("relationships", {}).get("total", 0), "parents": relations[RelationshipType.PARENT], + "domain_name": domain.display_name, + "domain_id": domain.urn, "entity_types": self._parse_types_and_sub_types(entity, "Dataset"), } - metadata.update(parse_domain(entity)) - metadata.update(custom_properties) - - fqn = self._get_fully_qualified_name(properties, name) - created, modified = parse_created_and_modified(properties) - - return SearchResult( - id=entity["urn"], - result_type=ResultType.TABLE, - matches=matches, - name=properties.get("name", name), - fully_qualified_name=fqn, - description=properties.get("description", ""), - metadata=metadata, - tags=tags, - last_updated=modified or last_updated, - first_created=created, - ) + metadata.update(custom_properties.usage_restrictions.model_dump()) + metadata.update(custom_properties.access_information.model_dump()) + metadata.update(custom_properties.data_summary.model_dump()) - def _parse_data_product(self, entity: dict[str, Any], matches) -> SearchResult: - """ - Map a data product entity to a SearchResult - """ - owner_email, owner_name = parse_owner(entity) - properties, custom_properties = parse_properties(entity) - tags = parse_tags(entity) - last_updated = parse_last_updated(entity) - metadata = { - "owner": owner_name, - "owner_email": owner_email, - "number_of_assets": properties["numAssets"], - } - metadata.update(parse_domain(entity)) - metadata.update(custom_properties) + # TODO: the way we return name/display name/qualified name should be + # consistent with the upsert/get methods + fqn = properties.get("qualifiedName", name) - fqn = self._get_fully_qualified_name(properties, properties["name"]) + _, modified = parse_created_and_modified(properties) return SearchResult( - id=entity["urn"], - result_type=ResultType.DATA_PRODUCT, + urn=entity["urn"], + result_type=result_type, matches=matches, - name=properties["name"], + name=properties.get("name", name), fully_qualified_name=fqn, description=properties.get("description", ""), metadata=metadata, - tags=tags, - last_updated=last_updated, - ) - - def _parse_chart(self, entity: dict[str, Any], matches) -> SearchResult: - properties, custom_properties = parse_properties(entity) - last_updated = parse_last_updated(entity) - - return SearchResult( - id=entity["urn"], - result_type=ResultType.CHART, - matches=matches, - name=properties["name"], - fully_qualified_name=properties["name"], - description=properties.get("description", ""), - metadata=custom_properties, - last_updated=last_updated, + tags=[tag_str.display_name for tag_str in tags], + last_modified=modified or last_modified, ) def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: @@ -373,18 +307,18 @@ def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: return SearchFacets(results) def _parse_glossary_term(self, entity) -> SearchResult: - properties, custom_properties = parse_properties(entity) + properties, _ = parse_properties(entity) metadata = {"parentNodes": entity["parentNodes"]["nodes"]} return SearchResult( - id=entity["urn"], + urn=entity["urn"], result_type=ResultType.GLOSSARY_TERM, matches={}, name=properties["name"], description=properties.get("description", ""), metadata=metadata, tags=[], - last_updated=None, + last_modified=None, ) def get_glossary_terms(self, count: int = 1000) -> SearchResponse: @@ -395,7 +329,7 @@ def get_glossary_terms(self, count: int = 1000) -> SearchResponse: self.get_glossary_terms_query, variables ) except GraphError as e: - raise Exception("Unable to execute getGlossaryTerms query") from e + raise CatalogueError("Unable to execute getGlossaryTerms query") from e page_results = [] response = response["searchAcrossEntities"] @@ -413,31 +347,38 @@ def _parse_container(self, entity: dict[str, Any], matches) -> SearchResult: Map a Container entity to a SearchResult """ tags = parse_tags(entity) - last_updated = parse_last_updated(entity) + last_modified = parse_last_modified(entity) properties, custom_properties = parse_properties(entity) - owner_email, owner_name = parse_owner(entity) + domain = parse_domain(entity) + owner = parse_owner(entity) + + # TODO: the way we return name/display name/qualified name should be + # consistent with the upsert/get methods + fqn = properties.get("qualifiedName", properties["name"]) metadata = { - "owner": owner_name, - "owner_email": owner_email, + "owner": owner.display_name, + "owner_email": owner.email, + "domain_name": domain.display_name, + "domain_id": domain.urn, "entity_types": self._parse_types_and_sub_types(entity, "Container"), } - fqn = self._get_fully_qualified_name(properties, properties["name"]) - - metadata.update(parse_domain(entity)) + metadata.update(custom_properties.usage_restrictions.model_dump()) + metadata.update(custom_properties.access_information.model_dump()) + metadata.update(custom_properties.data_summary.model_dump()) metadata.update(custom_properties) return SearchResult( - id=entity["urn"], + urn=entity["urn"], result_type=ResultType.DATABASE, matches=matches, name=properties["name"], fully_qualified_name=fqn, description=properties.get("description", ""), metadata=metadata, - tags=tags, - last_updated=last_updated, + tags=[tag.display_name for tag in tags], + last_modified=last_modified, ) def _parse_types_and_sub_types(self, entity: dict, entity_type: str) -> dict: @@ -447,11 +388,3 @@ def _parse_types_and_sub_types(self, entity: dict, entity_type: str) -> dict: else [entity_type] ) return {"entity_type": entity_type, "entity_sub_types": entity_sub_type} - - def _get_fully_qualified_name(self, entity_properties: dict, name: str) -> str: - fqn = ( - entity_properties.get("qualifiedName", name) - if entity_properties.get("qualifiedName") is not None - else name - ) - return fqn diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index b9cb60ae..b94a2024 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -1,200 +1,244 @@ -from dataclasses import dataclass, field from datetime import datetime -from enum import Enum, auto -from typing import Any +from enum import Enum +from typing import Optional + +from pydantic import BaseModel, Field DATAHUB_DATE_FORMAT = "%Y%m%d" -@dataclass -class CatalogueMetadata: - name: str - description: str - owner: str - tags: list[str] = field(default_factory=list) +class RelationshipType(Enum): + PARENT = "PARENT" + PLATFORM = "PLATFORM" + + +class EntityRef(BaseModel): + """ + A reference to another entity in the metadata graph. + """ + + urn: str = Field(description="The identifier of the entity being linked to.") + display_name: str = Field( + description="Display name that can be used for link text." + ) -@dataclass -class DataLocation: +class ColumnRef(BaseModel): """ - A representation of where the data can be found - (in our case, glue/athena) + A reference to a column in a table """ - fully_qualified_name: str - platform_type: str = "glue" - platform_id: str = "glue" + name: str = Field(description="The column name as it appears in the table") + display_name: str = Field(description="A user-friendly version of the name") + table: EntityRef = Field(description="Reference to the table the column belongs to") -class DataProductStatus(Enum): - DRAFT = auto() - PUBLISHED = auto() - RETIRED = auto() +class Column(BaseModel): + """ + A column definition in a table + """ + name: str = Field( + pattern=r"^[\w.]+$", + description="The name of a column as it appears in the table.", + ) + display_name: str = Field(description="A user-friendly version of the name") + type: str = Field( + description="The data type of the column as it appears in the table", + ) + description: str = Field(description="A description of the column") + nullable: bool = Field(description="Whether the field is nullable or not") + is_primary_key: bool = Field( + description="Whether the field is part of the primary key" + ) + foreign_keys: list[ColumnRef] | None = Field( + description="References to columns in other tables" + ) + + +class OwnerRef(BaseModel): + """ + A reference to a named individual that performs some kind of governance + """ -class DatabaseStatus(Enum): - PROD = auto() - PREPROD = auto() - DEV = auto() + display_name: str = Field( + description="The full name of the user as it should be displayed" + ) + email: str = Field("Contact email for the user") + urn: str = Field("Unique identifier for the user") -class RelationshipType(Enum): - PARENT = auto() - - -@dataclass -class RelatedEntity: - id: str - name: str - - -@dataclass -class DataProductMetadata: - name: str - description: str - version: str - owner: str - owner_display_name: str - maintainer: str | None - maintainer_display_name: str | None - email: str - retention_period_in_days: int - domain: str - subdomain: str | None - dpia_required: bool - dpia_location: str | None - last_updated: datetime - creation_date: datetime - s3_location: str | None - status: DataProductStatus = DataProductStatus.DRAFT - tags: list[str] = field(default_factory=list) - - @staticmethod - def from_data_product_metadata_dict(metadata: dict, version, owner_id: str): - """ - Expects a dict containing data product metatdata information as per the - required fields in the json schema at - https://github.com/ministryofjustice/modernisation-platform-environments/tree/main/terraform/environments/data-platform/data-product-metadata-json-schema - - Then populates a DataProductMetadata object with the given data. - """ - new_metadata = DataProductMetadata( - name=metadata["name"], - description=metadata["description"], - version=version, - owner=owner_id, - owner_display_name=metadata["dataProductOwnerDisplayName"], - maintainer=metadata.get("dataProductMaintainer"), - maintainer_display_name=metadata.get("dataProductMaintainerDisplayName"), - email=metadata["email"], - status=DataProductStatus[metadata["status"]], - retention_period_in_days=metadata["retentionPeriod"], - domain=metadata["domain"], - subdomain=metadata.get("subdomain"), - dpia_required=metadata["dpiaRequired"], - dpia_location=metadata.get("dpiaLocation"), - last_updated=datetime.strptime( - metadata["lastUpdated"], DATAHUB_DATE_FORMAT - ), - creation_date=datetime.strptime( - metadata["creationDate"], DATAHUB_DATE_FORMAT - ), - s3_location=metadata.get("s3Location"), - tags=metadata.get("tags", []), - ) - - return new_metadata - - -class SecurityClassification(Enum): - OFFICIAL = auto() - - -@dataclass -class TableMetadata: - name: str - description: str - column_details: list - retention_period_in_days: int | None - domain: str | None = None - parent_entity_name: str | None = None - relationships: dict[RelationshipType, list[RelatedEntity]] | None = None - source_dataset_name: str = "" - where_to_access_dataset: str = "" - fully_qualified_name: str | None = None - data_sensitivity_level: SecurityClassification = SecurityClassification.OFFICIAL - tags: list[str] = field(default_factory=list) - major_version: int = 1 - row_count: int | None = None - owner: str = "" - owner_email: str = "" - last_updated: datetime | None = None - first_created: datetime | None = None - - @staticmethod - def from_data_product_schema_dict( - metadata: dict[str, Any], table_name, retention_period: int | None = None - ): - """ - Expects a dict containing data product table schema information as per the - required fields in the json schema at - https://github.com/ministryofjustice/modernisation-platform-environments/tree/main/terraform/environments/data-platform/data-product-table-schema-json-schema - - and should be passed table name and optionally a retention period - - Then populates a TableMetadata object with the given data. - """ - - new_metadata = TableMetadata( - name=table_name, - description=metadata["tableDescription"], - column_details=metadata["columns"], - retention_period_in_days=retention_period, - source_dataset_name=metadata.get("sourceDatasetName", ""), - where_to_access_dataset=metadata.get("sourceDatasetLocation", ""), - data_sensitivity_level=SecurityClassification[ - metadata.get("securityClassification", "OFFICIAL") - ], - tags=metadata.get("tags", []), - ) - - return new_metadata - - -# jscpd:ignore-start -@dataclass -class DatabaseMetadata: - """ - For source system databases - currently matches DataProductMetadata - but will need to be revisted and refined potentially - """ - - name: str - description: str - version: str - owner: str - owner_display_name: str - maintainer: str | None - maintainer_display_name: str | None - email: str - retention_period_in_days: int - domain: str - subdomain: str | None - dpia_required: bool - dpia_location: str | None - last_updated: datetime - creation_date: datetime - s3_location: str | None - status: DataProductStatus = DataProductStatus.DRAFT - tags: list[str] = field(default_factory=list) - - -# jscpd:ignore-end - - -@dataclass -class ChartMetadata: - name: str - description: str - external_url: str - tags: list[str] = field(default_factory=list) +class Governance(BaseModel): + """ + Governance model for an entity or domain + """ + + data_owner: OwnerRef = Field( + description="The senior individual responsible for the data." + ) + data_stewards: list[OwnerRef] = Field( + description="Experts who manage the data day-to-day." + ) + + +class DomainRef(BaseModel): + """ + Reference to a domain that entities belong to + """ + + display_name: str = Field( + description="Display name", json_schema_extra={"example": "HMPPS"} + ) + urn: str = Field( + description="The identifier of the domain.", + json_schema_extra={"example": "urn:li:domain:HMCTS"}, + ) + + +class TagRef(BaseModel): + """ + Reference to a tag + """ + + display_name: str = Field( + description="Human friendly tag name", json_schema_extra={"example": "PII"} + ) + urn: str = Field( + description="The identifier of the tag", + json_schema_extra={"example": "urn:li:tag:PII"}, + ) + + +class UsageRestrictions(BaseModel): + """ + Metadata about how entities may be used. + """ + + dpia_required: bool | str | None = Field( + description="Bool for if a data privacy impact assessment (DPIA) is required to access this database", + default=None, + ) + dpia_location: str | None = Field( + description="Where to find the DPIA document", default=None + ) + + +class AccessInformation(BaseModel): + """ + Any metadata about how to access a data entity. + The same data entity may be accessable via multiple means. + """ + + where_to_access_dataset: str = Field( + description="User-friendly description of where the data can be accessed", + default="", + ) + source_dataset_name: str = Field( + description="The name of a dataset this data was derived from", default="" + ) + s3_location: str | None = Field( + description="Location of the data in s3", default=None + ) + + +class DataSummary(BaseModel): + """ + Summarised information derived from the actual data. + """ + + row_count: int | str = Field( + description="Row count when the metadata was last updated", default="" + ) + + +class CustomEntityProperties(BaseModel): + """Custom entity properties not part of DataHub's entity model""" + + usage_restrictions: UsageRestrictions = Field( + description="Limitations on how the data may be used and accessed", + default_factory=UsageRestrictions, + ) + access_information: AccessInformation = Field( + description="Metadata about how to access a data entity", + default_factory=AccessInformation, + ) + data_summary: DataSummary = Field( + description="Summary of data stored in this table", default_factory=DataSummary + ) + + +class Entity(BaseModel): + """ + Any searchable data entity that is present in the metadata graph, which + may be related to other entities. + Examples include platforms, databases, tables + """ + + urn: str | None = Field( + "Unique identifier for the entity. Relates to Datahub's urn" + ) + display_name: str | None = Field("Display name of the entity") + name: str = Field("Actual name of the entity in its source platform") + fully_qualified_name: str | None = Field( + "Fully qualified name of the entity in its source platform" + ) + description: str = Field( + description="Detailed description about what functional area this entity is representing, what purpose it has" + " and business related information.", + ) + relationships: dict[RelationshipType, list[EntityRef]] = Field( + default={}, + description="References to related entities in the metadata graph, such as platform or parent entities", + ) + domain: DomainRef = Field(description="The domain this entity belongs to.") + governance: Governance = Field(description="Information about governance") + tags: list[TagRef] = Field( + default_factory=list, + description="Additional tags to add.", + ) + last_modified: Optional[datetime] = Field( + description="When the metadata was last updated in the catalogue", + default=None, + ) + created: Optional[datetime] = Field( + description="When the data entity was first created", + default=None, + ) + platform: EntityRef = Field( + description="The platform that an entity should belong to, e.g. Glue, Athena, DBT. Should exist in datahub", + ) + custom_properties: CustomEntityProperties = Field( + description="Fields to add to DataHub custom properties", + default_factory=CustomEntityProperties, + ) + + +class Database(Entity): + """For source system databases""" + + +class Table(Entity): + """A table in a database or a tabular dataset""" + + column_details: list[Column] = Field( + description="A list of objects which relate to columns in your data, each list item will contain, a name of" + " the column, data type of the column and description of the column." + ) + + +class Chart(Entity): + """A visualisation of a dataset""" + + external_url: str = Field("URL to view the chart") + + +class Domain(Entity): + """Datahub domain""" + + +# if __name__ == "__main__": +# import erdantic as erd + +# erd.draw(Database, out="database.png") +# erd.draw(Table, out="table.png") +# erd.draw(Chart, out="chart.png") diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index 9057cd0d..d3c04137 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -1,3 +1,5 @@ +"""Search types module.""" + from dataclasses import dataclass, field from datetime import datetime from enum import Enum, auto @@ -5,7 +7,8 @@ class ResultType(Enum): - DATA_PRODUCT = auto() + """Result type.""" + TABLE = auto() GLOSSARY_TERM = auto() CHART = auto() @@ -51,7 +54,7 @@ class FacetOption: @dataclass class SearchResult: - id: str + urn: str result_type: ResultType name: str fully_qualified_name: str = "" @@ -59,8 +62,8 @@ class SearchResult: matches: dict[str, str] = field(default_factory=dict) metadata: dict[str, Any] = field(default_factory=dict) tags: list[str] = field(default_factory=list) - last_updated: datetime | None = None - first_created: datetime | None = None + last_modified: datetime | None = None + created: datetime | None = None @dataclass diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index 89eb2823..dc0e795f 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "acryl-datahub" @@ -232,24 +232,14 @@ files = [ frozenlist = ">=1.1.0" [[package]] -name = "antlr4-python3-runtime" -version = "4.9.2" -description = "ANTLR 4.9.2 runtime for Python 3.7" +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = "*" -files = [ - {file = "antlr4-python3-runtime-4.9.2.tar.gz", hash = "sha256:31f5abdc7faf16a1a6e9bf2eb31565d004359b821b09944436a34361929ae85a"}, -] - -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [[package]] @@ -310,62 +300,6 @@ files = [ avro = ">=1.10" six = "*" -[[package]] -name = "beautifulsoup4" -version = "4.12.2" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "boto3" -version = "1.28.84" -description = "The AWS SDK for Python" -optional = false -python-versions = ">= 3.7" -files = [ - {file = "boto3-1.28.84-py3-none-any.whl", hash = "sha256:98b01bbea27740720a06f7c7bc0132ae4ce902e640aab090cfb99ad3278449c3"}, - {file = "boto3-1.28.84.tar.gz", hash = "sha256:adfb915958d7b54d876891ea1599dd83189e35a2442eb41ca52b04ea716180b6"}, -] - -[package.dependencies] -botocore = ">=1.31.84,<1.32.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.7.0,<0.8.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.31.84" -description = "Low-level, data-driven core of boto 3." -optional = false -python-versions = ">= 3.7" -files = [ - {file = "botocore-1.31.84-py3-none-any.whl", hash = "sha256:d65bc05793d1a8a8c191a739f742876b4b403c5c713dc76beef262d18f7984a2"}, - {file = "botocore-1.31.84.tar.gz", hash = "sha256:8913bedb96ad0427660dee083aeaa675466eb662bbf1a47781956b5882aadcc5"}, -] - -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} - -[package.extras] -crt = ["awscrt (==0.19.10)"] - [[package]] name = "cached-property" version = "1.5.2" @@ -377,17 +311,6 @@ files = [ {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] -[[package]] -name = "cachetools" -version = "5.3.2" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, -] - [[package]] name = "certifi" version = "2023.7.22" @@ -399,81 +322,6 @@ files = [ {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] - [[package]] name = "charset-normalizer" version = "3.3.2" @@ -618,26 +466,6 @@ files = [ [package.extras] test = ["click", "pytest", "six"] -[[package]] -name = "collate-sqllineage" -version = "1.1.5" -description = "Collate SQL Lineage for Analysis Tool powered by Python and sqlfluff based on sqllineage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "collate-sqllineage-1.1.5.tar.gz", hash = "sha256:bc0d66c92202cd6735c30dcee74a7e57cfd02f4275e5517ccb2664499f52a251"}, - {file = "collate_sqllineage-1.1.5-py3-none-any.whl", hash = "sha256:c48e0a09f3704c10b8cd5954bfa8ccd0fca9e5a43c1d55c1ca1fb1be0482f5ef"}, -] - -[package.dependencies] -networkx = ">=2.4" -sqlfluff = "2.1.4" -sqlparse = "0.4.3" - -[package.extras] -ci = ["bandit", "black", "flake8", "flake8-blind-except", "flake8-builtins", "flake8-import-order", "flake8-logging-format", "mypy", "pytest", "pytest-cov", "tox", "twine", "wheel"] -docs = ["Sphinx (>=3.2.0)", "sphinx-rtd-theme (>=0.5.0)"] - [[package]] name = "colorama" version = "0.4.6" @@ -649,74 +477,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "croniter" -version = "1.3.15" -description = "croniter provides iteration for datetime object with cron like format" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "croniter-1.3.15-py2.py3-none-any.whl", hash = "sha256:f17f877be1d93b9e3191151584a19d8b367b017ab0febc8c5472b9300da61c4c"}, - {file = "croniter-1.3.15.tar.gz", hash = "sha256:924a38fda88f675ec6835667e1d32ac37ff0d65509c2152729d16ff205e32a65"}, -] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "cryptography" -version = "42.0.4" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, - {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, - {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, - {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, - {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, - {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, - {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, - {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, - {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, - {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, - {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, - {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, - {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, - {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - [[package]] name = "deepdiff" version = "6.7.1" @@ -752,46 +512,6 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] -[[package]] -name = "diff-cover" -version = "8.0.1" -description = "Run coverage and linting reports on diffs" -optional = false -python-versions = ">=3.8.10,<4.0.0" -files = [ - {file = "diff_cover-8.0.1-py3-none-any.whl", hash = "sha256:1eb651e7d4e25d9d7b4b246c4abe3e9e5645f3856f92c104fca04701fc0eb093"}, - {file = "diff_cover-8.0.1.tar.gz", hash = "sha256:cc39d199eb72fe41bcdcfee164eb5b591533b4c625580e3f9a9acc6869064d7c"}, -] - -[package.dependencies] -chardet = ">=3.0.0" -Jinja2 = ">=2.7.1" -pluggy = ">=0.13.1,<2" -Pygments = ">=2.9.0,<3.0.0" - -[package.extras] -toml = ["tomli (>=1.2.1)"] - -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - [[package]] name = "docker" version = "7.0.0" @@ -813,39 +533,6 @@ urllib3 = ">=1.26.0" ssh = ["paramiko (>=2.4.3)"] websockets = ["websocket-client (>=1.3.0)"] -[[package]] -name = "ecdsa" -version = "0.18.0" -description = "ECDSA cryptographic signature library (pure python)" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"}, - {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"}, -] - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "email-validator" -version = "2.1.0.post1" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.1.0.post1-py3-none-any.whl", hash = "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637"}, - {file = "email_validator-2.1.0.post1.tar.gz", hash = "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "exceptiongroup" version = "1.1.3" @@ -974,247 +661,6 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] -[[package]] -name = "google" -version = "3.0.0" -description = "Python bindings to the Google search engine." -optional = false -python-versions = "*" -files = [ - {file = "google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935"}, - {file = "google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - -[[package]] -name = "google-auth" -version = "2.23.4" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-auth-2.23.4.tar.gz", hash = "sha256:79905d6b1652187def79d491d6e23d0cbb3a21d3c7ba0dbaa9c8a01906b13ff3"}, - {file = "google_auth-2.23.4-py2.py3-none-any.whl", hash = "sha256:d4bbc92fe4b8bfd2f3e8d88e5ba7085935da208ee38a134fc280e7ce682a05f2"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "greenlet" -version = "3.0.1" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"}, - {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"}, - {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"}, - {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"}, - {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"}, - {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"}, - {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"}, - {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"}, - {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"}, - {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"}, - {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"}, - {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"}, - {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"}, - {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"}, - {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"}, - {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"}, - {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"}, - {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"}, - {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"}, - {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"}, - {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"}, - {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"}, - {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"}, - {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"}, - {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"}, - {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"}, - {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"}, -] - -[package.extras] -docs = ["Sphinx"] -test = ["objgraph", "psutil"] - -[[package]] -name = "grpcio" -version = "1.59.2" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:d2fa68a96a30dd240be80bbad838a0ac81a61770611ff7952b889485970c4c71"}, - {file = "grpcio-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:cf0dead5a2c5a3347af2cfec7131d4f2a2e03c934af28989c9078f8241a491fa"}, - {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e420ced29b5904cdf9ee5545e23f9406189d8acb6750916c2db4793dada065c6"}, - {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b230028a008ae1d0f430acb227d323ff8a619017415cf334c38b457f814119f"}, - {file = "grpcio-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4a3833c0e067f3558538727235cd8a49709bff1003200bbdefa2f09334e4b1"}, - {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6b25ed37c27e652db01be341af93fbcea03d296c024d8a0e680017a268eb85dd"}, - {file = "grpcio-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73abb8584b0cf74d37f5ef61c10722adc7275502ab71789a8fe3cb7ef04cf6e2"}, - {file = "grpcio-1.59.2-cp310-cp310-win32.whl", hash = "sha256:d6f70406695e3220f09cd7a2f879333279d91aa4a8a1d34303b56d61a8180137"}, - {file = "grpcio-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:3c61d641d4f409c5ae46bfdd89ea42ce5ea233dcf69e74ce9ba32b503c727e29"}, - {file = "grpcio-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:3059668df17627f0e0fa680e9ef8c995c946c792612e9518f5cc1503be14e90b"}, - {file = "grpcio-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:72ca2399097c0b758198f2ff30f7178d680de8a5cfcf3d9b73a63cf87455532e"}, - {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c978f864b35f2261e0819f5cd88b9830b04dc51bcf055aac3c601e525a10d2ba"}, - {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9411e24328a2302e279e70cae6e479f1fddde79629fcb14e03e6d94b3956eabf"}, - {file = "grpcio-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb7e0fe6ad73b7f06d7e2b689c19a71cf5cc48f0c2bf8608469e51ffe0bd2867"}, - {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2504eed520958a5b77cc99458297cb7906308cb92327f35fb7fbbad4e9b2188"}, - {file = "grpcio-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2171c39f355ba5b551c5d5928d65aa6c69807fae195b86ef4a7d125bcdb860a9"}, - {file = "grpcio-1.59.2-cp311-cp311-win32.whl", hash = "sha256:d2794f0e68b3085d99b4f6ff9c089f6fdd02b32b9d3efdfbb55beac1bf22d516"}, - {file = "grpcio-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:2067274c88bc6de89c278a672a652b4247d088811ece781a4858b09bdf8448e3"}, - {file = "grpcio-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:535561990e075fa6bd4b16c4c3c1096b9581b7bb35d96fac4650f1181e428268"}, - {file = "grpcio-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:a213acfbf186b9f35803b52e4ca9addb153fc0b67f82a48f961be7000ecf6721"}, - {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:6959fb07e8351e20501ffb8cc4074c39a0b7ef123e1c850a7f8f3afdc3a3da01"}, - {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e82c5cf1495244adf5252f925ac5932e5fd288b3e5ab6b70bec5593074b7236c"}, - {file = "grpcio-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023088764012411affe7db183d1ada3ad9daf2e23ddc719ff46d7061de661340"}, - {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:da2d94c15f88cd40d7e67f7919d4f60110d2b9d5b1e08cf354c2be773ab13479"}, - {file = "grpcio-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6009386a2df66159f64ac9f20425ae25229b29b9dd0e1d3dd60043f037e2ad7e"}, - {file = "grpcio-1.59.2-cp312-cp312-win32.whl", hash = "sha256:75c6ecb70e809cf1504465174343113f51f24bc61e22a80ae1c859f3f7034c6d"}, - {file = "grpcio-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:cbe946b3e6e60a7b4618f091e62a029cb082b109a9d6b53962dd305087c6e4fd"}, - {file = "grpcio-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:f8753a6c88d1d0ba64302309eecf20f70d2770f65ca02d83c2452279085bfcd3"}, - {file = "grpcio-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:f1ef0d39bc1feb420caf549b3c657c871cad4ebbcf0580c4d03816b0590de0cf"}, - {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c93f4abbb54321ee6471e04a00139c80c754eda51064187963ddf98f5cf36a4"}, - {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08d77e682f2bf730a4961eea330e56d2f423c6a9b91ca222e5b1eb24a357b19f"}, - {file = "grpcio-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff16d68bf453275466a9a46739061a63584d92f18a0f5b33d19fc97eb69867c"}, - {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4abb717e320e74959517dc8e84a9f48fbe90e9abe19c248541e9418b1ce60acd"}, - {file = "grpcio-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36f53c2b3449c015880e7d55a89c992c357f176327b0d2873cdaaf9628a37c69"}, - {file = "grpcio-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:cc3e4cd087f07758b16bef8f31d88dbb1b5da5671d2f03685ab52dece3d7a16e"}, - {file = "grpcio-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:27f879ae604a7fcf371e59fba6f3ff4635a4c2a64768bd83ff0cac503142fef4"}, - {file = "grpcio-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:7cf05053242f61ba94014dd3a986e11a083400a32664058f80bf4cf817c0b3a1"}, - {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:e1727c1c0e394096bb9af185c6923e8ea55a5095b8af44f06903bcc0e06800a2"}, - {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d573e70a6fe77555fb6143c12d3a7d3fa306632a3034b4e7c59ca09721546f8"}, - {file = "grpcio-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31176aa88f36020055ace9adff2405a33c8bdbfa72a9c4980e25d91b2f196873"}, - {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11168ef43e4a43ff1b1a65859f3e0ef1a173e277349e7fb16923ff108160a8cd"}, - {file = "grpcio-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:53c9aa5ddd6857c0a1cd0287225a2a25873a8e09727c2e95c4aebb1be83a766a"}, - {file = "grpcio-1.59.2-cp38-cp38-win32.whl", hash = "sha256:3b4368b33908f683a363f376dfb747d40af3463a6e5044afee07cf9436addf96"}, - {file = "grpcio-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:0a754aff9e3af63bdc4c75c234b86b9d14e14a28a30c4e324aed1a9b873d755f"}, - {file = "grpcio-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:1f9524d1d701e399462d2c90ba7c193e49d1711cf429c0d3d97c966856e03d00"}, - {file = "grpcio-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:f93dbf58f03146164048be5426ffde298b237a5e059144847e4940f5b80172c3"}, - {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6da6dea3a1bacf99b3c2187e296db9a83029ed9c38fd4c52b7c9b7326d13c828"}, - {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5f09cffa619adfb44799fa4a81c2a1ad77c887187613fb0a8f201ab38d89ba1"}, - {file = "grpcio-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c35aa9657f5d5116d23b934568e0956bd50c615127810fffe3ac356a914c176a"}, - {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:74100fecaec8a535e380cf5f2fb556ff84957d481c13e54051c52e5baac70541"}, - {file = "grpcio-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:128e20f57c5f27cb0157e73756d1586b83c1b513ebecc83ea0ac37e4b0e4e758"}, - {file = "grpcio-1.59.2-cp39-cp39-win32.whl", hash = "sha256:686e975a5d16602dc0982c7c703948d17184bd1397e16c8ee03511ecb8c4cdda"}, - {file = "grpcio-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:242adc47725b9a499ee77c6a2e36688fa6c96484611f33b1be4c57ab075a92dd"}, - {file = "grpcio-1.59.2.tar.gz", hash = "sha256:d8f9cd4ad1be90b0cf350a2f04a38a36e44a026cac1e036ac593dc48efe91d52"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.59.2)"] - -[[package]] -name = "grpcio-tools" -version = "1.59.2" -description = "Protobuf code generator for gRPC" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-tools-1.59.2.tar.gz", hash = "sha256:75905266cf90f1866b322575c2edcd4b36532c33fc512bb1b380dc58d84b1030"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:9b2885c0e2c9a97bde33497a919032afbd8b5c6dc2f8d4dd4198e77226e0de05"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:2f410375830a9bb7140a07da4d75bf380e0958377bed50d77d1dae302de4314e"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e21fc172522d2dda815223a359b2aca9bc317a1b5e5dea5a58cd5079333af133"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:072a7ce979ea4f7579c3c99fcbde3d1882c3d1942a3b51d159f67af83b714cd8"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38f8edb2909702c2478b52f6213982c21e4f66f739ac953b91f97863ba2c06a"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:12fdee2de80d83eadb1294e0f8a0cb6cefcd2e4988ed680038ab09cd04361ee4"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a3cb707da722a0b6c4021fc2cc1c005a8d4037d8ad0252f93df318b9b8a6b4f3"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-win32.whl", hash = "sha256:ec2fbb02ebb9f2ae1b1c69cccf913dee8c41f5acad94014d3ce11b53720376e3"}, - {file = "grpcio_tools-1.59.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0dc271a200dbab6547b2c73fcbdb7efe94c31cb633aa20d073f7cf4493493e1"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:d634b65cc8ee769edccf1647d8a16861a27e0d8cbd787c711168d2c5e9bddbd1"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:b0b712acec00a9cbc2204c271d638062a2cb8ce74f25d158b023ff6e93182659"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:dd5c78f8e7c6e721b9009c92481a0e3b30a9926ef721120723a03b8a34a34fb9"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:724f4f0eecc17fa66216eebfff145631070f04ed7fb4ddf7a7d1c4f954ecc2a1"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec33ddee691e60511e2a7c793aad4cf172ae20e08d95c786cbba395f6203a7"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa1b9dee7811fad081816e884d063c4dd4946dba61aa54243b4c76c311090c48"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba8dba19e7b2b6f7369004533866f222ba483b9e14d2d152ecf9339c0df1283a"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-win32.whl", hash = "sha256:df35d145bc2f6e5f57b74cb69f66526675a5f2dcf7d54617ce0deff0c82cca0a"}, - {file = "grpcio_tools-1.59.2-cp311-cp311-win_amd64.whl", hash = "sha256:99ddc0f5304071a355c261ae49ea5d29b9e9b6dcf422dfc55ada70a243e27e8f"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:670f5889853215999eb3511a623dd7dff01b1ce1a64610d13366e0fd337f8c79"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:1e949e66d4555ce319fd7acef90df625138078d8729c4dc6f6a9f05925034433"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:09d809ca88999b2578119683f9f0f6a9b42de95ea21550852114a1540b6a642c"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0925545180223fabd6da9b34513efac83aa16673ef8b1cb0cc678e8cf0923c"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2ccb59dfbf2ebd668a5a7c4b7bb2b859859641d2b199114b557cd045aac6102"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:12cc7698fad48866f68fdef831685cb31ef5814ac605d248c4e5fc964a6fb3f6"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:55c401599d5093c4cfa83b8f0ee9757b4d6d3029b10bd67be2cffeada7a44961"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-win32.whl", hash = "sha256:896f5cdf58f658025a4f7e4ea96c81183b4b6a4b1b4d92ae66d112ac91f062f1"}, - {file = "grpcio_tools-1.59.2-cp312-cp312-win_amd64.whl", hash = "sha256:b53db1523015a3acda75722357df6c94afae37f6023800c608e09a5c05393804"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:d08b398509ea4d544bcecddd9a21f59dc556396916c3915904cac206af2db72b"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:09749e832e06493841000275248b031f7154665900d1e1b0e42fc17a64bf904d"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:e972746000aa192521715f776fab617a3437bed29e90fe0e0fd0d0d6f498d7d4"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cbeeb3d8ec4cb25c92e17bfbdcef3c3669e85c5ee787a6e581cb942bc0ae2b88"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed8e6632d8d839456332d97b96db10bd2dbf3078e728d063394ac2d54597ad80"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:531f87c8e884c6a2e58f040039dfbfe997a4e33baa58f7c7d9993db37b1f5ad0"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:feca316e17cfead823af6eae0fc20c0d5299a94d71cfb7531a0e92d050a5fb2f"}, - {file = "grpcio_tools-1.59.2-cp37-cp37m-win_amd64.whl", hash = "sha256:41b5dd6a06c2563ac3b3adda6d875b15e63eb7b1629e85fc9af608c3a76c4c82"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:7ec536cdae870a74080c665cfb1dca8d0784a931aa3c26376ef971a3a51b59d4"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:9c106ebbed0db446f59f0efe5c3fce33a0a21bf75b392966585e4b5934891b92"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:32141ef309543a446337e934f0b7a2565a6fca890ff4e543630a09ef72c8d00b"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f2ce5ecd63c492949b03af73b1dd6d502c567cc2f9c2057137e518b0c702a01"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9ce2a209871ed1c5ae2229e6f4f5a3ea96d83b7871df5d9773d72a72545683"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7f0e26af7c07bfa906c91ca9f5932514928a7f032f5f20aecad6b5541037de7e"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:48782727c5cff8b8c96e028a8a58614ff6a37eadc0db85866516210c7aafe9ae"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-win32.whl", hash = "sha256:4a1810bc5de51cc162a19ed3c11da8ddc64d8cfcba049ef337c20fcb397f048b"}, - {file = "grpcio_tools-1.59.2-cp38-cp38-win_amd64.whl", hash = "sha256:3cf9949a2aadcece3c1e0dd59249aea53dbfc8cc94f7d707797acd67cf6cf931"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:f52e0ce8f2dcf1f160c847304016c446075a83ab925d98933d4681bfa8af2962"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:eb597d6bf9f5bfa54d00546e828f0d4e2c69250d1bc17c27903c0c7b66372135"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:17ef468836d7cf0b2419f4d5c7ac84ec2d598a1ae410773585313edacf7c393e"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dee5f7e7a56177234e61a483c70ca2ae34e73128372c801bb7039993870889f1"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f50ff312b88918c5a6461e45c5e03869749a066b1c24a7327e8e13e117efe4fc"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a85da4200295ee17e3c1ae068189a43844420ed7e9d531a042440f52de486dfb"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f518f22a3082de00f0d7a216e96366a87e6973111085ba1603c3bfa7dba2e728"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-win32.whl", hash = "sha256:6e735a26e8ea8bb89dc69343d1d00ea607449c6d81e21f339ee118562f3d1931"}, - {file = "grpcio_tools-1.59.2-cp39-cp39-win_amd64.whl", hash = "sha256:3491cb69c909d586c23d7e6d0ac87844ca22f496f505ce429c0d3301234f2cf3"}, -] - -[package.dependencies] -grpcio = ">=1.59.2" -protobuf = ">=4.21.6,<5.0dev" -setuptools = "*" - [[package]] name = "humanfriendly" version = "10.0" @@ -1338,25 +784,6 @@ files = [ {file = "ijson-3.2.3.tar.gz", hash = "sha256:10294e9bf89cb713da05bc4790bdff616610432db561964827074898e174f917"}, ] -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1368,59 +795,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - -[[package]] -name = "jsonpatch" -version = "1.32" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, - {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] - [[package]] name = "jsonref" version = "1.1.0" @@ -1467,89 +841,6 @@ files = [ [package.dependencies] referencing = ">=0.28.0" -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "memory-profiler" -version = "0.61.0" -description = "A module for monitoring memory usage of a python program" -optional = false -python-versions = ">=3.5" -files = [ - {file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"}, - {file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"}, -] - -[package.dependencies] -psutil = "*" - [[package]] name = "mixpanel" version = "4.10.0" @@ -1660,136 +951,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "networkx" -version = "3.2.1" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.9" -files = [ - {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, - {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, -] - -[package.extras] -default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "openmetadata-ingestion" -version = "1.2.0.1" -description = "Ingestion Framework for OpenMetadata" -optional = false -python-versions = ">=3.8" -files = [ - {file = "openmetadata-ingestion-1.2.0.1.tar.gz", hash = "sha256:9ff2349532fc5b243804d4fd2597efafb72c764b4c8dff3554f7d95198b02345"}, - {file = "openmetadata_ingestion-1.2.0.1-py3-none-any.whl", hash = "sha256:da20eac2be336a6c9c48a70c45d7b6b6240a6ec8f3142f7ccaf2aa89887da810"}, -] - -[package.dependencies] -antlr4-python3-runtime = "4.9.2" -avro = ">=1.11,<2.0" -boto3 = ">=1.20,<2.0" -cached-property = "1.5.2" -chardet = "4.0.0" -collate-sqllineage = ">=1.0.4" -croniter = ">=1.3.0,<1.4.0" -cryptography = "*" -email-validator = ">=1.0.3" -google = ">=3.0.0" -google-auth = ">=1.33.0" -grpcio-tools = ">=1.47.2" -idna = ">=2.5,<3" -importlib-metadata = ">=4.13.0" -Jinja2 = ">=2.11.3" -jsonpatch = "1.32" -jsonschema = "*" -memory-profiler = "*" -mypy-extensions = ">=0.4.3" -pydantic = ">=1.10,<2.0" -pymysql = ">=1.0.2" -python-dateutil = ">=2.8.1" -python-jose = ">=3.3,<4.0" -PyYAML = ">=6.0,<7.0" -requests = ">=2.23" -requests-aws4auth = ">=1.1,<2.0" -setuptools = ">=66.0.0,<66.1.0" -sqlalchemy = ">=1.4.0,<2" -tabulate = "0.9.0" -typing-compat = ">=0.1.0,<0.2.0" -typing-extensions = "<=4.5.0" -typing-inspect = "*" -wheel = ">=0.38.4,<0.39.0" - -[package.extras] -airflow = ["apache-airflow (==2.6.3)"] -all = ["GeoAlchemy2 (>=0.12,<1.0)", "Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "adlfs (>=2022.2.0)", "alembic (>=1.10.2,<1.11.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "azure-identity", "azure-identity (>=1.12,<2.0)", "azure-storage-blob", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "cachetools", "chardet (==4.0.0)", "clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)", "collate-sqllineage (>=1.0.4)", "confluent-kafka (==2.1.1)", "couchbase (>=4.1,<5.0)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "cx-Oracle (>=8.3.0,<9)", "dagster-graphql (>=1.1,<2.0)", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "delta-spark (<=2.3.0)", "elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)", "email-validator (>=1.0.3)", "fastavro (>=1.2.0)", "gcsfs (==2022.11.0)", "gitpython (>=3.1.34,<3.2.0)", "giturlparse", "google (>=3.0.0)", "google-auth (>=1.33.0)", "google-cloud", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "google-cloud-storage (==1.43.0)", "grpcio-tools (>=1.47.2)", "hdbcli", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "impyla (>=0.18.0,<0.19.0)", "impyla[kerberos] (>=0.18.0,<0.19.0)", "jsonpatch (==1.32)", "jsonschema", "ldap3 (==2.9.1)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "memory-profiler", "mlflow-skinny (>=2.3.0)", "msal (>=1.2,<2.0)", "mypy-extensions (>=0.4.3)", "neo4j (>=5.3.0,<5.4.0)", "okta (>=2.3,<3.0)", "oracledb (>=1.2,<2.0)", "packaging (==21.3)", "pandas (==1.3.5)", "pinotdb (>=0.3,<1.0)", "presidio-analyzer (==2.2.32)", "presto-types-parser (>=0.0.2)", "protobuf", "psycopg2-binary", "pyarrow (>=10.0,<11.0)", "pyathena (==3.0.8)", "pydantic (>=1.10,<2.0)", "pydomo (>=0.3,<1.0)", "pydruid (>=0.6.5)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pymssql (>=2.2.0,<2.3.0)", "pymysql (>=1.0.2)", "pyodbc (>=4.0.35,<5)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "python-on-whales (==0.55.0)", "python-snappy (>=0.6.1,<0.7.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "s3fs (==0.4.2)", "sasl (>=0.3,<1.0)", "scikit-learn (>=1.0,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "simple-salesforce (==1.11.4)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy (>=1.4.0,<2)", "sqlalchemy-bigquery (>=1.2.2)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-hana", "sqlalchemy-pgspider", "sqlalchemy-pytds (>=0.3,<1.0)", "sqlalchemy-redshift (==0.8.12)", "sqlalchemy-vertica[vertica-python] (>=0.0.5)", "tableau-api-lib (>=0.1,<1.0)", "tabulate (==0.9.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)", "trino[sqlalchemy]", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "websocket-client (>=1.6.1,<1.7.0)", "wheel (>=0.38.4,<0.39.0)"] -amundsen = ["neo4j (>=5.3.0,<5.4.0)"] -athena = ["pyathena (==3.0.8)"] -azure-sso = ["msal (>=1.2,<2.0)"] -azuresql = ["pyodbc (>=4.0.35,<5)"] -backup = ["azure-identity", "azure-storage-blob", "boto3 (>=1.20,<2.0)"] -base = ["Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "chardet (==4.0.0)", "collate-sqllineage (>=1.0.4)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "email-validator (>=1.0.3)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "grpcio-tools (>=1.47.2)", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "jsonpatch (==1.32)", "jsonschema", "memory-profiler", "mypy-extensions (>=0.4.3)", "pydantic (>=1.10,<2.0)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "sqlalchemy (>=1.4.0,<2)", "tabulate (==0.9.0)", "typing-compat (>=0.1.0,<0.2.0)", "typing-extensions (<=4.5.0)", "typing-inspect", "wheel (>=0.38.4,<0.39.0)"] -bigquery = ["cachetools", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "pyarrow (>=10.0,<11.0)", "sqlalchemy-bigquery (>=1.2.2)"] -clickhouse = ["clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)"] -couchbase = ["couchbase (>=4.1,<5.0)"] -dagster = ["GeoAlchemy2 (>=0.12,<1.0)", "dagster-graphql (>=1.1,<2.0)", "psycopg2-binary", "pymysql (>=1.0.2)"] -data-insight = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] -databricks = ["databricks-sdk (>=0.1,<1.0)", "sqlalchemy-databricks (>=0.1,<1.0)"] -datalake-azure = ["adlfs (>=2022.2.0)", "azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] -datalake-gcs = ["boto3 (>=1.20,<2.0)", "gcsfs (==2022.11.0)", "google-cloud-storage (==1.43.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] -datalake-s3 = ["boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)", "s3fs (==0.4.2)"] -db2 = ["ibm-db-sa (>=0.3,<1.0)"] -dbt = ["azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "dbt-artifacts-parser", "google-cloud", "google-cloud-storage (==1.43.0)"] -deltalake = ["delta-spark (<=2.3.0)"] -dev = ["black (==22.3.0)", "datamodel-code-generator (==0.22.0)", "docker", "isort", "pre-commit", "pycln", "pylint (>=3.0.0,<3.1.0)", "twine"] -docker = ["python-on-whales (==0.55.0)"] -domo = ["pydomo (>=0.3,<1.0)"] -druid = ["pydruid (>=0.6.5)"] -dynamodb = ["boto3 (>=1.20,<2.0)"] -e2e-test = ["pytest-base-url", "pytest-playwright"] -elasticsearch = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] -glue = ["boto3 (>=1.20,<2.0)"] -great-expectations = ["great-expectations (>=0.17.0,<0.18.0)"] -hive = ["impyla (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] -impala = ["impyla[kerberos] (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] -kafka = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] -kinesis = ["boto3 (>=1.20,<2.0)"] -ldap-users = ["ldap3 (==2.9.1)"] -looker = ["gitpython (>=3.1.34,<3.2.0)", "giturlparse", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)"] -mlflow = ["alembic (>=1.10.2,<1.11.0)", "mlflow-skinny (>=2.3.0)"] -mongo = ["pandas (==1.3.5)", "pymongo (>=4.3,<5.0)"] -mssql = ["sqlalchemy-pytds (>=0.3,<1.0)"] -mssql-odbc = ["pyodbc (>=4.0.35,<5)"] -mysql = ["pymysql (>=1.0.2)"] -okta = ["okta (>=2.3,<3.0)"] -oracle = ["cx-Oracle (>=8.3.0,<9)", "oracledb (>=1.2,<2.0)"] -pgspider = ["psycopg2-binary", "sqlalchemy-pgspider"] -pii-processor = ["pandas (==1.3.5)", "presidio-analyzer (==2.2.32)", "spacy (==3.5.0)"] -pinotdb = ["pinotdb (>=0.3,<1.0)"] -postgres = ["GeoAlchemy2 (>=0.12,<1.0)", "packaging (==21.3)", "psycopg2-binary", "pymysql (>=1.0.2)"] -powerbi = ["msal (>=1.2,<2.0)"] -presto = ["presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)"] -pymssql = ["pymssql (>=2.2.0,<2.3.0)"] -qliksense = ["websocket-client (>=1.6.1,<1.7.0)"] -quicksight = ["boto3 (>=1.20,<2.0)"] -redash = ["packaging (==21.3)"] -redpanda = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] -redshift = ["GeoAlchemy2 (>=0.12,<1.0)", "psycopg2-binary", "sqlalchemy-redshift (==0.8.12)"] -sagemaker = ["boto3 (>=1.20,<2.0)"] -salesforce = ["simple-salesforce (==1.11.4)"] -sap-hana = ["hdbcli", "sqlalchemy-hana"] -singlestore = ["pymysql (>=1.0.2)"] -sklearn = ["scikit-learn (>=1.0,<2.0)"] -snowflake = ["snowflake-sqlalchemy (>=1.4,<2.0)"] -tableau = ["tableau-api-lib (>=0.1,<1.0)"] -test = ["apache-airflow (==2.6.3)", "coverage", "databricks-sdk (>=0.1,<1.0)", "dbt-artifacts-parser", "elasticsearch8 (>=8.9.0,<8.10.0)", "giturlparse", "google (>=3.0.0)", "great-expectations (>=0.17.0,<0.18.0)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "moto (==4.0.8)", "pyarrow (>=10.0,<11.0)", "pydomo (>=0.3,<1.0)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pytest (==7.0.0)", "pytest-cov", "pytest-order", "scikit-learn (>=1.0,<2.0)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-redshift (==0.8.12)", "tableau-api-lib (>=0.1,<1.0)", "trino[sqlalchemy]"] -trino = ["trino[sqlalchemy]"] -vertica = ["sqlalchemy-vertica[vertica-python] (>=0.0.5)"] - [[package]] name = "ordered-set" version = "4.1.0" @@ -1815,17 +976,6 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - [[package]] name = "pluggy" version = "1.3.0" @@ -1859,26 +1009,6 @@ python-utils = ">=3.8.1" docs = ["sphinx (>=1.8.5)", "sphinx-autodoc-typehints (>=1.6.0)"] tests = ["dill (>=0.3.6)", "flake8 (>=3.7.7)", "freezegun (>=0.3.11)", "pytest (>=4.6.9)", "pytest-cov (>=2.6.1)", "pytest-mypy", "sphinx (>=1.8.5)"] -[[package]] -name = "protobuf" -version = "4.25.0" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "protobuf-4.25.0-cp310-abi3-win32.whl", hash = "sha256:5c1203ac9f50e4853b0a0bfffd32c67118ef552a33942982eeab543f5c634395"}, - {file = "protobuf-4.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:c40ff8f00aa737938c5378d461637d15c442a12275a81019cc2fef06d81c9419"}, - {file = "protobuf-4.25.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:cf21faba64cd2c9a3ed92b7a67f226296b10159dbb8fbc5e854fc90657d908e4"}, - {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:32ac2100b0e23412413d948c03060184d34a7c50b3e5d7524ee96ac2b10acf51"}, - {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:683dc44c61f2620b32ce4927de2108f3ebe8ccf2fd716e1e684e5a50da154054"}, - {file = "protobuf-4.25.0-cp38-cp38-win32.whl", hash = "sha256:1a3ba712877e6d37013cdc3476040ea1e313a6c2e1580836a94f76b3c176d575"}, - {file = "protobuf-4.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:b2cf8b5d381f9378afe84618288b239e75665fe58d0f3fd5db400959274296e9"}, - {file = "protobuf-4.25.0-cp39-cp39-win32.whl", hash = "sha256:63714e79b761a37048c9701a37438aa29945cd2417a97076048232c1df07b701"}, - {file = "protobuf-4.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:d94a33db8b7ddbd0af7c467475fb9fde0c705fb315a8433c0e2020942b863a1f"}, - {file = "protobuf-4.25.0-py3-none-any.whl", hash = "sha256:1a53d6f64b00eecf53b65ff4a8c23dc95df1fa1e97bb06b8122e5a64f49fc90a"}, - {file = "protobuf-4.25.0.tar.gz", hash = "sha256:68f7caf0d4f012fd194a301420cf6aa258366144d814f358c5b32558228afa7c"}, -] - [[package]] name = "psutil" version = "5.9.6" @@ -1907,123 +1037,121 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] -[[package]] -name = "pyasn1" -version = "0.5.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.3.0" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - [[package]] name = "pydantic" -version = "1.10.13" -description = "Data validation and settings management using python type hints" +version = "2.0" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-2.0-py3-none-any.whl", hash = "sha256:8bf7355be5e1207c756dfbc8046236dadd4ce04101fb482e6c8834a06d9aa04f"}, + {file = "pydantic-2.0.tar.gz", hash = "sha256:6e313661b310eb5b2c45168ce05d8dd79f57563adaf3906162a917585576b846"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.0.1" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] [[package]] -name = "pygments" -version = "2.16.1" -description = "Pygments is a syntax highlighting package written in Python." +name = "pydantic-core" +version = "2.0.1" +description = "" optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pydantic_core-2.0.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:92b01e166a3b69e8054308709acabec1bae65dae83ba6329f4fcc8448e170a06"}, + {file = "pydantic_core-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae53240f9f92f634b73a3e5ee87b9ec8ac38d5bee96ea65034af58f48d489a65"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0dd6bb98271519a309e96e927b52f8ca1323a99762bec87cda8fdaaa221e5cd"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c656b8d4603af6744ed2f2c0be499790f0913a2186ef7214c88d47d42051ae4b"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_armv7l.whl", hash = "sha256:ddbad540cba15b5262bd800bb6f0746a4ac719de0fe0a2acab8e0d50eb54ba9a"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:e2e9025e132761e7ea8dab448923ccd8839c60199e863a6348d7e8b1a674edd1"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_s390x.whl", hash = "sha256:ac6a57d01c0b67563dd273f2b71e9aab643573b569a202bfff7dad502b0b8ee0"}, + {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44c8cec1d74d74c29da59c86e8cd472851c85b44d75128096ef3751c5c87c204"}, + {file = "pydantic_core-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76d5d18ef9065ecbf62d6ec82c45ddbb47174a7400eb780040a7ebdad1c0ead8"}, + {file = "pydantic_core-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:722aa193ba1f587226991a789a3f098235b5f04e85cf815af9e8ad823a5a85e1"}, + {file = "pydantic_core-2.0.1-cp310-none-win32.whl", hash = "sha256:16977790d69bac6034baa2349326db2ff465ad346c53b8d54c3674e05b070af2"}, + {file = "pydantic_core-2.0.1-cp310-none-win_amd64.whl", hash = "sha256:0fcdb43190588f6219709b43ffa679e562c0d4a44a50aafb6cc88978da4a84b7"}, + {file = "pydantic_core-2.0.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:73c464afa0a959472045f242ef7cdaf6a38b76a6d7dfa1ef270de0967c04408d"}, + {file = "pydantic_core-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab7eafb33fdc7aa8667634be58a3d1c8ed3fa8923c6bc5014657bf95b51b4a46"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf3e484bc8e8c8a568d572a6619696d7e2e2aef214b0be503f0814f8bafca9f"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc99af5be239961d718bbf8e4d6bd1caa6de556e44ed08eb5135cfbefc958728"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_armv7l.whl", hash = "sha256:e55fc76ce657208c0d7e21e2e96925993dd4063d5c5ee9227dcdf4e550c02a29"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:ccb06e1667a9784a96e0fc2500b989b8afbe9ac68a39a3c806c056ee228eff3c"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_s390x.whl", hash = "sha256:b23ae8b27b6eff72909a9a88123ac28b746d95f25927ce67d3b0f3dabe099a0a"}, + {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6387c2956baf16891e7bc20d864a769c0f9f61799d4895c8f493e2de8f7b88aa"}, + {file = "pydantic_core-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1c855ef11370eacff25556658fb7fa243e8c0bd4235fa20a0f473bded2ede252"}, + {file = "pydantic_core-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f9452d470012ee86a00a36f7673843038fd1a88661a28c72e65e7f3f084da8d8"}, + {file = "pydantic_core-2.0.1-cp311-none-win32.whl", hash = "sha256:0872a1c52da4cfc494e23c83532c7fc1313de311a14334b7a58216a8dea828e0"}, + {file = "pydantic_core-2.0.1-cp311-none-win_amd64.whl", hash = "sha256:7a4fc3e8c788798739f4aa6772d994e4453a17dadb1b8eea4582a31cdfe683d2"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:ddb23eaf427dbbde41b543d98a0c4a7aeb73bf649e3faa75b94a2fd882a669ba"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:4eda2b350b02293c7060f2371ad3ce7b00342bd61c8654d2ba374bd10c6b6b66"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7727a4fcb93572d4e521b028f1c64f1eda2da49d506b1a6208576faa9e0acd64"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:007cdcee7e1a40951768d0d250e566b603e25d0fa8b8302901e38560bc9badf9"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_armv7l.whl", hash = "sha256:89123ab11a23fa9c332655933350dc231945ca6b1148c1e1960aad0a5a6de1c0"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:03d12c44decb122d5feede5408cc6c67e506b64016ce4b59c825d1a8c90f288a"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_s390x.whl", hash = "sha256:ff015389ae4ca6869a2fdd16c21ee1ce7c134503f2148efd46db643ce27ca520"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8daded5c64811da4bdc7d6792afa10328bff5c3514536f69457596d4a2646b49"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a1dd1b182fde9f95f1cc28964612fb1b180fdd3ca2cac881c108db29906b2e01"}, + {file = "pydantic_core-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c8e53bae6e58a8ff8e93f9a77440cfe8fc017bb9a8430dc03beb6bdd648572d2"}, + {file = "pydantic_core-2.0.1-cp37-none-win32.whl", hash = "sha256:a7d0de538719feda5cabf19c63cc17345df6a0ab579b95518925d2b25276daaf"}, + {file = "pydantic_core-2.0.1-cp37-none-win_amd64.whl", hash = "sha256:1bb6d1057c054056614aefeced05299d3590acf76768538b34ebec9cbbf26953"}, + {file = "pydantic_core-2.0.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9ee1c2d0cf5c92faf722ff366814859c764c82b30af7f91b9b1950e15efecb9e"}, + {file = "pydantic_core-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:adc2efaf0c45135214dff4d18d4aaf2b692249cb369f921fe0fde3a13cf7ddad"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8927c166f20e3933cc9a9a68701acc8de22ee54b70d8c4044ad461b043b3cf9b"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adbfc6c7ddd1cca6efe62a0292cae7cf2d05c9ebb139d0da10b0d44346e253c7"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_armv7l.whl", hash = "sha256:659f22427d653769d1b4c672fd2daf53e639a5a93b0dd6fc0b37ef822a6e77d7"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:71cf43912edeae476f47d16520e48bddbf9af0ebdd98961c38ca8944f4f22b9d"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_s390x.whl", hash = "sha256:10736490eacc426d681ae6f00f1d8ce01fc77c45086a597e829c3eed127179b1"}, + {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5f3158cb4cda580f3b063b03257c7f5c2d9e66f9c2a93466c76056f7c4d5a3b7"}, + {file = "pydantic_core-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ca5a743af642700fc69dc64e0b964dd7499dcabb399e5cc2223fbc9cb33965d"}, + {file = "pydantic_core-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fa5a3d49ddbeaa80bb2a8927b90e9cdd43373616ba0b7b7a74a3ae33b5c9640c"}, + {file = "pydantic_core-2.0.1-cp38-none-win32.whl", hash = "sha256:d6e21da7f7e3935b24bfd17d7c3eefe4c1edca380edaec854a8593796d8d96f1"}, + {file = "pydantic_core-2.0.1-cp38-none-win_amd64.whl", hash = "sha256:0b154abef540a76bb2b7a641b3ae1e05e5c4b08eb9ad6c27a217b3c64ffcda0b"}, + {file = "pydantic_core-2.0.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:aad8b177370002f73f08eafefa3d969d9c4498da6d67d8a43ffdeb4b4e560e1c"}, + {file = "pydantic_core-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9cf1ba93657cad863d23ecb09227665c0abe26c131acd24abb5edc6249a36a70"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79225132aa1fe97a5e947da820b323d63372fb3475d94ff81ca6f91669717a01"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd78c4f04794a8e527d32c8ec1a26682b35b5c9347bb6e3cc853ba1a43c72a5"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_armv7l.whl", hash = "sha256:bb2daa4e3d4efbf2e2dedc1a7cea3e48ff12d0c95ab2011e7f731bdc97d16ed0"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:958964a0ad0cea700b25037b21f5a2da38d19bddaa2f15ce36f51c048a9efe92"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_s390x.whl", hash = "sha256:e0edd3c6762b3ff3fdbd90517a09808e5d67cce86d7c43ec6f5ca3f65bfe7fd9"}, + {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e76a9b0c2b2fb29a80764e106b1ea35c1b96a4e62e7ce7dde44f5df153fd5b66"}, + {file = "pydantic_core-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:88fc72e60d818924cb3d32948b682bcea0dadd0fd2efae9a4d0b7a55e310908a"}, + {file = "pydantic_core-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c04aa22ded4baf29c3c1ec3b76d5264dd91794b974a737251fdd0827abcc2c78"}, + {file = "pydantic_core-2.0.1-cp39-none-win32.whl", hash = "sha256:4372e8fcb458aad1e155c04e663ff1840f36b859fb1422578372712a78866051"}, + {file = "pydantic_core-2.0.1-cp39-none-win_amd64.whl", hash = "sha256:c5fef2dc7ed589ea83ac5ce526fcb8e8eb0ab79bfa67f958dafbda0a05ab3018"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e4785a8c5440e410394f88e30c3db862ed05841595311ddc969b3fde377f95ea"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e90b99b6aa9fd6eee6d6f86921d38252c6e55c319dc6c5e411922d0dc173825"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:182a0e5ce9382a0a77aab8407ead303b6e310c673a46b18937fa1a90c22ccbc4"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b83e11a68936f80ee92ef1001bf6b9fedf0602396acc417b16a9c136a9b3b7bd"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5e4a918eeae2c566fdcad9ee89d8708a59dc5ec3d5083b61a886b19f82f69f5c"}, + {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:88b56a4e7480f4f22fa2faefdb0a887d70420d9cd8cb160677e8abe46769e7b0"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3a85fde791e6567f879b50b59f1740afc55333060d93548d6bbb46bf1b6a1b49"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef349e4ac794559c1538787a0fbce378a1beb991ef4f7707a6cde3156294259d"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f90928ed48b91d93add357fb1e81cef729bffaff3ab88882b76549434b4574"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3f8fea22690c6c33c4d36d2236732da29da560f815cd9aba1d3b5ab59dcb214"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbbefd38ef80b37d056592c366a164a37b4e87b12f0aba23c35087d890fb31ba"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:8945ba48644b45d4e66cc3e56b896e97fb1d7f166dd0ee1eb137bbfdf1285483"}, + {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1c318bd2bdaa88ec078dc7932e108a9c43caeabc84d2cf545081fb6a99ed1b90"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7176ffa02c45d557cceb75f1290a2ddf53da680c6878aae54e69aafb21c52efd"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:176eb3ec03da4c36da7708d2398139e13d1130b3b3d1af4334a959f46278baa9"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46bb28295082a22f3c7f5fa5546d669aed7eb43151ec0032e8c352c59f5e36af"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4b4c836100e5f07189b0aea8b4afae326f169bfdef91e86fd90a0d3c27f0c75"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b56f3758b82f26414a4dccd76f05c768df7bd2735e0ac43f3dfff2f5603d32a9"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0c8877d9e0bd128f103a1b0f02899aa7d4be1104eef5dc35e2b633042b64a2d1"}, + {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1672c8c36414c56adf704753b2d7e22e7528d7bd21cd357f24edeff76d4fd4ca"}, + {file = "pydantic_core-2.0.1.tar.gz", hash = "sha256:f9fffcb5507bff84a1312d1616406cad157806f105d78bd184d1e6b3b00e6417"}, ] -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pymysql" -version = "1.1.0" -description = "Pure Python MySQL Driver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, - {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, +[package.dependencies] +typing-extensions = [ + {version = ">=4.6.0", markers = "platform_python_implementation != \"PyPy\""}, + {version = ">=4.6.0,<4.7.0", markers = "platform_python_implementation == \"PyPy\""}, ] -[package.extras] -ed25519 = ["PyNaCl (>=1.4.0)"] -rsa = ["cryptography"] - [[package]] name = "pyreadline3" version = "3.4.1" @@ -2071,27 +1199,6 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "python-jose" -version = "3.3.0" -description = "JOSE implementation in Python" -optional = false -python-versions = "*" -files = [ - {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, - {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, -] - -[package.dependencies] -ecdsa = "!=0.15" -pyasn1 = "*" -rsa = "*" - -[package.extras] -cryptography = ["cryptography (>=3.4.0)"] -pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] -pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] - [[package]] name = "python-utils" version = "3.8.1" @@ -2209,103 +1316,6 @@ files = [ attrs = ">=22.2.0" rpds-py = ">=0.7.0" -[[package]] -name = "regex" -version = "2023.10.3" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.7" -files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, -] - [[package]] name = "requests" version = "2.31.0" @@ -2327,24 +1337,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "requests-aws4auth" -version = "1.2.3" -description = "AWS4 authentication for Requests" -optional = false -python-versions = ">=3.3" -files = [ - {file = "requests-aws4auth-1.2.3.tar.gz", hash = "sha256:d4c73c19f37f80d4aa9c5bd4fa376cfd0c69299c48b00a8eb2ae6b0416164fb8"}, - {file = "requests_aws4auth-1.2.3-py2.py3-none-any.whl", hash = "sha256:8070a5207e95fa5fe88e87d9a75f34e768cbab35bb3557ef20cbbf9426dee4d5"}, -] - -[package.dependencies] -requests = "*" -six = "*" - -[package.extras] -httpx = ["httpx"] - [[package]] name = "requests-file" version = "1.5.1" @@ -2487,20 +1479,6 @@ files = [ {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, ] -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -2578,23 +1556,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] -[[package]] -name = "s3transfer" -version = "0.7.0" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">= 3.7" -files = [ - {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, - {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, -] - -[package.dependencies] -botocore = ">=1.12.36,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] - [[package]] name = "sentry-sdk" version = "1.39.1" @@ -2640,22 +1601,6 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] -[[package]] -name = "setuptools" -version = "66.0.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "setuptools-66.0.0-py3-none-any.whl", hash = "sha256:a78d01d1e2c175c474884671dde039962c9d74c7223db7369771fcf6e29ceeab"}, - {file = "setuptools-66.0.0.tar.gz", hash = "sha256:bd6eb2d6722568de6d14b87c44a96fac54b2a45ff5e940e639979a3d1792adb6"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -2667,134 +1612,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - -[[package]] -name = "sqlalchemy" -version = "1.4.50" -description = "Database Abstraction Library" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "SQLAlchemy-1.4.50-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:54138aa80d2dedd364f4e8220eef284c364d3270aaef621570aa2bd99902e2e8"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00665725063692c42badfd521d0c4392e83c6c826795d38eb88fb108e5660e5"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85292ff52ddf85a39367057c3d7968a12ee1fb84565331a36a8fead346f08796"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0fed0f791d78e7767c2db28d34068649dfeea027b83ed18c45a423f741425cb"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db4db3c08ffbb18582f856545f058a7a5e4ab6f17f75795ca90b3c38ee0a8ba4"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-win32.whl", hash = "sha256:6c78e3fb4a58e900ec433b6b5f4efe1a0bf81bbb366ae7761c6e0051dd310ee3"}, - {file = "SQLAlchemy-1.4.50-cp310-cp310-win_amd64.whl", hash = "sha256:d55f7a33e8631e15af1b9e67c9387c894fedf6deb1a19f94be8731263c51d515"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:324b1fdd50e960a93a231abb11d7e0f227989a371e3b9bd4f1259920f15d0304"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14b0cacdc8a4759a1e1bd47dc3ee3f5db997129eb091330beda1da5a0e9e5bd7"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fb9cb60e0f33040e4f4681e6658a7eb03b5cb4643284172f91410d8c493dace"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-win32.whl", hash = "sha256:8bdab03ff34fc91bfab005e96f672ae207d87e0ac7ee716d74e87e7046079d8b"}, - {file = "SQLAlchemy-1.4.50-cp311-cp311-win_amd64.whl", hash = "sha256:52e01d60b06f03b0a5fc303c8aada405729cbc91a56a64cead8cb7c0b9b13c1a"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:77fde9bf74f4659864c8e26ac08add8b084e479b9a18388e7db377afc391f926"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cb501d585aa74a0f86d0ea6263b9c5e1d1463f8f9071392477fd401bd3c7cc"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7a66297e46f85a04d68981917c75723e377d2e0599d15fbe7a56abed5e2d75"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-win32.whl", hash = "sha256:e86c920b7d362cfa078c8b40e7765cbc34efb44c1007d7557920be9ddf138ec7"}, - {file = "SQLAlchemy-1.4.50-cp312-cp312-win_amd64.whl", hash = "sha256:6b3df20fbbcbcd1c1d43f49ccf3eefb370499088ca251ded632b8cbaee1d497d"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:fb9adc4c6752d62c6078c107d23327aa3023ef737938d0135ece8ffb67d07030"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1db0221cb26d66294f4ca18c533e427211673ab86c1fbaca8d6d9ff78654293"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7dbe6369677a2bea68fe9812c6e4bbca06ebfa4b5cde257b2b0bf208709131"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9bddb60566dc45c57fd0a5e14dd2d9e5f106d2241e0a2dc0c1da144f9444516"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82dd4131d88395df7c318eeeef367ec768c2a6fe5bd69423f7720c4edb79473c"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-win32.whl", hash = "sha256:1b9c4359d3198f341480e57494471201e736de459452caaacf6faa1aca852bd8"}, - {file = "SQLAlchemy-1.4.50-cp36-cp36m-win_amd64.whl", hash = "sha256:35e4520f7c33c77f2636a1e860e4f8cafaac84b0b44abe5de4c6c8890b6aaa6d"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:f5b1fb2943d13aba17795a770d22a2ec2214fc65cff46c487790192dda3a3ee7"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273505fcad22e58cc67329cefab2e436006fc68e3c5423056ee0513e6523268a"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3257a6e09626d32b28a0c5b4f1a97bced585e319cfa90b417f9ab0f6145c33c"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d69738d582e3a24125f0c246ed8d712b03bd21e148268421e4a4d09c34f521a5"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34e1c5d9cd3e6bf3d1ce56971c62a40c06bfc02861728f368dcfec8aeedb2814"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-win32.whl", hash = "sha256:7b4396452273aedda447e5aebe68077aa7516abf3b3f48408793e771d696f397"}, - {file = "SQLAlchemy-1.4.50-cp37-cp37m-win_amd64.whl", hash = "sha256:752f9df3dddbacb5f42d8405b2d5885675a93501eb5f86b88f2e47a839cf6337"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:35c7ed095a4b17dbc8813a2bfb38b5998318439da8e6db10a804df855e3a9e3a"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1fcee5a2c859eecb4ed179edac5ffbc7c84ab09a5420219078ccc6edda45436"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbaf6643a604aa17e7a7afd74f665f9db882df5c297bdd86c38368f2c471f37d"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e70e0673d7d12fa6cd363453a0d22dac0d9978500aa6b46aa96e22690a55eab"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b881ac07d15fb3e4f68c5a67aa5cdaf9eb8f09eb5545aaf4b0a5f5f4659be18"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-win32.whl", hash = "sha256:8a219688297ee5e887a93ce4679c87a60da4a5ce62b7cb4ee03d47e9e767f558"}, - {file = "SQLAlchemy-1.4.50-cp38-cp38-win_amd64.whl", hash = "sha256:a648770db002452703b729bdcf7d194e904aa4092b9a4d6ab185b48d13252f63"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:4be4da121d297ce81e1ba745a0a0521c6cf8704634d7b520e350dce5964c71ac"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6997da81114daef9203d30aabfa6b218a577fc2bd797c795c9c88c9eb78d49"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdb77e1789e7596b77fd48d99ec1d2108c3349abd20227eea0d48d3f8cf398d9"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:128a948bd40780667114b0297e2cc6d657b71effa942e0a368d8cc24293febb3"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d526aeea1bd6a442abc7c9b4b00386fd70253b80d54a0930c0a216230a35be"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-win32.whl", hash = "sha256:a7c9b9dca64036008962dd6b0d9fdab2dfdbf96c82f74dbd5d86006d8d24a30f"}, - {file = "SQLAlchemy-1.4.50-cp39-cp39-win_amd64.whl", hash = "sha256:df200762efbd672f7621b253721644642ff04a6ff957236e0e2fe56d9ca34d2c"}, - {file = "SQLAlchemy-1.4.50.tar.gz", hash = "sha256:3b97ddf509fc21e10b09403b5219b06c5b558b27fc2453150274fa4e70707dbf"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] - -[[package]] -name = "sqlfluff" -version = "2.1.4" -description = "The SQL Linter for Humans" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sqlfluff-2.1.4-py3-none-any.whl", hash = "sha256:3884e375b9a884485263cf156b21ff55a38adadb7be6451e27ee6b8f1a75f018"}, - {file = "sqlfluff-2.1.4.tar.gz", hash = "sha256:86ee197cb6919e50962c4def5f64ba8bafb35b1ffab943b2e87fca137e3df2a2"}, -] - -[package.dependencies] -appdirs = "*" -chardet = "*" -click = "*" -colorama = ">=0.3" -diff-cover = ">=2.5.0" -Jinja2 = "*" -pathspec = "*" -pytest = "*" -pyyaml = ">=5.1" -regex = "*" -tblib = "*" -toml = {version = "*", markers = "python_version < \"3.11\""} -tqdm = "*" -typing-extensions = "*" - -[[package]] -name = "sqlparse" -version = "0.4.3" -description = "A non-validating SQL parser." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, - {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, -] - [[package]] name = "tabulate" version = "0.9.0" @@ -2809,17 +1626,6 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tblib" -version = "3.0.0" -description = "Traceback serialization library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"}, - {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"}, -] - [[package]] name = "termcolor" version = "2.4.0" @@ -2857,45 +1663,25 @@ files = [ ] [[package]] -name = "tqdm" -version = "4.66.1" -description = "Fast, Extensible Progress Meter" +name = "typing-extensions" +version = "4.6.3" +description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "typing-compat" -version = "0.1.0" -description = "Python typing compatibility library" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "typing-compat-0.1.0.tar.gz", hash = "sha256:a7159db80406de342bc128ab315fae3afe1ddc87cbc2df7a023763090fdfe5d2"}, - {file = "typing_compat-0.1.0-py2.py3-none-any.whl", hash = "sha256:a8b94eb34c95982fbd34b8daa46fd78c43db33c075b98c79b6e1d77e8f7dd4d5"}, + {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, + {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, ] [[package]] name = "typing-extensions" -version = "4.5.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -2930,20 +1716,6 @@ secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17. socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "wheel" -version = "0.38.4" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, -] - -[package.extras] -test = ["pytest (>=3.0.0)"] - [[package]] name = "wrapt" version = "1.16.0" @@ -3126,22 +1898,7 @@ files = [ idna = ">=2.0" multidict = ">=4.0" -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "5efc740c64f8cf9732e86742152e6576a4231ad85cbdeae30350f8394a25a023" +content-hash = "3961dc21b5a8bab7e68d7e692051f52bed11968d359e023fdb75904707700fa3" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index eae504b5..a2c9a4e2 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -1,6 +1,16 @@ +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.isort] +profile = "black" + +[tool.pylint] +max-line-length = 120 + [tool.poetry] name = "ministryofjustice-data-platform-catalogue" -version = "0.24.2" +version = "1.0.0" description = "Library to integrate the MoJ data platform with the catalogue component." authors = ["MoJ Data Platform Team "] license = "MIT" @@ -9,15 +19,12 @@ packages = [{ include = "data_platform_catalogue" }] [tool.poetry.dependencies] python = "^3.10" -openmetadata-ingestion = "~1.2.0.1" acryl-datahub = { extras = ["datahub-rest"], version = "^0.12.1.3" } freezegun = "^1.4.0" deepdiff = "^6.7.1" +pydantic = "2" [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" pytest = "^7.4.2" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 2ea4e91e..939afe12 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -3,25 +3,28 @@ from unittest.mock import MagicMock, patch import pytest -from data_platform_catalogue.client.datahub.datahub_client import ( +from data_platform_catalogue.client.datahub_client import ( DataHubCatalogueClient, InvalidDomain, - MissingDatabaseMetadata, ) +from data_platform_catalogue.client.exceptions import ReferencedEntityMissing from data_platform_catalogue.entities import ( - CatalogueMetadata, - ChartMetadata, - DatabaseMetadata, - DatabaseStatus, - DataLocation, - DataProductMetadata, - DataProductStatus, - RelatedEntity, + AccessInformation, + Chart, + Column, + ColumnRef, + CustomEntityProperties, + Database, + DataSummary, + DomainRef, + EntityRef, + Governance, + OwnerRef, RelationshipType, - SecurityClassification, - TableMetadata, + Table, + TagRef, + UsageRestrictions, ) -from datahub.metadata.schema_classes import DataProductPropertiesClass class TestCatalogueClientWithDatahub: @@ -31,90 +34,176 @@ class TestCatalogueClientWithDatahub: If this is the case, then the final metadata graph should match a snapshot we took earlier. """ - @pytest.fixture - def catalogue(self): - return CatalogueMetadata( - name="data_platform", - description="All data products hosted on the data platform", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - ) - - @pytest.fixture - def data_product(self): - return DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - @pytest.fixture def database(self): - return DatabaseMetadata( + return Database( name="my_database", description="little test db", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DatabaseStatus.PROD.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), + governance=Governance( + data_owner=OwnerRef( + urn="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + display_name="April Gonzalez", + email="abc@digital.justice.gov.uk", + ), + data_stewards=[ + OwnerRef( + urn="abc", + display_name="Jonjo Shelvey", + email="j.shelvey@digital.justice.gov.uk", + ) + ], + ), + domain=DomainRef(urn="LAA", display_name="LAA"), + last_modified=datetime(2020, 5, 17), creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], + access_information=AccessInformation( + s3_location="s3://databucket/", + ), + tags=[TagRef(urn="test", display_name="test")], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + dpia_required=False, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="analytical_platform", + s3_location="s3://databucket/", + ), + ), ) @pytest.fixture def table(self): - return TableMetadata( - name="my_table", - description="bla bla", + return Table( + urn=None, + display_name="Foo.Dataset", + name="Dataset", + fully_qualified_name="Foo.Dataset", + description="Dataset", + relationships={ + RelationshipType.PARENT: [ + EntityRef( + urn="urn:li:container:my_database", display_name="database" + ) + ] + }, + domain=DomainRef(display_name="LAA", urn="LAA"), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) + ], + ), ], - retention_period_in_days=365, - source_dataset_name="my_source_table", - where_to_access_dataset="s3://databucket/table1", - data_sensitivity_level=SecurityClassification.OFFICIAL, - parent_entity_name="my_database", - domain="LAA", + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(row_count=5), + usage_restrictions=UsageRestrictions( + dpia_required=True, + dpia_location=None, + ), + ), ) @pytest.fixture def table2(self): - return TableMetadata( - name="my_table2", - description="this is a different table", + return Table( + urn=None, + display_name="Foo.Dataset", + name="Dataset", + fully_qualified_name="Foo.Dataset", + description="Dataset", + relationships={ + RelationshipType.PARENT: [ + EntityRef( + urn="urn:li:container:my_database", display_name="database" + ) + ] + }, + domain=DomainRef(display_name="LAA", urn="LAA"), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, column_details=[ - {"name": "boo", "type": "boolean", "description": "spooky"}, - {"name": "yar", "type": "string", "description": "shiver my timbers"}, + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[], + ), + Column( + name="upstreamLineage", + display_name="upstreamLineage", + type="upstreamLineage", + description="Upstream lineage of a dataset", + nullable=False, + is_primary_key=False, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) + ], + ), ], - retention_period_in_days=1, - source_dataset_name="my_source_table", - where_to_access_dataset="s3://databucket/table2", - data_sensitivity_level=SecurityClassification.OFFICIAL, + platform=EntityRef(urn="athena", display_name="athena"), + custom_properties=CustomEntityProperties( + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(row_count=5), + usage_restrictions=UsageRestrictions( + dpia_required=True, + dpia_location=None, + ), + ), ) @pytest.fixture @@ -135,143 +224,6 @@ def golden_file_in_db(self): Path(__file__).parent / "../../test_resources/golden_database_in.json" ) - def test_create_table_datahub( - self, - datahub_client, - base_mock_graph, - table, - tmp_path, - check_snapshot, - golden_file_in_dp, - ): - """ - Case where we just create a dataset (no data product) - """ - mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in_dp) - - fqn = datahub_client.upsert_table( - metadata=table, - location=DataLocation(fully_qualified_name="my_database"), - ) - fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - - assert fqn == fqn_out - - output_file = Path(tmp_path / "datahub_create_table.json") - base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_table.json", output_file) - - def test_create_table_with_metadata_datahub( - self, - datahub_client, - table, - data_product, - base_mock_graph, - tmp_path, - check_snapshot, - golden_file_in_dp, - ): - """ - Case where we create a dataset, data product and domain - """ - mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in_dp) - - fqn = datahub_client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("my_database"), - ) - fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - - assert fqn == fqn_out - - # check data product properties persist - dp_properties = mock_graph.get_aspect( - "urn:li:dataProduct:my_data_product", aspect_type=DataProductPropertiesClass - ) - assert dp_properties.description == "bla bla" - assert dp_properties.customProperties == {"version": "2.0", "dpia": "false"} - output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") - base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_table_with_metadata.json", output_file) - - def test_create_two_tables_with_metadata( - self, - datahub_client, - table, - table2, - data_product, - base_mock_graph, - tmp_path, - check_snapshot, - golden_file_in_dp, - ): - """ - Case where we create a dataset, data product and domain - """ - - mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in_dp) - - fqn = datahub_client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("my_database"), - ) - fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - - assert fqn == fqn_out - - fqn = datahub_client.upsert_table( - metadata=table2, - data_product_metadata=data_product, - location=DataLocation("my_database"), - ) - fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)" - - assert fqn == fqn_out - - output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") - base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_two_tables_with_metadata.json", output_file) - - def test_create_table_and_metadata_idempotent_datahub( - self, - datahub_client, - table, - data_product, - base_mock_graph, - tmp_path, - check_snapshot, - golden_file_in_dp, - ): - """ - `create_table` should work even if the entities already exist in the metadata graph. - """ - mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in_dp) - - datahub_client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("my_database"), - ) - - fqn = datahub_client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("my_database"), - ) - fqn_out = "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - - assert fqn == fqn_out - - output_file = Path(tmp_path / "datahub_create_table_with_metadata.json") - base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_table_with_metadata.json", output_file) - def test_get_dataset( self, datahub_client, @@ -288,7 +240,7 @@ def test_get_dataset( "relationships": [ { "entity": { - "urn": "urn:li:container:databse", + "urn": "urn:li:container:database", "properties": {"name": "database"}, } } @@ -301,7 +253,7 @@ def test_get_dataset( "qualifiedName": "Foo.Dataset", "description": "Dataset", "customProperties": [ - {"key": "sensitivityLevel", "value": "OFFICIAL-SENSITIVE"} + {"key": "sensitivityLevel", "value": "OFFICIAL"} ], "lastModified": {"time": 1709619407814}, }, @@ -328,14 +280,6 @@ def test_get_dataset( "type": "STRING", "nativeDataType": "string", }, - { - "fieldPath": "upstreamLineage", - "label": None, - "nullable": False, - "description": "Upstream lineage of a dataset", - "type": "STRUCT", - "nativeDataType": "upstreamLineage", - }, ], "primaryKeys": ["urn"], "foreignKeys": [ @@ -349,57 +293,70 @@ def test_get_dataset( "qualifiedName": None, }, }, - "sourceFields": [{"fieldPath": "upstreamLineage"}], + "sourceFields": [{"fieldPath": "urn"}], }, ], }, } } base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + with patch( + "data_platform_catalogue.client.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + ) as mock_exists: + mock_exists.return_value = True + dataset = datahub_client.get_table_details(urn) - dataset = datahub_client.get_table_details(urn) - - assert dataset == TableMetadata( + assert dataset == Table( + urn=None, + display_name="Foo.Dataset", name="Dataset", - description="Dataset", fully_qualified_name="Foo.Dataset", - column_details=[ - { - "name": "urn", - "type": "string", - "description": "The primary identifier for the dataset entity.", - "isPrimaryKey": True, - "foreignKeys": [], - "nullable": False, - }, - { - "name": "upstreamLineage", - "type": "upstreamLineage", - "description": "Upstream lineage of a dataset", - "foreignKeys": [ - { - "tableId": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", - "fieldName": "urn", - "tableName": "Dataset", - } - ], - "isPrimaryKey": False, - "nullable": False, - }, - ], - retention_period_in_days=None, - source_dataset_name="", - where_to_access_dataset="", - data_sensitivity_level=SecurityClassification.OFFICIAL, - tags=["some-tag"], - major_version=1, + description="Dataset", relationships={ RelationshipType.PARENT: [ - RelatedEntity(id="urn:li:container:databse", name="database") + EntityRef(urn="urn:li:container:database", display_name="database") ] }, - domain="", - last_updated=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + domain=DomainRef(display_name="", urn=""), + governance=Governance( + data_owner=OwnerRef(display_name="", email="", urn=""), + data_stewards=[OwnerRef(display_name="", email="", urn="")], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, + platform=EntityRef(urn="datahub", display_name="datahub"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + status=None, + dpia_required=None, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(), + ), + column_details=[ + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) + ], + ) + ], ) def test_get_dataset_minimal_properties( @@ -422,22 +379,40 @@ def test_get_dataset_minimal_properties( } base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) - dataset = datahub_client.get_table_details(urn) + with patch( + "data_platform_catalogue.client.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + ) as mock_exists: + mock_exists.return_value = True + dataset = datahub_client.get_table_details(urn) - assert dataset == TableMetadata( + assert dataset == Table( + urn=None, + display_name="notinproperties", name="notinproperties", fully_qualified_name="notinproperties", description="", - column_details=[], - retention_period_in_days=None, - source_dataset_name="", - where_to_access_dataset="", - data_sensitivity_level=SecurityClassification.OFFICIAL, - tags=[], - major_version=1, relationships={}, - domain="", - last_updated=None, + domain=DomainRef(display_name="", urn=""), + governance=Governance( + data_owner=OwnerRef(display_name="", email="", urn=""), + data_stewards=[OwnerRef(display_name="", email="", urn="")], + ), + tags=[], + last_modified=None, + created=None, + platform=EntityRef(urn="datahub", display_name="datahub"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + status=None, + dpia_required=None, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(), + ), + column_details=[], ) def test_get_chart_details(self, datahub_client, base_mock_graph): @@ -461,14 +436,47 @@ def test_get_chart_details(self, datahub_client, base_mock_graph): } base_mock_graph.execute_graphql = MagicMock(return_value=datahub_response) - chart = datahub_client.get_chart_details(urn) - assert chart == ChartMetadata( + with patch( + "data_platform_catalogue.client.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + ) as mock_exists: + mock_exists.return_value = True + chart = datahub_client.get_chart_details(urn) + + assert chart == Chart( + urn="urn:li:chart:(justice-data,absconds)", + display_name="Absconds", name="Absconds", + fully_qualified_name="Absconds", description="a test description", + relationships={}, + domain=DomainRef(display_name="", urn=""), + governance=Governance( + data_owner=OwnerRef(display_name="", email="", urn=""), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=[], + last_modified=None, + created=None, + platform=EntityRef(urn="justice-data", display_name="justice-data"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + status=None, + dpia_required=None, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(), + ), external_url="https://data.justice.gov.uk/prisons/public-protection/absconds", ) - def test_create_athena_database_and_table( + def test_upsert_table_and_database( self, datahub_client, base_mock_graph, @@ -481,68 +489,61 @@ def test_create_athena_database_and_table( """ Case where we create separate database and table """ - mock_graph = base_mock_graph - mock_graph.import_file(golden_file_in_db) + base_mock_graph.import_file(golden_file_in_db) with patch( - "data_platform_catalogue.client.datahub.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + "data_platform_catalogue.client.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" ) as mock_exists: mock_exists.return_value = True - fqn_db = datahub_client.upsert_athena_database(metadata=database) - fqn_t = datahub_client.upsert_athena_table(metadata=table) + fqn_db = datahub_client.upsert_database(database=database) + fqn_t = datahub_client.upsert_table(table=table) fqn_db_out = "urn:li:container:my_database" assert fqn_db == fqn_db_out - fqn_t_out = ( - "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)" - ) + fqn_t_out = "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)" assert fqn_t == fqn_t_out - output_file = Path(tmp_path / "datahub_create_athena_table.json") + output_file = Path(tmp_path / "test_upsert_table_and_database.json") base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_athena_table.json", output_file) + check_snapshot("test_upsert_table_and_database.json", output_file) - def test_create_athena_table_with_metadata( + def test_upsert_table( self, datahub_client, table, - database, base_mock_graph, tmp_path, check_snapshot, golden_file_in_db, ): """ - Case where we create a dataset (athena table) and container (athena database) - via upsert_athena_table method + Case where we create a dataset via upsert_table method """ mock_graph = base_mock_graph mock_graph.import_file(golden_file_in_db) with patch( - "data_platform_catalogue.client.datahub.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" + "data_platform_catalogue.client.datahub_client.DataHubCatalogueClient.check_entity_exists_by_urn" ) as mock_exists: mock_exists.return_value = True - fqn = datahub_client.upsert_athena_table( - metadata=table, - database_metadata=database, + fqn = datahub_client.upsert_table( + table=table, ) - fqn_out = ( - "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)" - ) + fqn_out = "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)" assert fqn == fqn_out - output_file = Path(tmp_path / "datahub_create_athena_table_with_metadata.json") + output_file = Path(tmp_path / "test_upsert_table.json") base_mock_graph.sink_to_file(output_file) - check_snapshot("datahub_create_athena_table_with_metadata.json", output_file) + check_snapshot("test_upsert_table.json", output_file) def test_domain_does_not_exist_error(self, datahub_client, database): with pytest.raises(InvalidDomain): - datahub_client.upsert_athena_database(metadata=database) + datahub_client.upsert_database(database=database) - def test_database_not_exist_with_no_metadata_given_error( - self, datahub_client, table - ): - with pytest.raises(MissingDatabaseMetadata): - datahub_client.upsert_athena_table(metadata=table) + def test_database_not_exist_given_error(self, datahub_client, table, database): + with pytest.raises(ReferencedEntityMissing): + datahub_client.upsert_table(table=table) + + def test_get_custom_property_key_value_pairs(self, datahub_client, database): + datahub_client._get_custom_property_key_value_pairs(database.custom_properties) diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py index 643d3f62..33276563 100644 --- a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -1,11 +1,21 @@ from datetime import datetime, timezone -from data_platform_catalogue.client.datahub.graphql_helpers import ( +from data_platform_catalogue.client.graphql_helpers import ( parse_columns, parse_created_and_modified, + parse_properties, parse_relations, ) -from data_platform_catalogue.entities import RelatedEntity, RelationshipType +from data_platform_catalogue.entities import ( + AccessInformation, + Column, + ColumnRef, + CustomEntityProperties, + DataSummary, + EntityRef, + RelationshipType, + UsageRestrictions, +) def test_parse_columns_with_primary_key_and_foreign_key(): @@ -21,7 +31,7 @@ def test_parse_columns_with_primary_key_and_foreign_key(): "nativeDataType": "string", }, { - "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "fieldPath": "upstream.upstreamLineage", "label": None, "nullable": False, "description": "Upstream lineage of a dataset", @@ -38,39 +48,40 @@ def test_parse_columns_with_primary_key_and_foreign_key(): "urn": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", "properties": {"name": "Dataset", "qualifiedName": None}, }, - "sourceFields": [ - { - "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage" - } - ], + "sourceFields": [{"fieldPath": "upstream.upstreamLineage"}], } ], } } assert parse_columns(entity) == [ - { - "name": "urn", - "type": "string", - "isPrimaryKey": True, - "foreignKeys": [], - "nullable": False, - "description": "The primary identifier for the dataset entity.", - }, - { - "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", - "type": "upstreamLineage", - "description": "Upstream lineage of a dataset", - "nullable": False, - "isPrimaryKey": False, - "foreignKeys": [ - { - "tableId": "urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", - "fieldName": "urn", - "tableName": "Dataset", - } + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[], + ), + Column( + name="upstream.upstreamLineage", + display_name="upstreamLineage", + type="upstreamLineage", + description="Upstream lineage of a dataset", + nullable=False, + is_primary_key=False, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) ], - }, + ), ] @@ -87,7 +98,7 @@ def test_parse_columns_with_no_keys(): "nativeDataType": "string", }, { - "fieldPath": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", + "fieldPath": "upstreamLineage", "label": None, "nullable": False, "description": "Upstream lineage of a dataset", @@ -101,22 +112,24 @@ def test_parse_columns_with_no_keys(): } assert parse_columns(entity) == [ - { - "name": "[version=2.0].[type=dataset].[type=UpstreamLineage].upstreamLineage", - "type": "upstreamLineage", - "description": "Upstream lineage of a dataset", - "nullable": False, - "isPrimaryKey": False, - "foreignKeys": [], - }, - { - "name": "urn", - "type": "string", - "isPrimaryKey": False, - "foreignKeys": [], - "nullable": False, - "description": "The primary identifier for the dataset entity.", - }, + Column( + name="upstreamLineage", + display_name="upstreamLineage", + type="upstreamLineage", + description="Upstream lineage of a dataset", + nullable=False, + is_primary_key=False, + foreign_keys=[], + ), + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=False, + foreign_keys=[], + ), ] @@ -144,7 +157,7 @@ def test_parse_relations(): result = parse_relations(RelationshipType.PARENT, relations["relationships"]) assert result == { RelationshipType.PARENT: [ - RelatedEntity(id="urn:li:dataProduct:test", name="test") + EntityRef(urn="urn:li:dataProduct:test", display_name="test") ] } @@ -165,3 +178,43 @@ def test_parse_created_and_modified(): assert created == datetime(2024, 3, 14, 14, 35, 20, tzinfo=timezone.utc) assert modified == datetime(2024, 3, 14, 14, 35, 21, tzinfo=timezone.utc) + + +def test_parse_properties(): + entity = { + "properties": { + "customProperties": [ + {"key": "dpia_required", "value": False}, + {"key": "dpia_location", "value": None}, + {"key": "data_sensitivity_level", "value": "OFFICIAL"}, + {"key": "where_to_access_dataset", "value": "analytical_platform"}, + {"key": "source_dataset_name", "value": ""}, + {"key": "s3_location", "value": "s3://databucket/"}, + {"key": "row_count", "value": 100}, + {"key": "Not_IN", "value": "dddd"}, + ], + "name": "test", + "description": "test description", + }, + "editableProperties": {"edit1": "q"}, + } + properties, custom_properties = parse_properties(entity) + + assert properties == { + "name": "test", + "description": "test description", + "edit1": "q", + } + + assert custom_properties == CustomEntityProperties( + usage_restrictions=UsageRestrictions( + dpia_required=False, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="analytical_platform", + source_dataset_name="", + s3_location="s3://databucket/", + ), + data_summary=DataSummary(row_count=100), + ) diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 6c9f57eb..8216084b 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -2,8 +2,12 @@ from unittest.mock import MagicMock import pytest -from data_platform_catalogue.client.datahub.search import SearchClient -from data_platform_catalogue.entities import RelatedEntity +from data_platform_catalogue.client.search import SearchClient +from data_platform_catalogue.entities import ( + AccessInformation, + DataSummary, + UsageRestrictions, +) from data_platform_catalogue.search_types import ( FacetOption, MultiSelectFilter, @@ -40,6 +44,26 @@ def test_empty_search_results(mock_graph, searcher): assert response == SearchResponse(total_results=0, page_results=[]) +def test_no_search_results(mock_graph, searcher): + datahub_response = { + "searchAcrossEntities": { + "start": 0, + "count": 0, + "total": 0, + "searchResults": [], + } + } + mock_graph.execute_graphql = MagicMock(return_value=datahub_response) + + response = searcher.search() + expected = SearchResponse( + total_results=0, + page_results=[], + facets=SearchFacets(facets={}), + ) + assert response == expected + + def test_one_search_result(mock_graph, searcher): datahub_response = { "searchAcrossEntities": { @@ -49,14 +73,17 @@ def test_one_search_result(mock_graph, searcher): "searchResults": [ { "entity": { - "type": "DATA_PRODUCT", - "urn": "urn:li:dataProduct:6cc5cbc4-c002-42c3-b80b-ed55df17d39f", + "type": "DATASET", + "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 + "platform": {"name": "bigquery"}, "ownership": None, + "name": "calm-pagoda-323403.jaffle_shop.customers", "properties": { - "name": "Use of force", - "description": "Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 - "customProperties": [], - "numAssets": 7, + "name": "customers", + "qualifiedName": "jaffle_shop.customers", + "customProperties": [ + {"key": "dataSensitivity", "value": "OFFICIAL"}, + ], }, "domain": { "domain": { @@ -64,24 +91,14 @@ def test_one_search_result(mock_graph, searcher): "id": "3dc18e48-c062-4407-84a9-73e23f768023", "properties": { "name": "HMPPS", - "description": "HMPPS is an executive agency that carries out sentences given by the courts, in custody and the community, and rehabilitates people through education and employment.", # noqa E501 + "description": "HMPPS is an executive agency that ...", }, - } - }, - "tags": { - "tags": [ - { - "tag": { - "urn": "urn:li:tag:custody", - "properties": { - "name": "custody", - "description": "Data about prisons and prisoners. Not just NOMIS!", - }, - } - } - ] + }, + "editableProperties": None, + "tags": None, + "lastIngested": 1705990502353, }, - } + }, } ], } @@ -89,27 +106,42 @@ def test_one_search_result(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:dataProduct:6cc5cbc4-c002-42c3-b80b-ed55df17d39f", + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + result_type=ResultType.TABLE, + name="customers", + fully_qualified_name="jaffle_shop.customers", + description="", matches={}, - result_type=ResultType.DATA_PRODUCT, - name="Use of force", - fully_qualified_name="Use of force", - description="Prisons in England and Wales are required to record all instances of Use of Force within their establishment. Use of Force can be planned or unplanned and may involve various categories of control and restraint (C&R) techniques such as physical restraint or handcuffs.\n\nPlease refer to [PSO 1600](https://www.gov.uk/government/publications/use-of-force-in-prisons-pso-1600) for the current guidance.", # noqa E501 metadata={ - "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", - "domain_name": "HMPPS", "owner": "", "owner_email": "", - "number_of_assets": 7, + "total_parents": 0, + "parents": [], + "domain_name": "HMPPS", + "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, - tags=["custody"], + tags=[], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + assert response == expected def test_dataset_result(mock_graph, searcher): @@ -160,16 +192,16 @@ def test_dataset_result(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", - matches={}, + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", fully_qualified_name="jaffle_shop.customers", description="", + matches={}, metadata={ "owner": "", "owner_email": "", @@ -177,17 +209,25 @@ def test_dataset_result(mock_graph, searcher): "parents": [], "domain_name": "HMPPS", "domain_id": "urn:li:domain:3dc18e48-c062-4407-84a9-73e23f768023", - "StoredAsSubDirectories": "False", - "CreatedByJob": "moj-reg-prod-hmpps-assess-risks-and-needs-prod-glue-job", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], - ), + last_modified=None, + created=None, + ) ], + facets=SearchFacets(facets={}), ) + assert response == expected def test_full_page(mock_graph, searcher): @@ -243,80 +283,104 @@ def test_full_page(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=5, page_results=[ SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", - matches={}, + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", fully_qualified_name="jaffle_shop.customers", description="", + matches={}, metadata={ "owner": "", "owner_email": "", - "parents": [], "total_parents": 0, + "parents": [], "domain_name": "", "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], - last_updated=datetime( + last_modified=datetime( 2024, 1, 23, 6, 15, 2, 353000, tzinfo=timezone.utc ), + created=None, ), SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", - matches={}, + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", result_type=ResultType.TABLE, name="customers2", - fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers2", + fully_qualified_name=None, description="", + matches={}, metadata={ "owner": "", "owner_email": "", - "parents": [], "total_parents": 0, + "parents": [], "domain_name": "", "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], - last_updated=None, + last_modified=None, + created=None, ), SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers3,PROD)", - matches={}, + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers3,PROD)", result_type=ResultType.TABLE, name="customers3", fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers3", description="", + matches={}, metadata={ "owner": "", "owner_email": "", - "parents": [], "total_parents": 0, + "parents": [], "domain_name": "", "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], - last_updated=None, + last_modified=None, + created=None, ), ], + facets=SearchFacets(facets={}), ) + assert response == expected + def test_query_match(mock_graph, searcher): datahub_response = { @@ -354,36 +418,46 @@ def test_query_match(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", + result_type=ResultType.TABLE, + name="customers", + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", + description="", matches={ "urn": "urn:li:dataset:(urn:li:dataPlatform:looker,long_tail_companions.view.customer_focused,PROD)", # noqa E501 "name": "customer_focused", "sensitivityLevel": "OFFICIAL", }, - result_type=ResultType.TABLE, - name="customers", - fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", - description="", metadata={ "owner": "", "owner_email": "", - "parents": [], "total_parents": 0, - "domain_id": "", + "parents": [], "domain_name": "", + "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + assert expected == response def test_result_with_owner(mock_graph, searcher): @@ -423,33 +497,44 @@ def test_result_with_owner(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", - matches={}, + urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", + matches={}, metadata={ "owner": "Shannon Lovett", "owner_email": "shannon@longtail.com", - "parents": [], "total_parents": 0, - "domain_id": "", + "parents": [], "domain_name": "", + "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Dataset"], }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, tags=[], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + assert response == expected + def test_filter(searcher, mock_graph): datahub_response = { @@ -659,139 +744,6 @@ def test_search_results_with_facets(searcher, mock_graph): ) -def test_result_with_data_product(mock_graph, searcher): - datahub_response = { - "searchAcrossEntities": { - "start": 0, - "count": 1, - "total": 1, - "searchResults": [ - { - "entity": { - "type": "DATASET", - "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 - "name": "calm-pagoda-323403.jaffle_shop.customers", - "subType": None, - "relationships": { - "total": 1, - "relationships": [ - { - "entity": { - "urn": "urn:abc", - "properties": {"name": "abc"}, - } - } - ], - }, - "properties": { - "name": "customers", - }, - }, - } - ], - } - } - - mock_graph.execute_graphql = MagicMock(return_value=datahub_response) - - response = searcher.search() - assert response == SearchResponse( - total_results=1, - page_results=[ - SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", - matches={}, - result_type=ResultType.TABLE, - name="customers", - fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", - description="", - metadata={ - "owner": "", - "owner_email": "", - "parents": [RelatedEntity(id="urn:abc", name="abc")], - "total_parents": 1, - "domain_id": "", - "domain_name": "", - "entity_types": { - "entity_type": "Dataset", - "entity_sub_types": ["Dataset"], - }, - }, - tags=[], - ) - ], - ) - - -def test_list_data_product_assets(mock_graph, searcher): - datahub_response = { - "listDataProductAssets": { - "start": 0, - "count": 20, - "total": 1, - "searchResults": [ - { - "entity": { - "type": "DATASET", - "urn": "urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", # noqa E501 - "name": "calm-pagoda-323403.jaffle_shop.customers", - "relationships": { - "total": 1, - "relationships": [ - { - "entity": { - "urn": "urn:abc", - "properties": {"name": "abc"}, - } - } - ], - }, - "properties": { - "name": "customers", - "description": "just some customers", - }, - }, - } - ], - } - } - - mock_graph.execute_graphql = MagicMock(return_value=datahub_response) - - response = searcher.list_data_product_assets( - urn="urn:li:dataProduct:test", - start=0, - count=20, - ) - - assert response == SearchResponse( - total_results=1, - page_results=[ - SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", - matches={}, - result_type=ResultType.TABLE, - name="customers", - fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", - description="just some customers", - metadata={ - "owner": "", - "owner_email": "", - "parents": [RelatedEntity(id="urn:abc", name="abc")], - "total_parents": 1, - "domain_id": "", - "domain_name": "", - "entity_types": { - "entity_type": "Dataset", - "entity_sub_types": ["Dataset"], - }, - }, - tags=[], - ) - ], - ) - - def test_get_glossary_terms(mock_graph, searcher): datahub_response = { "searchAcrossEntities": { @@ -840,7 +792,7 @@ def test_get_glossary_terms(mock_graph, searcher): total_results=2, page_results=[ SearchResult( - id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", name="IAO", description="Information asset owner.\n", metadata={ @@ -856,7 +808,7 @@ def test_get_glossary_terms(mock_graph, searcher): result_type=ResultType.GLOSSARY_TERM, ), SearchResult( - id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", + urn="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", name="Security classification", description="Only data that is 'official'", metadata={"parentNodes": []}, @@ -907,24 +859,48 @@ def test_search_for_charts(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:chart:(justice-data,absconds)", + urn="urn:li:chart:(justice-data,absconds)", + result_type=ResultType.CHART, name="Absconds", - fully_qualified_name="Absconds", + fully_qualified_name=None, description="test", matches={ "urn": "urn:li:chart:(justice-data,absconds)", "description": "test", "title": "Absconds", }, - result_type=ResultType.CHART, + metadata={ + "owner": "", + "owner_email": "", + "total_parents": 0, + "parents": [], + "domain_name": "", + "domain_id": "", + "entity_types": { + "entity_type": "Dataset", + "entity_sub_types": ["Dataset"], + }, + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", + }, + tags=[], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + assert expected == response + def test_search_for_container(mock_graph, searcher): datahub_response = { @@ -1000,11 +976,12 @@ def test_search_for_container(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.search() - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:container:test_db", + urn="urn:li:container:test_db", + result_type=ResultType.DATABASE, name="test_db", fully_qualified_name="test_db", description="test", @@ -1013,22 +990,41 @@ def test_search_for_container(mock_graph, searcher): "description": "test", "name": "test_db", }, - result_type=ResultType.DATABASE, metadata={ "owner": "Shannon Lovett", "owner_email": "shannon@longtail.com", - "domain_id": "urn:li:domain:testdom", "domain_name": "testdom", - "dpia_required": "False", + "domain_id": "urn:li:domain:testdom", "entity_types": { "entity_type": "Container", "entity_sub_types": ["Database"], }, + "dpia_required": "False", + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", + "usage_restrictions": UsageRestrictions( + status=None, + dpia_required="False", + dpia_location=None, + ), + "access_information": AccessInformation( + where_to_access_dataset="", + source_dataset_name="", + s3_location=None, + ), + "data_summary": DataSummary(), }, tags=["test"], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + assert response == expected def test_list_database_tables(mock_graph, searcher): @@ -1065,29 +1061,40 @@ def test_list_database_tables(mock_graph, searcher): mock_graph.execute_graphql = MagicMock(return_value=datahub_response) response = searcher.list_database_tables(urn="urn:li:athena:test", count=10) - assert response == SearchResponse( + expected = SearchResponse( total_results=1, page_results=[ SearchResult( - id="urn:li:dataset:(urn:li:dataPlatform:athena,test_db.test_table,PROD)", + urn="urn:li:dataset:(urn:li:dataPlatform:athena,test_db.test_table,PROD)", + result_type=ResultType.TABLE, name="test_table", fully_qualified_name="test_db.test_table", description="just for test", - result_type=ResultType.TABLE, + matches={}, metadata={ "owner": "", "owner_email": "", - "domain_id": "", + "total_parents": 0, + "parents": [], "domain_name": "", - "whereToAccessDataset": "analytical_platform", - "sensitivityLevel": "OFFICIAL", + "domain_id": "", "entity_types": { "entity_type": "Dataset", "entity_sub_types": ["Table"], }, - "total_parents": 0, - "parents": [], + "dpia_required": None, + "dpia_location": None, + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": None, + "row_count": "", }, + tags=[], + last_modified=None, + created=None, ) ], + facets=SearchFacets(facets={}), ) + + assert response == expected diff --git a/lib/datahub-client/tests/conftest.py b/lib/datahub-client/tests/conftest.py index 7752d91a..8878a6c5 100644 --- a/lib/datahub-client/tests/conftest.py +++ b/lib/datahub-client/tests/conftest.py @@ -12,9 +12,9 @@ @pytest.fixture def base_entity_metadata(): return { - "urn:li:domain:12345": { + "urn:li:domain:LAA": { "domainProperties": DomainPropertiesClass( - name="Marketing", description="Marketing Domain" + name="LAA", description="Legal Aid Authority" ) } } diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table.json b/lib/datahub-client/tests/snapshots/datahub_create_table.json deleted file mode 100644 index 75a4dd6b..00000000 --- a/lib/datahub-client/tests/snapshots/datahub_create_table.json +++ /dev/null @@ -1,77 +0,0 @@ -[ -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "datasetProperties", - "aspect": { - "json": { - "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "OFFICIAL", - "rowCount": "1177" - }, - "name": "my_table", - "qualifiedName": "my_database.my_table", - "description": "bla bla", - "tags": [] - } - } -}, -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "schemaMetadata", - "aspect": { - "json": { - "schemaName": "my_table", - "platform": "urn:li:dataPlatform:glue", - "version": 1, - "created": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "lastModified": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "hash": "", - "platformSchema": { - "com.linkedin.schema.OtherSchema": { - "rawSchema": "" - } - }, - "fields": [ - { - "fieldPath": "foo", - "nullable": false, - "description": "a", - "type": { - "type": { - "com.linkedin.schema.StringType": {} - } - }, - "nativeDataType": "string", - "recursive": false, - "isPartOfKey": false - }, - { - "fieldPath": "bar", - "nullable": false, - "description": "b", - "type": { - "type": { - "com.linkedin.schema.NumberType": {} - } - }, - "nativeDataType": "int", - "recursive": false, - "isPartOfKey": false - } - ] - } - } -} -] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json deleted file mode 100644 index 833a67bb..00000000 --- a/lib/datahub-client/tests/snapshots/datahub_create_table_with_metadata.json +++ /dev/null @@ -1,162 +0,0 @@ -[ -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "datasetProperties", - "aspect": { - "json": { - "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "OFFICIAL", - "rowCount": "1177" - }, - "name": "my_table", - "qualifiedName": "my_database.my_table", - "description": "bla bla", - "tags": [] - } - } -}, -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "schemaMetadata", - "aspect": { - "json": { - "schemaName": "my_table", - "platform": "urn:li:dataPlatform:glue", - "version": 1, - "created": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "lastModified": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "hash": "", - "platformSchema": { - "com.linkedin.schema.OtherSchema": { - "rawSchema": "" - } - }, - "fields": [ - { - "fieldPath": "foo", - "nullable": false, - "description": "a", - "type": { - "type": { - "com.linkedin.schema.StringType": {} - } - }, - "nativeDataType": "string", - "recursive": false, - "isPartOfKey": false - }, - { - "fieldPath": "bar", - "nullable": false, - "description": "b", - "type": { - "type": { - "com.linkedin.schema.NumberType": {} - } - }, - "nativeDataType": "int", - "recursive": false, - "isPartOfKey": false - } - ] - } - } -}, -{ - "entityType": "domain", - "entityUrn": "urn:li:domain:LAA", - "changeType": "UPSERT", - "aspectName": "domainProperties", - "aspect": { - "json": { - "name": "LAA", - "description": "" - } - } -}, -{ - "entityType": "domain", - "entityUrn": "urn:li:domain:Legal Aid", - "changeType": "UPSERT", - "aspectName": "domainProperties", - "aspect": { - "json": { - "name": "Legal Aid", - "description": "", - "parentDomain": "urn:li:domain:LAA" - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "domains", - "aspect": { - "json": { - "domains": [ - "urn:li:domain:Legal Aid" - ] - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "owner_display_name": "April Gonzalez", - "maintainer": "j.shelvey@digital.justice.gov.uk", - "maintainer_display_name": "Jonjo Shelvey", - "email": "justice@justice.gov.uk", - "retention_period_in_days": "365", - "dpia_required": "False", - "dpia_location": "None", - "last_updated": "2020-05-17 00:00:00", - "creation_date": "2020-05-17 00:00:00", - "s3_location": "s3://databucket/", - "status": "DataProductStatus.DRAFT" - }, - "name": "my_data_product", - "description": "bla bla" - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "version": "2.0", - "dpia": "false" - }, - "name": "my_data_product", - "description": "bla bla", - "assets": [ - { - "sourceUrn": "urn:li:dataProduct:my_data_product", - "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - } - ] - } - } -} -] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json b/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json deleted file mode 100644 index d9fe0634..00000000 --- a/lib/datahub-client/tests/snapshots/datahub_create_two_tables_with_metadata.json +++ /dev/null @@ -1,259 +0,0 @@ -[ -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "datasetProperties", - "aspect": { - "json": { - "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "OFFICIAL", - "rowCount": "1177" - }, - "name": "my_table", - "qualifiedName": "my_database.my_table", - "description": "bla bla", - "tags": [] - } - } -}, -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)", - "changeType": "UPSERT", - "aspectName": "schemaMetadata", - "aspect": { - "json": { - "schemaName": "my_table", - "platform": "urn:li:dataPlatform:glue", - "version": 1, - "created": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "lastModified": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "hash": "", - "platformSchema": { - "com.linkedin.schema.OtherSchema": { - "rawSchema": "" - } - }, - "fields": [ - { - "fieldPath": "foo", - "nullable": false, - "description": "a", - "type": { - "type": { - "com.linkedin.schema.StringType": {} - } - }, - "nativeDataType": "string", - "recursive": false, - "isPartOfKey": false - }, - { - "fieldPath": "bar", - "nullable": false, - "description": "b", - "type": { - "type": { - "com.linkedin.schema.NumberType": {} - } - }, - "nativeDataType": "int", - "recursive": false, - "isPartOfKey": false - } - ] - } - } -}, -{ - "entityType": "domain", - "entityUrn": "urn:li:domain:LAA", - "changeType": "UPSERT", - "aspectName": "domainProperties", - "aspect": { - "json": { - "name": "LAA", - "description": "" - } - } -}, -{ - "entityType": "domain", - "entityUrn": "urn:li:domain:Legal Aid", - "changeType": "UPSERT", - "aspectName": "domainProperties", - "aspect": { - "json": { - "name": "Legal Aid", - "description": "", - "parentDomain": "urn:li:domain:LAA" - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "domains", - "aspect": { - "json": { - "domains": [ - "urn:li:domain:Legal Aid" - ] - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "owner_display_name": "April Gonzalez", - "maintainer": "j.shelvey@digital.justice.gov.uk", - "maintainer_display_name": "Jonjo Shelvey", - "email": "justice@justice.gov.uk", - "retention_period_in_days": "365", - "dpia_required": "False", - "dpia_location": "None", - "last_updated": "2020-05-17 00:00:00", - "creation_date": "2020-05-17 00:00:00", - "s3_location": "s3://databucket/", - "status": "DataProductStatus.DRAFT" - }, - "name": "my_data_product", - "description": "bla bla" - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "version": "2.0", - "dpia": "false" - }, - "name": "my_data_product", - "description": "bla bla", - "assets": [ - { - "sourceUrn": "urn:li:dataProduct:my_data_product", - "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table,PROD)" - } - ] - } - } -}, -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)", - "changeType": "UPSERT", - "aspectName": "datasetProperties", - "aspect": { - "json": { - "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table2", - "sensitivityLevel": "OFFICIAL", - "rowCount": "1177" - }, - "name": "my_table2", - "qualifiedName": "my_database.my_table2", - "description": "this is a different table", - "tags": [] - } - } -}, -{ - "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)", - "changeType": "UPSERT", - "aspectName": "schemaMetadata", - "aspect": { - "json": { - "schemaName": "my_table2", - "platform": "urn:li:dataPlatform:glue", - "version": 1, - "created": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "lastModified": { - "time": 0, - "actor": "urn:li:corpuser:unknown" - }, - "hash": "", - "platformSchema": { - "com.linkedin.schema.OtherSchema": { - "rawSchema": "" - } - }, - "fields": [ - { - "fieldPath": "boo", - "nullable": false, - "description": "spooky", - "type": { - "type": { - "com.linkedin.schema.BooleanType": {} - } - }, - "nativeDataType": "boolean", - "recursive": false, - "isPartOfKey": false - }, - { - "fieldPath": "yar", - "nullable": false, - "description": "shiver my timbers", - "type": { - "type": { - "com.linkedin.schema.StringType": {} - } - }, - "nativeDataType": "string", - "recursive": false, - "isPartOfKey": false - } - ] - } - } -}, -{ - "entityType": "dataproduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "version": "2.0", - "dpia": "false" - }, - "name": "my_data_product", - "description": "bla bla", - "assets": [ - { - "sourceUrn": "urn:li:dataProduct:my_data_product", - "destinationUrn": "urn:li:dataset:(urn:li:dataPlatform:glue,my_database.my_table2,PROD)" - } - ] - } - } -} -] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json b/lib/datahub-client/tests/snapshots/test_upsert_table.json similarity index 53% rename from lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json rename to lib/datahub-client/tests/snapshots/test_upsert_table.json index 356be2f7..da541768 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_athena_table_with_metadata.json +++ b/lib/datahub-client/tests/snapshots/test_upsert_table.json @@ -1,31 +1,34 @@ [ { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "datasetProperties", "aspect": { "json": { "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "OFFICIAL" + "dpia_required": "True", + "dpia_location": "None", + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": "None", + "row_count": "5" }, - "name": "my_table", - "qualifiedName": "my_database.my_table", - "description": "bla bla", + "name": "Dataset", + "qualifiedName": "database.Dataset", + "description": "Dataset", "tags": [] } } }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "schemaMetadata", "aspect": { "json": { - "schemaName": "my_table", + "schemaName": "Dataset", "platform": "urn:li:dataPlatform:athena", "version": 1, "created": { @@ -44,9 +47,9 @@ }, "fields": [ { - "fieldPath": "foo", + "fieldPath": "urn", "nullable": false, - "description": "a", + "description": "The primary identifier for the dataset entity.", "type": { "type": { "com.linkedin.schema.StringType": {} @@ -55,19 +58,6 @@ "nativeDataType": "string", "recursive": false, "isPartOfKey": false - }, - { - "fieldPath": "bar", - "nullable": false, - "description": "b", - "type": { - "type": { - "com.linkedin.schema.NumberType": {} - } - }, - "nativeDataType": "int", - "recursive": false, - "isPartOfKey": false } ] } @@ -75,7 +65,7 @@ }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "subTypes", "aspect": { @@ -88,7 +78,7 @@ }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "container", "aspect": { @@ -96,5 +86,33 @@ "container": "urn:li:container:my_database" } } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", + "changeType": "UPSERT", + "aspectName": "globalTags", + "aspect": { + "json": { + "tags": [ + { + "tag": "urn:li:tag:some-tag" + } + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:LAA" + ] + } + } } ] \ No newline at end of file diff --git a/lib/datahub-client/tests/snapshots/datahub_create_athena_table.json b/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json similarity index 58% rename from lib/datahub-client/tests/snapshots/datahub_create_athena_table.json rename to lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json index 906198e6..903e8004 100644 --- a/lib/datahub-client/tests/snapshots/datahub_create_athena_table.json +++ b/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json @@ -1,4 +1,17 @@ [ +{ + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:LAA" + ] + } + } +}, { "entityType": "container", "entityUrn": "urn:li:container:my_database", @@ -7,18 +20,12 @@ "aspect": { "json": { "customProperties": { - "owner_display_name": "April Gonzalez", - "maintainer": "j.shelvey@digital.justice.gov.uk", - "maintainer_display_name": "Jonjo Shelvey", - "email": "justice@justice.gov.uk", - "retention_period_in_days": "365", - "subdomain": "Legal Aid", "dpia_required": "False", "dpia_location": "None", - "last_updated": "2020-05-17 00:00:00", - "creation_date": "2020-05-17 00:00:00", + "where_to_access_dataset": "analytical_platform", + "source_dataset_name": "", "s3_location": "s3://databucket/", - "status": "PROD" + "row_count": "" }, "name": "my_database", "description": "little test db" @@ -38,33 +45,47 @@ } } }, +{ + "entityType": "container", + "entityUrn": "urn:li:container:my_database", + "changeType": "UPSERT", + "aspectName": "dataPlatformInstance", + "aspect": { + "json": { + "platform": "urn:li:dataPlatform:athena" + } + } +}, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "datasetProperties", "aspect": { "json": { "customProperties": { - "sourceDatasetName": "my_source_table", - "whereToAccessDataset": "s3://databucket/table1", - "sensitivityLevel": "OFFICIAL" + "dpia_required": "True", + "dpia_location": "None", + "where_to_access_dataset": "", + "source_dataset_name": "", + "s3_location": "None", + "row_count": "5" }, - "name": "my_table", - "qualifiedName": "my_database.my_table", - "description": "bla bla", + "name": "Dataset", + "qualifiedName": "database.Dataset", + "description": "Dataset", "tags": [] } } }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "schemaMetadata", "aspect": { "json": { - "schemaName": "my_table", + "schemaName": "Dataset", "platform": "urn:li:dataPlatform:athena", "version": 1, "created": { @@ -83,9 +104,9 @@ }, "fields": [ { - "fieldPath": "foo", + "fieldPath": "urn", "nullable": false, - "description": "a", + "description": "The primary identifier for the dataset entity.", "type": { "type": { "com.linkedin.schema.StringType": {} @@ -94,19 +115,6 @@ "nativeDataType": "string", "recursive": false, "isPartOfKey": false - }, - { - "fieldPath": "bar", - "nullable": false, - "description": "b", - "type": { - "type": { - "com.linkedin.schema.NumberType": {} - } - }, - "nativeDataType": "int", - "recursive": false, - "isPartOfKey": false } ] } @@ -114,7 +122,7 @@ }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", "aspectName": "subTypes", "aspect": { @@ -126,24 +134,41 @@ } }, { - "entityType": "container", - "entityUrn": "urn:li:container:my_database", + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", - "aspectName": "dataPlatformInstance", + "aspectName": "container", "aspect": { "json": { - "platform": "urn:li:dataPlatform:athena" + "container": "urn:li:container:my_database" } } }, { "entityType": "dataset", - "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.my_table,PROD)", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", "changeType": "UPSERT", - "aspectName": "container", + "aspectName": "globalTags", "aspect": { "json": { - "container": "urn:li:container:my_database" + "tags": [ + { + "tag": "urn:li:tag:some-tag" + } + ] + } + } +}, +{ + "entityType": "dataset", + "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:athena,database.Dataset,PROD)", + "changeType": "UPSERT", + "aspectName": "domains", + "aspect": { + "json": { + "domains": [ + "urn:li:domain:LAA" + ] } } } diff --git a/lib/datahub-client/tests/test_entities.py b/lib/datahub-client/tests/test_entities.py deleted file mode 100644 index 99ec92ea..00000000 --- a/lib/datahub-client/tests/test_entities.py +++ /dev/null @@ -1,94 +0,0 @@ -from datetime import datetime - -import pytest -from data_platform_catalogue.entities import ( - DataProductMetadata, - DataProductStatus, - SecurityClassification, - TableMetadata, -) - - -@pytest.fixture -def data_product(): - return DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - -@pytest.fixture -def table(): - return TableMetadata( - name="my_table", - description="bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days=None, - source_dataset_name="my_source_table", - where_to_access_dataset="s3://source-bucket/folder", - data_sensitivity_level=SecurityClassification.OFFICIAL, - tags=["test"], - ) - - -def test_from_data_product_metadata_dict(data_product): - data_product2 = DataProductMetadata.from_data_product_metadata_dict( - { - "name": "my_data_product", - "description": "bla bla", - "domain": "LAA", - "subdomain": "Legal Aid", - "dataProductOwner": "justice@justice.gov.uk", - "dataProductOwnerDisplayName": "April Gonzalez", - "dataProductMaintainer": "j.shelvey@digital.justice.gov.uk", - "dataProductMaintainerDisplayName": "Jonjo Shelvey", - "email": "justice@justice.gov.uk", - "status": "DRAFT", - "dpiaRequired": False, - "retentionPeriod": 365, - "lastUpdated": "20200517", - "creationDate": "20200517", - "s3Location": "s3://databucket/", - "tags": ["test"], - }, - "v1.0.0", - "2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - ) - assert data_product2 == data_product - - -def test_from_data_product_schema_dict(table): - table2 = TableMetadata.from_data_product_schema_dict( - { - "tableDescription": "bla bla", - "columns": [ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - "tags": ["test"], - "sourceDatasetName": "my_source_table", - "sourceDatasetLocation": "s3://source-bucket/folder", - }, - "my_table", - ) - - assert table2 == table diff --git a/lib/datahub-client/tests/test_helpers/graph_helpers.py b/lib/datahub-client/tests/test_helpers/graph_helpers.py index ffa2cc64..64290591 100644 --- a/lib/datahub-client/tests/test_helpers/graph_helpers.py +++ b/lib/datahub-client/tests/test_helpers/graph_helpers.py @@ -40,7 +40,8 @@ def import_file(self, file: Path) -> None: This function can be called repeatedly on the same Mock instance to load up metadata from multiple files.""" file_source: GenericFileSource = GenericFileSource( - ctx=PipelineContext(run_id="test"), config=FileSourceConfig(path=str(file)) + ctx=PipelineContext(run_id="test"), + config=FileSourceConfig(path=str(file), file_extension=file.suffix), ) for wu in file_source.get_workunits(): if isinstance(wu, MetadataWorkUnit): @@ -79,8 +80,8 @@ def get_aspect( result = self.entity_graph.get(entity_urn, {}).get(aspect_name, None) if result is not None and isinstance(result, dict): return aspect_type.from_obj(result) - else: - return result + + return result def get_domain_urn_by_name(self, domain_name: str) -> Optional[str]: domain_metadata = { @@ -101,8 +102,8 @@ def get_domain_urn_by_name(self, domain_name: str) -> Optional[str]: ] if urn_match: return urn_match[0] - else: - return None + + return None def emit( self, diff --git a/lib/datahub-client/tests/test_integration_with_datahub_server.py b/lib/datahub-client/tests/test_integration_with_datahub_server.py index 3612b0d3..9eade49b 100644 --- a/lib/datahub-client/tests/test_integration_with_datahub_server.py +++ b/lib/datahub-client/tests/test_integration_with_datahub_server.py @@ -9,93 +9,33 @@ import os import time -from datetime import datetime +from datetime import datetime, timezone import pytest -from data_platform_catalogue import DatabaseMetadata, DataProductMetadata, TableMetadata -from data_platform_catalogue.client.datahub.datahub_client import DataHubCatalogueClient +from data_platform_catalogue.client.datahub_client import DataHubCatalogueClient from data_platform_catalogue.entities import ( - DatabaseStatus, - DataLocation, - DataProductStatus, + AccessInformation, + Column, + ColumnRef, + CustomEntityProperties, + Database, + DataSummary, + DomainRef, + EntityRef, + Governance, + OwnerRef, + RelationshipType, + Table, + TagRef, + UsageRestrictions, ) from data_platform_catalogue.search_types import MultiSelectFilter, ResultType -from datahub.metadata.schema_classes import DatasetPropertiesClass, SchemaMetadataClass jwt_token = os.environ.get("JWT_TOKEN") api_url = os.environ.get("API_URL", "") runs_on_development_server = pytest.mark.skipif("not jwt_token or not api_url") -@runs_on_development_server -def test_data_product_upsert_test_hierarchy(): - client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - table = TableMetadata( - name="test_table", - description="bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days=365, - source_dataset_name="my_source_table", - where_to_access_dataset="s3://databucket/folder", - tags=["test"], - ) - - table_fqn = client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("test_data_product_v2"), - ) - assert ( - table_fqn - == "urn:li:dataset:(urn:li:dataPlatform:glue,test_data_product_v2.test_table,PROD)" - ) - - # Ensure data went through - assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) - assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) - - dataset_properties = client.graph.get_aspect( - table_fqn, aspect_type=DatasetPropertiesClass - ) - # check properties been loaded to datahub dataset - assert dataset_properties.description == table.description - assert dataset_properties.qualifiedName == f"test_data_product_v2.{table.name}" - assert dataset_properties.name == table.name - assert ( - dataset_properties.customProperties["sourceDatasetName"] - == table.source_dataset_name - ) - assert ( - dataset_properties.customProperties["whereToAccessDataset"] - == table.where_to_access_dataset - ) - - @runs_on_development_server def test_search(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) @@ -104,46 +44,13 @@ def test_search(): assert len(response.page_results) == 20 -@runs_on_development_server -def test_search_for_data_product(): - client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - client.upsert_data_product(data_product) - - response = client.search( - query="my_data_product", result_types=(ResultType.DATA_PRODUCT,) - ) - assert response.total_results >= 1 - assert response.page_results[0].id == "urn:li:dataProduct:my_data_product" - - @runs_on_development_server def test_search_by_domain(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) response = client.search( filters=[MultiSelectFilter("domains", ["does-not-exist"])], - result_types=(ResultType.DATA_PRODUCT,), + result_types=(ResultType.TABLE,), ) assert response.total_results == 0 @@ -152,27 +59,43 @@ def test_search_by_domain(): def test_domain_facets_are_returned(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), + database = Database( + name="my_database", + description="little test db", + governance=Governance( + data_owner=OwnerRef( + urn="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + display_name="April Gonzalez", + email="abc@digital.justice.gov.uk", + ), + data_stewards=[ + OwnerRef( + urn="abc", + display_name="Jonjo Shelvey", + email="j.shelvey@digital.justice.gov.uk", + ) + ], + ), + domain=DomainRef(urn="LAA", display_name="LAA"), + last_modified=datetime(2020, 5, 17), creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], + access_information=AccessInformation( + s3_location="s3://databucket/", + ), + tags=[TagRef(urn="test", display_name="test")], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + dpia_required=False, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="analytical_platform", + s3_location="s3://databucket/", + ), + ), ) - client.upsert_data_product(data_product) + client.upsert_database(database) response = client.search() assert response.facets.options("domains") @@ -183,27 +106,43 @@ def test_domain_facets_are_returned(): def test_filter_by_urn(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), + database = Database( + name="my_database", + description="little test db", + governance=Governance( + data_owner=OwnerRef( + urn="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + display_name="April Gonzalez", + email="abc@digital.justice.gov.uk", + ), + data_stewards=[ + OwnerRef( + urn="abc", + display_name="Jonjo Shelvey", + email="j.shelvey@digital.justice.gov.uk", + ) + ], + ), + domain=DomainRef(urn="LAA", display_name="LAA"), + last_modified=datetime(2020, 5, 17), creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], + access_information=AccessInformation( + s3_location="s3://databucket/", + ), + tags=[TagRef(urn="test", display_name="test")], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + dpia_required=False, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="analytical_platform", + s3_location="s3://databucket/", + ), + ), ) - urn = client.upsert_data_product(data_product) + urn = client.upsert_database(database) response = client.search( filters=[MultiSelectFilter(filter_name="urn", included_values=[urn])] @@ -215,45 +154,101 @@ def test_filter_by_urn(): def test_fetch_dataset_belonging_to_data_product(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - data_product = DataProductMetadata( - name="my_data_product", - description="bla bla", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DataProductStatus.DRAFT.name, - retention_period_in_days=365, - domain="LAA", - subdomain="Legal Aid", - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), + database = Database( + name="my_database", + description="little test db", + display_name="database", + governance=Governance( + data_owner=OwnerRef( + urn="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", + display_name="April Gonzalez", + email="abc@digital.justice.gov.uk", + ), + data_stewards=[ + OwnerRef( + urn="abc", + display_name="Jonjo Shelvey", + email="j.shelvey@digital.justice.gov.uk", + ) + ], + ), + domain=DomainRef(urn="LAA", display_name="LAA"), + last_modified=datetime(2020, 5, 17), creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], + access_information=AccessInformation( + s3_location="s3://databucket/", + ), + tags=[TagRef(urn="test", display_name="test")], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + usage_restrictions=UsageRestrictions( + dpia_required=False, + dpia_location=None, + ), + access_information=AccessInformation( + where_to_access_dataset="analytical_platform", + s3_location="s3://databucket/", + ), + ), ) - - table = TableMetadata( - name="test_table", - description="bla bla", + client.upsert_database(database) + + table = Table( + urn=None, + display_name="Foo.Dataset", + name="Dataset", + fully_qualified_name="Foo.Dataset", + description="Dataset", + relationships={ + RelationshipType.PARENT: [ + EntityRef(urn="urn:li:container:my_database", display_name="database") + ] + }, + domain=DomainRef(display_name="", urn=""), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef(display_name="", email="Contact email for the user", urn="") + ], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) + ], + ), ], - retention_period_in_days=365, - source_dataset_name="my_source_table", - where_to_access_dataset="s3://databucket/folder", - tags=["test"], - ) - - urn = client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("test_data_product_v2"), + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties( + access_information=AccessInformation( + where_to_access_dataset="", source_dataset_name="", s3_location=None + ), + data_summary=DataSummary(row_count=5), + usage_restrictions=UsageRestrictions( + dpia_required=True, + dpia_location=None, + ), + ), ) + urn = client.upsert_table(table=table) # Introduce sleep to combat race conditions with table association time.sleep(2) @@ -276,9 +271,9 @@ def test_paginated_search_results_unique(): @runs_on_development_server -def test_list_data_product_assets_returns(): +def test_list_database_tables(): client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - assets = client.list_data_product_assets( + assets = client.list_database_tables( urn="urn:li:dataProduct:my_data_product", count=20 ) assert assets @@ -307,69 +302,69 @@ def test_get_dataset(): assert table -@runs_on_development_server -def test_athena_upsert_test_hierarchy(): - client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - - database = DatabaseMetadata( - name="my_database", - description="testing", - version="v1.0.0", - owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", - owner_display_name="April Gonzalez", - maintainer="j.shelvey@digital.justice.gov.uk", - maintainer_display_name="Jonjo Shelvey", - email="justice@justice.gov.uk", - status=DatabaseStatus.PROD.name, - retention_period_in_days=365, - domain="prison", - subdomain=None, - dpia_required=False, - dpia_location=None, - last_updated=datetime(2020, 5, 17), - creation_date=datetime(2020, 5, 17), - s3_location="s3://databucket/", - tags=["test"], - ) - - table = TableMetadata( - name="test_table", - parent_database_name="my_database", - description="bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days=365, - where_to_access_dataset="analytical_platform", - tags=["test"], - ) - - table_fqn = client.upsert_athena_table( - metadata=table, - database_metadata=database, - ) - assert ( - table_fqn - == "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.test_table,PROD)" - ) - - # Ensure data went through - assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) - assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) - - dataset_properties = client.graph.get_aspect( - table_fqn, aspect_type=DatasetPropertiesClass - ) - # check properties been loaded to datahub dataset - assert dataset_properties.description == table.description - assert dataset_properties.qualifiedName == f"{database.name}.{table.name}" - assert dataset_properties.name == table.name - assert ( - dataset_properties.customProperties["sourceDatasetName"] - == table.source_dataset_name - ) - assert ( - dataset_properties.customProperties["whereToAccessDataset"] - == table.where_to_access_dataset - ) +# @runs_on_development_server +# def test_athena_upsert_test_hierarchy(): +# client = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) + +# database = Database( +# name="my_database", +# description="testing", +# version="v1.0.0", +# owner="2e1fa91a-c607-49e4-9be2-6f072ebe27c7", +# owner_display_name="April Gonzalez", +# maintainer="j.shelvey@digital.justice.gov.uk", +# maintainer_display_name="Jonjo Shelvey", +# email="justice@justice.gov.uk", +# retention_period_in_days=365, +# domain="prison", +# subdomain=None, +# dpia_required=False, +# dpia_location=None, +# last_modified=datetime(2020, 5, 17), +# creation_date=datetime(2020, 5, 17), +# s3_location="s3://databucket/", +# tags=["test"], +# ) + +# table = Table( +# name="test_table", +# parent_database_name="my_database", +# description="bla bla", +# column_details=[ +# {"name": "foo", "type": "string", "description": "a"}, +# {"name": "bar", "type": "int", "description": "b"}, +# ], +# retention_period_in_days=365, +# where_to_access_dataset="analytical_platform", +# tags=["test"], +# ) + +# # This function doesn't exist after the refactor. Unsure if we still need the test. +# table_fqn = client.upsert_athena_table( +# metadata=table, +# database_metadata=database, +# ) +# assert ( +# table_fqn +# == "urn:li:dataset:(urn:li:dataPlatform:athena,my_database.test_table,PROD)" +# ) + +# # Ensure data went through +# assert client.graph.get_aspect(table_fqn, DatasetPropertiesClass) +# assert client.graph.get_aspect(table_fqn, SchemaMetadataClass) + +# dataset_properties = client.graph.get_aspect( +# table_fqn, aspect_type=DatasetPropertiesClass +# ) +# # check properties been loaded to datahub dataset +# assert dataset_properties.description == table.description +# assert dataset_properties.qualifiedName == f"{database.name}.{table.name}" +# assert dataset_properties.name == table.name +# assert ( +# dataset_properties.customProperties["sourceDatasetName"] +# == table.source_dataset_name +# ) +# assert ( +# dataset_properties.customProperties["whereToAccessDataset"] +# == table.where_to_access_dataset +# ) diff --git a/lib/datahub-client/tests/test_resources/golden_data_product_in.json b/lib/datahub-client/tests/test_resources/golden_data_product_in.json deleted file mode 100644 index 4fc6a087..00000000 --- a/lib/datahub-client/tests/test_resources/golden_data_product_in.json +++ /dev/null @@ -1,59 +0,0 @@ -[ - { - "entityType": "dataProduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "dataProductProperties", - "aspect": { - "json": { - "customProperties": { - "version": "2.0", - "dpia": "false" - }, - "externalUrl": "https://github.com/datahub-project/datahub", - "name": "my_data_product", - "description": "bla bla", - "assets": [] - } - } - }, - { - "entityType": "dataProduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "domains", - "aspect": { - "json": { - "domains": [ - "urn:li:domain:LAA" - ] - } - } - }, - { - "entityType": "dataProduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "globalTags", - "aspect": { - "json": { - "tags": [ - { - "tag": "urn:li:tag:awesome" - } - ] - } - } - }, - { - "entityType": "dataProduct", - "entityUrn": "urn:li:dataProduct:my_data_product", - "changeType": "UPSERT", - "aspectName": "status", - "aspect": { - "json": { - "removed": false - } - } - } -] From 11e6192b7397effac4bd1589c15bfdc82b5eb9f9 Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 1 May 2024 14:47:38 +0100 Subject: [PATCH 49/64] Consistently parse names from datahub (#4244) * Consistently parse names from datahub Previously, we were parsing names inconsistently, and there were edge cases where we would get unexpected None values or KeyErrors. Datahub has 4 possible names that might be set - name: Unique guid for dataset No longer to be used as the Dataset display name. Use properties.name instead - properties.name - properties.displayName - properties.qualifiedName These are used inconsistently by different ingestion sources. If we have displayName / qualifiedName it's unambiguous, but the top level name can be either. We expose 3 values: - `displayName` and `qualifiedName` align with the corresponding datahub names, and fall back to the generic name if not set. - `name` is probably redundant now, but I've kept it in to avoid breaking things further. * Lint * Add missing __init__.py * Disable pylint warnings for missing docstrings Not sure why this has suddenly started being checked, but we don't care right now. We will soon move this to a different repo and settle on a single linter. --- .../data_platform_catalogue/__init__.py | 0 .../client/datahub_client.py | 16 ++++----- .../client/graphql_helpers.py | 23 +++++++++++++ .../data_platform_catalogue/client/search.py | 33 +++++++++---------- .../data_platform_catalogue/search_types.py | 1 + lib/datahub-client/pyproject.toml | 1 - .../client/datahub/test_datahub_client.py | 2 +- .../tests/client/datahub/test_search.py | 18 ++++++++-- 8 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 lib/datahub-client/data_platform_catalogue/__init__.py diff --git a/lib/datahub-client/data_platform_catalogue/__init__.py b/lib/datahub-client/data_platform_catalogue/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py index 3d41522f..c9450726 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py @@ -14,6 +14,7 @@ parse_columns, parse_created_and_modified, parse_domain, + parse_names, parse_owner, parse_properties, parse_relations, @@ -222,8 +223,8 @@ def get_table_details(self, urn) -> Table: domain = parse_domain(response) owner = parse_owner(response) tags = parse_tags(response) - name = properties.get("name", response.get("name")) created, modified = parse_created_and_modified(properties) + name, display_name, qualified_name = parse_names(response, properties) # A dataset can't have multiple parents, but if we did # start to use in that we'd need to change this @@ -235,9 +236,9 @@ def get_table_details(self, urn) -> Table: relations = {} return Table( urn=None, - display_name=properties.get("qualifiedName") or name, + display_name=display_name, name=name, - fully_qualified_name=properties.get("qualifiedName") or name, + fully_qualified_name=qualified_name, description=properties.get("description", ""), relationships=relations, domain=domain, @@ -264,16 +265,15 @@ def get_chart_details(self, urn) -> Chart: domain = parse_domain(response) owner = parse_owner(response) tags = parse_tags(response) + name, display_name, qualified_name = parse_names(response, properties) return Chart( urn=urn, external_url=properties.get("externalUrl", ""), description=properties.get("description", ""), - display_name=properties.get("displayName", properties.get("name")), - name=properties["name"], - fully_qualified_name=properties.get( - "qualifiedName", properties.get("name") - ), + name=name, + display_name=display_name, + fully_qualified_name=qualified_name, domain=domain, governance=Governance( data_owner=owner, diff --git a/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py index 11ee28fc..311b70f3 100644 --- a/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py @@ -106,6 +106,29 @@ def parse_properties( return properties, custom_properties +def parse_names( + entity: dict[str, Any], properties: dict[str, Any] +) -> Tuple[str, str, str]: + """ + Returns a tuple of 3 name values. + + The first value is the non-qualified version of the entity name, + and the second value is the human-friendly display name. + + Either of these can be used when showing the entity providing it is within + the context of its container. + + The third value is the fully qualified name (e.g. my_database.my_table), which + can be used to show the entity out of context. + """ + top_level_name = entity.get("name") + name = properties.get("name", top_level_name) + display_name = properties.get("displayName") or name + qualified_name = properties.get("qualifiedName") or top_level_name or name + + return name, display_name, qualified_name + + def parse_domain(entity: dict[str, Any]) -> DomainRef: domain = entity.get("domain") or {} inner_domain = domain.get("domain") or {} diff --git a/lib/datahub-client/data_platform_catalogue/client/search.py b/lib/datahub-client/data_platform_catalogue/client/search.py index a2488392..5521d134 100644 --- a/lib/datahub-client/data_platform_catalogue/client/search.py +++ b/lib/datahub-client/data_platform_catalogue/client/search.py @@ -3,19 +3,18 @@ from importlib.resources import files from typing import Any, Sequence -from data_platform_catalogue.client.exceptions import ( # pylint: disable=E0611 - CatalogueError, -) +from data_platform_catalogue.client.exceptions import CatalogueError from data_platform_catalogue.client.graphql_helpers import ( parse_created_and_modified, parse_domain, parse_last_modified, + parse_names, parse_owner, parse_properties, parse_relations, parse_tags, ) -from data_platform_catalogue.entities import RelationshipType # pylint: disable=E0611 +from data_platform_catalogue.entities import RelationshipType from data_platform_catalogue.search_types import ( FacetOption, MultiSelectFilter, @@ -244,7 +243,7 @@ def _parse_result( properties, custom_properties = parse_properties(entity) tags = parse_tags(entity) last_modified = parse_last_modified(entity) - name = entity.get("name") + name, display_name, qualified_name = parse_names(entity, properties) relations = parse_relations( RelationshipType.PARENT, entity.get("relationships", {}) @@ -265,18 +264,15 @@ def _parse_result( metadata.update(custom_properties.access_information.model_dump()) metadata.update(custom_properties.data_summary.model_dump()) - # TODO: the way we return name/display name/qualified name should be - # consistent with the upsert/get methods - fqn = properties.get("qualifiedName", name) - _, modified = parse_created_and_modified(properties) return SearchResult( urn=entity["urn"], result_type=result_type, matches=matches, - name=properties.get("name", name), - fully_qualified_name=fqn, + name=name, + display_name=display_name, + fully_qualified_name=qualified_name, description=properties.get("description", ""), metadata=metadata, tags=[tag_str.display_name for tag_str in tags], @@ -309,12 +305,15 @@ def _parse_facets(self, facets: list[dict[str, Any]]) -> SearchFacets: def _parse_glossary_term(self, entity) -> SearchResult: properties, _ = parse_properties(entity) metadata = {"parentNodes": entity["parentNodes"]["nodes"]} + name, display_name, qualified_name = parse_names(entity, properties) return SearchResult( urn=entity["urn"], result_type=ResultType.GLOSSARY_TERM, matches={}, - name=properties["name"], + name=name, + display_name=display_name, + fully_qualified_name=qualified_name, description=properties.get("description", ""), metadata=metadata, tags=[], @@ -351,10 +350,7 @@ def _parse_container(self, entity: dict[str, Any], matches) -> SearchResult: properties, custom_properties = parse_properties(entity) domain = parse_domain(entity) owner = parse_owner(entity) - - # TODO: the way we return name/display name/qualified name should be - # consistent with the upsert/get methods - fqn = properties.get("qualifiedName", properties["name"]) + name, display_name, qualified_name = parse_names(entity, properties) metadata = { "owner": owner.display_name, @@ -373,8 +369,9 @@ def _parse_container(self, entity: dict[str, Any], matches) -> SearchResult: urn=entity["urn"], result_type=ResultType.DATABASE, matches=matches, - name=properties["name"], - fully_qualified_name=fqn, + name=name, + fully_qualified_name=qualified_name, + display_name=display_name, description=properties.get("description", ""), metadata=metadata, tags=[tag.display_name for tag in tags], diff --git a/lib/datahub-client/data_platform_catalogue/search_types.py b/lib/datahub-client/data_platform_catalogue/search_types.py index d3c04137..19540d57 100644 --- a/lib/datahub-client/data_platform_catalogue/search_types.py +++ b/lib/datahub-client/data_platform_catalogue/search_types.py @@ -57,6 +57,7 @@ class SearchResult: urn: str result_type: ResultType name: str + display_name: str = "" fully_qualified_name: str = "" description: str = "" matches: dict[str, str] = field(default_factory=dict) diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index a2c9a4e2..d54a07c8 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -27,4 +27,3 @@ pydantic = "2" [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" pytest = "^7.4.2" - diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 939afe12..9e97775c 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -308,7 +308,7 @@ def test_get_dataset( assert dataset == Table( urn=None, - display_name="Foo.Dataset", + display_name="Dataset", name="Dataset", fully_qualified_name="Foo.Dataset", description="Dataset", diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 8216084b..84bffb5e 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -113,6 +113,7 @@ def test_one_search_result(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", + display_name="customers", fully_qualified_name="jaffle_shop.customers", description="", matches={}, @@ -199,6 +200,7 @@ def test_dataset_result(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", + display_name="customers", fully_qualified_name="jaffle_shop.customers", description="", matches={}, @@ -291,6 +293,7 @@ def test_full_page(mock_graph, searcher): result_type=ResultType.TABLE, name="customers", fully_qualified_name="jaffle_shop.customers", + display_name="customers", description="", matches={}, metadata={ @@ -321,7 +324,8 @@ def test_full_page(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers2,PROD)", result_type=ResultType.TABLE, name="customers2", - fully_qualified_name=None, + fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers2", + display_name="customers2", description="", matches={}, metadata={ @@ -351,6 +355,7 @@ def test_full_page(mock_graph, searcher): result_type=ResultType.TABLE, name="customers3", fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers3", + display_name="customers3", description="", matches={}, metadata={ @@ -425,6 +430,7 @@ def test_query_match(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", + display_name="customers", fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", matches={ @@ -504,6 +510,7 @@ def test_result_with_owner(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:bigquery,calm-pagoda-323403.jaffle_shop.customers,PROD)", result_type=ResultType.TABLE, name="customers", + display_name="customers", fully_qualified_name="calm-pagoda-323403.jaffle_shop.customers", description="", matches={}, @@ -794,6 +801,8 @@ def test_get_glossary_terms(mock_graph, searcher): SearchResult( urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", name="IAO", + display_name="IAO", + fully_qualified_name="IAO", description="Information asset owner.\n", metadata={ "parentNodes": [ @@ -810,6 +819,8 @@ def test_get_glossary_terms(mock_graph, searcher): SearchResult( urn="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", name="Security classification", + display_name="Security classification", + fully_qualified_name="Security classification", description="Only data that is 'official'", metadata={"parentNodes": []}, result_type=ResultType.GLOSSARY_TERM, @@ -866,7 +877,8 @@ def test_search_for_charts(mock_graph, searcher): urn="urn:li:chart:(justice-data,absconds)", result_type=ResultType.CHART, name="Absconds", - fully_qualified_name=None, + display_name="Absconds", + fully_qualified_name="Absconds", description="test", matches={ "urn": "urn:li:chart:(justice-data,absconds)", @@ -983,6 +995,7 @@ def test_search_for_container(mock_graph, searcher): urn="urn:li:container:test_db", result_type=ResultType.DATABASE, name="test_db", + display_name="test_db", fully_qualified_name="test_db", description="test", matches={ @@ -1068,6 +1081,7 @@ def test_list_database_tables(mock_graph, searcher): urn="urn:li:dataset:(urn:li:dataPlatform:athena,test_db.test_table,PROD)", result_type=ResultType.TABLE, name="test_table", + display_name="test_table", fully_qualified_name="test_db.test_table", description="just for test", matches={}, From a3b6de7c2937a24e9dfbb7a5c75ac680b9b372db Mon Sep 17 00:00:00 2001 From: Mat Date: Wed, 1 May 2024 15:05:49 +0100 Subject: [PATCH 50/64] Coerce descriptions and custom properties to string/empty string (#4256) * Coerce column description to string In some cases we column description is set to None, but we have defined it as a non-nullable string. For simplicity, we can treat None as empty string, as this is what we'll actually display in the frontend. Resolves https://github.com/ministryofjustice/find-moj-data/issues/296 * Simplify type of list field * Simplify types Custom properties values are always String in datahub, so using string throughout will make our lives easier. We can use empty string to indicate missing values here. See https://datahubproject.io/docs/graphql/objects/#custompropertiesentry dpia_required is a special case - it's still a string in datahub but we want it to be a boolean when we use it - since "False" is truthy in python. When setting custom properties, we take the string representation. This will serialize booleans to 'True' and 'False' which will fail pydantic's validation, so we need to explicitly convert this back to a boolean. https://docs.pydantic.dev/2.0/usage/types/booleans/ If dpia_required is unset, we should just pass empty string, rather than "None". --- .../client/datahub_client.py | 3 +- .../client/graphql_helpers.py | 10 +++- .../data_platform_catalogue/entities.py | 14 +++--- .../client/datahub/test_datahub_client.py | 29 ++++------- .../client/datahub/test_graphql_helpers.py | 35 ++++++++++++- .../tests/client/datahub/test_search.py | 49 +++++++++---------- .../tests/snapshots/test_upsert_table.json | 6 +-- .../test_upsert_table_and_database.json | 8 +-- 8 files changed, 88 insertions(+), 66 deletions(-) diff --git a/lib/datahub-client/data_platform_catalogue/client/datahub_client.py b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py index c9450726..576da45f 100644 --- a/lib/datahub-client/data_platform_catalogue/client/datahub_client.py +++ b/lib/datahub-client/data_platform_catalogue/client/datahub_client.py @@ -512,8 +512,7 @@ def _get_custom_property_key_value_pairs( ) custom_properties_unnested = self._flatten_dict(custom_properties_dict) custom_properties_unnested_all_string_values = { - key: str(value) - # if value is not None else value + key: str(value) if value is not None else "" for key, value in custom_properties_unnested.items() } diff --git a/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py index 311b70f3..36b35397 100644 --- a/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py +++ b/lib/datahub-client/data_platform_catalogue/client/graphql_helpers.py @@ -90,8 +90,14 @@ def parse_properties( editable_properties = entity.get("editableProperties") or {} properties.update(editable_properties) custom_properties_dict = { - i["key"]: i["value"] for i in properties.get("customProperties", []) + i["key"]: i["value"] or "" for i in properties.get("customProperties", []) } + + if "dpia_required" in custom_properties_dict: + custom_properties_dict["dpia_required"] = ( + custom_properties_dict["dpia_required"] == "True" + ) + properties.pop("customProperties", None) access_information = AccessInformation.model_validate(custom_properties_dict) usage_restrictions = UsageRestrictions.model_validate(custom_properties_dict) @@ -200,7 +206,7 @@ def parse_columns(entity: dict[str, Any]) -> list[Column]: Column( name=field_path, display_name=display_name, - description=field["description"], + description=field.get("description") or "", type=field.get("nativeDataType", field["type"]), nullable=field["nullable"], is_primary_key=is_primary_key, diff --git a/lib/datahub-client/data_platform_catalogue/entities.py b/lib/datahub-client/data_platform_catalogue/entities.py index b94a2024..d7b4e880 100644 --- a/lib/datahub-client/data_platform_catalogue/entities.py +++ b/lib/datahub-client/data_platform_catalogue/entities.py @@ -51,8 +51,8 @@ class Column(BaseModel): is_primary_key: bool = Field( description="Whether the field is part of the primary key" ) - foreign_keys: list[ColumnRef] | None = Field( - description="References to columns in other tables" + foreign_keys: list[ColumnRef] = Field( + description="References to columns in other tables", default_factory=list ) @@ -114,12 +114,12 @@ class UsageRestrictions(BaseModel): Metadata about how entities may be used. """ - dpia_required: bool | str | None = Field( + dpia_required: bool | None = Field( description="Bool for if a data privacy impact assessment (DPIA) is required to access this database", default=None, ) - dpia_location: str | None = Field( - description="Where to find the DPIA document", default=None + dpia_location: str = Field( + description="Where to find the DPIA document", default="" ) @@ -136,9 +136,7 @@ class AccessInformation(BaseModel): source_dataset_name: str = Field( description="The name of a dataset this data was derived from", default="" ) - s3_location: str | None = Field( - description="Location of the data in s3", default=None - ) + s3_location: str = Field(description="Location of the data in s3", default="") class DataSummary(BaseModel): diff --git a/lib/datahub-client/tests/client/datahub/test_datahub_client.py b/lib/datahub-client/tests/client/datahub/test_datahub_client.py index 9e97775c..7158d8f0 100644 --- a/lib/datahub-client/tests/client/datahub/test_datahub_client.py +++ b/lib/datahub-client/tests/client/datahub/test_datahub_client.py @@ -64,7 +64,7 @@ def database(self): custom_properties=CustomEntityProperties( usage_restrictions=UsageRestrictions( dpia_required=False, - dpia_location=None, + dpia_location="", ), access_information=AccessInformation( where_to_access_dataset="analytical_platform", @@ -125,12 +125,12 @@ def table(self): platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), custom_properties=CustomEntityProperties( access_information=AccessInformation( - where_to_access_dataset="", source_dataset_name="", s3_location=None + where_to_access_dataset="", source_dataset_name="", s3_location="" ), data_summary=DataSummary(row_count=5), usage_restrictions=UsageRestrictions( dpia_required=True, - dpia_location=None, + dpia_location="", ), ), ) @@ -196,12 +196,12 @@ def table2(self): platform=EntityRef(urn="athena", display_name="athena"), custom_properties=CustomEntityProperties( access_information=AccessInformation( - where_to_access_dataset="", source_dataset_name="", s3_location=None + where_to_access_dataset="", source_dataset_name="", s3_location="" ), data_summary=DataSummary(row_count=5), usage_restrictions=UsageRestrictions( dpia_required=True, - dpia_location=None, + dpia_location="", ), ), ) @@ -326,17 +326,6 @@ def test_get_dataset( last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), created=None, platform=EntityRef(urn="datahub", display_name="datahub"), - custom_properties=CustomEntityProperties( - usage_restrictions=UsageRestrictions( - status=None, - dpia_required=None, - dpia_location=None, - ), - access_information=AccessInformation( - where_to_access_dataset="", source_dataset_name="", s3_location=None - ), - data_summary=DataSummary(), - ), column_details=[ Column( name="urn", @@ -405,10 +394,10 @@ def test_get_dataset_minimal_properties( usage_restrictions=UsageRestrictions( status=None, dpia_required=None, - dpia_location=None, + dpia_location="", ), access_information=AccessInformation( - where_to_access_dataset="", source_dataset_name="", s3_location=None + where_to_access_dataset="", source_dataset_name="", s3_location="" ), data_summary=DataSummary(), ), @@ -466,10 +455,10 @@ def test_get_chart_details(self, datahub_client, base_mock_graph): usage_restrictions=UsageRestrictions( status=None, dpia_required=None, - dpia_location=None, + dpia_location="", ), access_information=AccessInformation( - where_to_access_dataset="", source_dataset_name="", s3_location=None + where_to_access_dataset="", source_dataset_name="", s3_location="" ), data_summary=DataSummary(), ), diff --git a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py index 33276563..783406e0 100644 --- a/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py +++ b/lib/datahub-client/tests/client/datahub/test_graphql_helpers.py @@ -133,6 +133,37 @@ def test_parse_columns_with_no_keys(): ] +def test_parse_columns_with_null_descriptions(): + entity = { + "schemaMetadata": { + "fields": [ + { + "fieldPath": "urn", + "label": None, + "nullable": False, + "description": None, + "type": "STRING", + "nativeDataType": "string", + } + ], + "primaryKeys": [], + "foreignKeys": [], + } + } + + assert parse_columns(entity) == [ + Column( + name="urn", + display_name="urn", + type="string", + description="", + nullable=False, + is_primary_key=False, + foreign_keys=[], + ) + ] + + def test_parse_columns_with_no_schema(): entity = {} @@ -185,7 +216,7 @@ def test_parse_properties(): "properties": { "customProperties": [ {"key": "dpia_required", "value": False}, - {"key": "dpia_location", "value": None}, + {"key": "dpia_location", "value": ""}, {"key": "data_sensitivity_level", "value": "OFFICIAL"}, {"key": "where_to_access_dataset", "value": "analytical_platform"}, {"key": "source_dataset_name", "value": ""}, @@ -209,7 +240,7 @@ def test_parse_properties(): assert custom_properties == CustomEntityProperties( usage_restrictions=UsageRestrictions( dpia_required=False, - dpia_location=None, + dpia_location="", ), access_information=AccessInformation( where_to_access_dataset="analytical_platform", diff --git a/lib/datahub-client/tests/client/datahub/test_search.py b/lib/datahub-client/tests/client/datahub/test_search.py index 84bffb5e..fb518b07 100644 --- a/lib/datahub-client/tests/client/datahub/test_search.py +++ b/lib/datahub-client/tests/client/datahub/test_search.py @@ -129,10 +129,10 @@ def test_one_search_result(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -216,10 +216,10 @@ def test_dataset_result(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -308,10 +308,10 @@ def test_full_page(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -340,10 +340,10 @@ def test_full_page(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -370,10 +370,10 @@ def test_full_page(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -450,10 +450,10 @@ def test_query_match(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -526,10 +526,10 @@ def test_result_with_owner(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -897,10 +897,10 @@ def test_search_for_charts(mock_graph, searcher): "entity_sub_types": ["Dataset"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], @@ -1012,21 +1012,20 @@ def test_search_for_container(mock_graph, searcher): "entity_type": "Container", "entity_sub_types": ["Database"], }, - "dpia_required": "False", - "dpia_location": None, + "dpia_required": False, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", "usage_restrictions": UsageRestrictions( - status=None, - dpia_required="False", - dpia_location=None, + dpia_required=False, + dpia_location="", ), "access_information": AccessInformation( where_to_access_dataset="", source_dataset_name="", - s3_location=None, + s3_location="", ), "data_summary": DataSummary(), }, @@ -1097,10 +1096,10 @@ def test_list_database_tables(mock_graph, searcher): "entity_sub_types": ["Table"], }, "dpia_required": None, - "dpia_location": None, + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": None, + "s3_location": "", "row_count": "", }, tags=[], diff --git a/lib/datahub-client/tests/snapshots/test_upsert_table.json b/lib/datahub-client/tests/snapshots/test_upsert_table.json index da541768..729a212e 100644 --- a/lib/datahub-client/tests/snapshots/test_upsert_table.json +++ b/lib/datahub-client/tests/snapshots/test_upsert_table.json @@ -8,10 +8,10 @@ "json": { "customProperties": { "dpia_required": "True", - "dpia_location": "None", + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": "None", + "s3_location": "", "row_count": "5" }, "name": "Dataset", @@ -115,4 +115,4 @@ } } } -] \ No newline at end of file +] diff --git a/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json b/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json index 903e8004..ff6fdcca 100644 --- a/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json +++ b/lib/datahub-client/tests/snapshots/test_upsert_table_and_database.json @@ -21,7 +21,7 @@ "json": { "customProperties": { "dpia_required": "False", - "dpia_location": "None", + "dpia_location": "", "where_to_access_dataset": "analytical_platform", "source_dataset_name": "", "s3_location": "s3://databucket/", @@ -65,10 +65,10 @@ "json": { "customProperties": { "dpia_required": "True", - "dpia_location": "None", + "dpia_location": "", "where_to_access_dataset": "", "source_dataset_name": "", - "s3_location": "None", + "s3_location": "", "row_count": "5" }, "name": "Dataset", @@ -172,4 +172,4 @@ } } } -] \ No newline at end of file +] From 5485ffa425372e86f605a97dc24e1cc4af7673ca Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Thu, 2 May 2024 11:43:33 +0100 Subject: [PATCH 51/64] Configure pytest to test both projects --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bce668c5..7ffe9a72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ build-backend = "poetry.core.masonry.api" DJANGO_SETTINGS_MODULE = "core.settings" python_files = ["test_*.py", "*_test.py", "testing/python/*.py"] markers = ["slow: marks tests as slow (deselect with '-m \"not slow\"')"] +testpaths = ["tests", "lib/data_platform_catalogue/tests"] [tool.isort] profile = "black" From 9bad477a331e56d1c13d8380519b77999ae17bc7 Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Wed, 1 May 2024 17:09:54 +0100 Subject: [PATCH 52/64] fix relative paths --- lib/datahub-client/tests/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/datahub-client/tests/conftest.py b/lib/datahub-client/tests/conftest.py index 8878a6c5..a8c92c2e 100644 --- a/lib/datahub-client/tests/conftest.py +++ b/lib/datahub-client/tests/conftest.py @@ -3,6 +3,7 @@ import pytest from datahub.metadata.schema_classes import DomainPropertiesClass + from tests.test_helpers.graph_helpers import MockDataHubGraph from tests.test_helpers.mce_helpers import check_golden_file @@ -29,7 +30,8 @@ def base_mock_graph( @pytest.fixture def test_snapshots_dir(pytestconfig: pytest.Config) -> Path: - return pytestconfig.rootpath / "tests/snapshots" + rootpath = Path(__file__).parent + return rootpath / "snapshots" @pytest.fixture From 011bf5442a93699c05b7f5bd4948955cf1685daa Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Thu, 2 May 2024 11:54:51 +0100 Subject: [PATCH 53/64] Fix post-merge incompatabilities between lib/datahub-client and the django app - Remove lib from gitignore - Update pydantic to latest version - Depend on relative path rather than pypi --- .gitignore | 1 - lib/datahub-client/poetry.lock | 197 ++- lib/datahub-client/pyproject.toml | 2 +- poetry.lock | 1865 +++++++---------------------- pyproject.toml | 4 +- 5 files changed, 503 insertions(+), 1566 deletions(-) diff --git a/.gitignore b/.gitignore index 48c5d70b..647dea36 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ lib64/ parts/ sdist/ diff --git a/lib/datahub-client/poetry.lock b/lib/datahub-client/poetry.lock index dc0e795f..0b23519d 100644 --- a/lib/datahub-client/poetry.lock +++ b/lib/datahub-client/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "acryl-datahub" @@ -1039,18 +1039,18 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pydantic" -version = "2.0" +version = "2.7.1" description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-2.0-py3-none-any.whl", hash = "sha256:8bf7355be5e1207c756dfbc8046236dadd4ce04101fb482e6c8834a06d9aa04f"}, - {file = "pydantic-2.0.tar.gz", hash = "sha256:6e313661b310eb5b2c45168ce05d8dd79f57563adaf3906162a917585576b846"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.0.1" +pydantic-core = "2.18.2" typing-extensions = ">=4.6.1" [package.extras] @@ -1058,99 +1058,94 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.0.1" -description = "" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.0.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:92b01e166a3b69e8054308709acabec1bae65dae83ba6329f4fcc8448e170a06"}, - {file = "pydantic_core-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae53240f9f92f634b73a3e5ee87b9ec8ac38d5bee96ea65034af58f48d489a65"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0dd6bb98271519a309e96e927b52f8ca1323a99762bec87cda8fdaaa221e5cd"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c656b8d4603af6744ed2f2c0be499790f0913a2186ef7214c88d47d42051ae4b"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_armv7l.whl", hash = "sha256:ddbad540cba15b5262bd800bb6f0746a4ac719de0fe0a2acab8e0d50eb54ba9a"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:e2e9025e132761e7ea8dab448923ccd8839c60199e863a6348d7e8b1a674edd1"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_24_s390x.whl", hash = "sha256:ac6a57d01c0b67563dd273f2b71e9aab643573b569a202bfff7dad502b0b8ee0"}, - {file = "pydantic_core-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44c8cec1d74d74c29da59c86e8cd472851c85b44d75128096ef3751c5c87c204"}, - {file = "pydantic_core-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76d5d18ef9065ecbf62d6ec82c45ddbb47174a7400eb780040a7ebdad1c0ead8"}, - {file = "pydantic_core-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:722aa193ba1f587226991a789a3f098235b5f04e85cf815af9e8ad823a5a85e1"}, - {file = "pydantic_core-2.0.1-cp310-none-win32.whl", hash = "sha256:16977790d69bac6034baa2349326db2ff465ad346c53b8d54c3674e05b070af2"}, - {file = "pydantic_core-2.0.1-cp310-none-win_amd64.whl", hash = "sha256:0fcdb43190588f6219709b43ffa679e562c0d4a44a50aafb6cc88978da4a84b7"}, - {file = "pydantic_core-2.0.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:73c464afa0a959472045f242ef7cdaf6a38b76a6d7dfa1ef270de0967c04408d"}, - {file = "pydantic_core-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab7eafb33fdc7aa8667634be58a3d1c8ed3fa8923c6bc5014657bf95b51b4a46"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf3e484bc8e8c8a568d572a6619696d7e2e2aef214b0be503f0814f8bafca9f"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc99af5be239961d718bbf8e4d6bd1caa6de556e44ed08eb5135cfbefc958728"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_armv7l.whl", hash = "sha256:e55fc76ce657208c0d7e21e2e96925993dd4063d5c5ee9227dcdf4e550c02a29"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_ppc64le.whl", hash = "sha256:ccb06e1667a9784a96e0fc2500b989b8afbe9ac68a39a3c806c056ee228eff3c"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_24_s390x.whl", hash = "sha256:b23ae8b27b6eff72909a9a88123ac28b746d95f25927ce67d3b0f3dabe099a0a"}, - {file = "pydantic_core-2.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6387c2956baf16891e7bc20d864a769c0f9f61799d4895c8f493e2de8f7b88aa"}, - {file = "pydantic_core-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1c855ef11370eacff25556658fb7fa243e8c0bd4235fa20a0f473bded2ede252"}, - {file = "pydantic_core-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f9452d470012ee86a00a36f7673843038fd1a88661a28c72e65e7f3f084da8d8"}, - {file = "pydantic_core-2.0.1-cp311-none-win32.whl", hash = "sha256:0872a1c52da4cfc494e23c83532c7fc1313de311a14334b7a58216a8dea828e0"}, - {file = "pydantic_core-2.0.1-cp311-none-win_amd64.whl", hash = "sha256:7a4fc3e8c788798739f4aa6772d994e4453a17dadb1b8eea4582a31cdfe683d2"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:ddb23eaf427dbbde41b543d98a0c4a7aeb73bf649e3faa75b94a2fd882a669ba"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:4eda2b350b02293c7060f2371ad3ce7b00342bd61c8654d2ba374bd10c6b6b66"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7727a4fcb93572d4e521b028f1c64f1eda2da49d506b1a6208576faa9e0acd64"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:007cdcee7e1a40951768d0d250e566b603e25d0fa8b8302901e38560bc9badf9"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_armv7l.whl", hash = "sha256:89123ab11a23fa9c332655933350dc231945ca6b1148c1e1960aad0a5a6de1c0"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:03d12c44decb122d5feede5408cc6c67e506b64016ce4b59c825d1a8c90f288a"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_24_s390x.whl", hash = "sha256:ff015389ae4ca6869a2fdd16c21ee1ce7c134503f2148efd46db643ce27ca520"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8daded5c64811da4bdc7d6792afa10328bff5c3514536f69457596d4a2646b49"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a1dd1b182fde9f95f1cc28964612fb1b180fdd3ca2cac881c108db29906b2e01"}, - {file = "pydantic_core-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c8e53bae6e58a8ff8e93f9a77440cfe8fc017bb9a8430dc03beb6bdd648572d2"}, - {file = "pydantic_core-2.0.1-cp37-none-win32.whl", hash = "sha256:a7d0de538719feda5cabf19c63cc17345df6a0ab579b95518925d2b25276daaf"}, - {file = "pydantic_core-2.0.1-cp37-none-win_amd64.whl", hash = "sha256:1bb6d1057c054056614aefeced05299d3590acf76768538b34ebec9cbbf26953"}, - {file = "pydantic_core-2.0.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:9ee1c2d0cf5c92faf722ff366814859c764c82b30af7f91b9b1950e15efecb9e"}, - {file = "pydantic_core-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:adc2efaf0c45135214dff4d18d4aaf2b692249cb369f921fe0fde3a13cf7ddad"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8927c166f20e3933cc9a9a68701acc8de22ee54b70d8c4044ad461b043b3cf9b"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adbfc6c7ddd1cca6efe62a0292cae7cf2d05c9ebb139d0da10b0d44346e253c7"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_armv7l.whl", hash = "sha256:659f22427d653769d1b4c672fd2daf53e639a5a93b0dd6fc0b37ef822a6e77d7"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:71cf43912edeae476f47d16520e48bddbf9af0ebdd98961c38ca8944f4f22b9d"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_24_s390x.whl", hash = "sha256:10736490eacc426d681ae6f00f1d8ce01fc77c45086a597e829c3eed127179b1"}, - {file = "pydantic_core-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5f3158cb4cda580f3b063b03257c7f5c2d9e66f9c2a93466c76056f7c4d5a3b7"}, - {file = "pydantic_core-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ca5a743af642700fc69dc64e0b964dd7499dcabb399e5cc2223fbc9cb33965d"}, - {file = "pydantic_core-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fa5a3d49ddbeaa80bb2a8927b90e9cdd43373616ba0b7b7a74a3ae33b5c9640c"}, - {file = "pydantic_core-2.0.1-cp38-none-win32.whl", hash = "sha256:d6e21da7f7e3935b24bfd17d7c3eefe4c1edca380edaec854a8593796d8d96f1"}, - {file = "pydantic_core-2.0.1-cp38-none-win_amd64.whl", hash = "sha256:0b154abef540a76bb2b7a641b3ae1e05e5c4b08eb9ad6c27a217b3c64ffcda0b"}, - {file = "pydantic_core-2.0.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:aad8b177370002f73f08eafefa3d969d9c4498da6d67d8a43ffdeb4b4e560e1c"}, - {file = "pydantic_core-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9cf1ba93657cad863d23ecb09227665c0abe26c131acd24abb5edc6249a36a70"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79225132aa1fe97a5e947da820b323d63372fb3475d94ff81ca6f91669717a01"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bd78c4f04794a8e527d32c8ec1a26682b35b5c9347bb6e3cc853ba1a43c72a5"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_armv7l.whl", hash = "sha256:bb2daa4e3d4efbf2e2dedc1a7cea3e48ff12d0c95ab2011e7f731bdc97d16ed0"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:958964a0ad0cea700b25037b21f5a2da38d19bddaa2f15ce36f51c048a9efe92"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_24_s390x.whl", hash = "sha256:e0edd3c6762b3ff3fdbd90517a09808e5d67cce86d7c43ec6f5ca3f65bfe7fd9"}, - {file = "pydantic_core-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e76a9b0c2b2fb29a80764e106b1ea35c1b96a4e62e7ce7dde44f5df153fd5b66"}, - {file = "pydantic_core-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:88fc72e60d818924cb3d32948b682bcea0dadd0fd2efae9a4d0b7a55e310908a"}, - {file = "pydantic_core-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c04aa22ded4baf29c3c1ec3b76d5264dd91794b974a737251fdd0827abcc2c78"}, - {file = "pydantic_core-2.0.1-cp39-none-win32.whl", hash = "sha256:4372e8fcb458aad1e155c04e663ff1840f36b859fb1422578372712a78866051"}, - {file = "pydantic_core-2.0.1-cp39-none-win_amd64.whl", hash = "sha256:c5fef2dc7ed589ea83ac5ce526fcb8e8eb0ab79bfa67f958dafbda0a05ab3018"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e4785a8c5440e410394f88e30c3db862ed05841595311ddc969b3fde377f95ea"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e90b99b6aa9fd6eee6d6f86921d38252c6e55c319dc6c5e411922d0dc173825"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:182a0e5ce9382a0a77aab8407ead303b6e310c673a46b18937fa1a90c22ccbc4"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b83e11a68936f80ee92ef1001bf6b9fedf0602396acc417b16a9c136a9b3b7bd"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5e4a918eeae2c566fdcad9ee89d8708a59dc5ec3d5083b61a886b19f82f69f5c"}, - {file = "pydantic_core-2.0.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:88b56a4e7480f4f22fa2faefdb0a887d70420d9cd8cb160677e8abe46769e7b0"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3a85fde791e6567f879b50b59f1740afc55333060d93548d6bbb46bf1b6a1b49"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef349e4ac794559c1538787a0fbce378a1beb991ef4f7707a6cde3156294259d"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f90928ed48b91d93add357fb1e81cef729bffaff3ab88882b76549434b4574"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3f8fea22690c6c33c4d36d2236732da29da560f815cd9aba1d3b5ab59dcb214"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbbefd38ef80b37d056592c366a164a37b4e87b12f0aba23c35087d890fb31ba"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:8945ba48644b45d4e66cc3e56b896e97fb1d7f166dd0ee1eb137bbfdf1285483"}, - {file = "pydantic_core-2.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1c318bd2bdaa88ec078dc7932e108a9c43caeabc84d2cf545081fb6a99ed1b90"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:7176ffa02c45d557cceb75f1290a2ddf53da680c6878aae54e69aafb21c52efd"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:176eb3ec03da4c36da7708d2398139e13d1130b3b3d1af4334a959f46278baa9"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46bb28295082a22f3c7f5fa5546d669aed7eb43151ec0032e8c352c59f5e36af"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4b4c836100e5f07189b0aea8b4afae326f169bfdef91e86fd90a0d3c27f0c75"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b56f3758b82f26414a4dccd76f05c768df7bd2735e0ac43f3dfff2f5603d32a9"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0c8877d9e0bd128f103a1b0f02899aa7d4be1104eef5dc35e2b633042b64a2d1"}, - {file = "pydantic_core-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1672c8c36414c56adf704753b2d7e22e7528d7bd21cd357f24edeff76d4fd4ca"}, - {file = "pydantic_core-2.0.1.tar.gz", hash = "sha256:f9fffcb5507bff84a1312d1616406cad157806f105d78bd184d1e6b3b00e6417"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] [package.dependencies] -typing-extensions = [ - {version = ">=4.6.0", markers = "platform_python_implementation != \"PyPy\""}, - {version = ">=4.6.0,<4.7.0", markers = "platform_python_implementation == \"PyPy\""}, -] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyreadline3" @@ -1266,7 +1261,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1662,17 +1656,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "typing-extensions" -version = "4.6.3" -description = "Backported and Experimental Type Hints for Python 3.7+" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, - {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, -] - [[package]] name = "typing-extensions" version = "4.11.0" @@ -1901,4 +1884,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "3961dc21b5a8bab7e68d7e692051f52bed11968d359e023fdb75904707700fa3" +content-hash = "77d46ee3bba8663a195229559ba90622abd809707c8dab3e0738b163e4886a05" diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index d54a07c8..d676a12b 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -22,7 +22,7 @@ python = "^3.10" acryl-datahub = { extras = ["datahub-rest"], version = "^0.12.1.3" } freezegun = "^1.4.0" deepdiff = "^6.7.1" -pydantic = "2" +pydantic = "^2.7.1" [tool.poetry.group.dev.dependencies] requests-mock = "^1.11.0" diff --git a/poetry.lock b/poetry.lock index 251a9bde..c3a1a8f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "acryl-datahub" @@ -122,87 +122,87 @@ vertica = ["Deprecated", "PyYAML", "acryl-sqlglot (==20.4.1.dev14)", "aiohttp (< [[package]] name = "aiohttp" -version = "3.9.4" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:76d32588ef7e4a3f3adff1956a0ba96faabbdee58f2407c122dd45aa6e34f372"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56181093c10dbc6ceb8a29dfeea1e815e1dfdc020169203d87fd8d37616f73f9"}, - {file = "aiohttp-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7a5b676d3c65e88b3aca41816bf72831898fcd73f0cbb2680e9d88e819d1e4d"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1df528a85fb404899d4207a8d9934cfd6be626e30e5d3a5544a83dbae6d8a7e"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f595db1bceabd71c82e92df212dd9525a8a2c6947d39e3c994c4f27d2fe15b11"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c0b09d76e5a4caac3d27752027fbd43dc987b95f3748fad2b924a03fe8632ad"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689eb4356649ec9535b3686200b231876fb4cab4aca54e3bece71d37f50c1d13"}, - {file = "aiohttp-3.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3666cf4182efdb44d73602379a66f5fdfd5da0db5e4520f0ac0dcca644a3497"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b65b0f8747b013570eea2f75726046fa54fa8e0c5db60f3b98dd5d161052004a"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1885d2470955f70dfdd33a02e1749613c5a9c5ab855f6db38e0b9389453dce7"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0593822dcdb9483d41f12041ff7c90d4d1033ec0e880bcfaf102919b715f47f1"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:47f6eb74e1ecb5e19a78f4a4228aa24df7fbab3b62d4a625d3f41194a08bd54f"}, - {file = "aiohttp-3.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c8b04a3dbd54de6ccb7604242fe3ad67f2f3ca558f2d33fe19d4b08d90701a89"}, - {file = "aiohttp-3.9.4-cp310-cp310-win32.whl", hash = "sha256:8a78dfb198a328bfb38e4308ca8167028920fb747ddcf086ce706fbdd23b2926"}, - {file = "aiohttp-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:e78da6b55275987cbc89141a1d8e75f5070e577c482dd48bd9123a76a96f0bbb"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c111b3c69060d2bafc446917534150fd049e7aedd6cbf21ba526a5a97b4402a5"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efbdd51872cf170093998c87ccdf3cb5993add3559341a8e5708bcb311934c94"}, - {file = "aiohttp-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bfdb41dc6e85d8535b00d73947548a748e9534e8e4fddd2638109ff3fb081df"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd9d334412961125e9f68d5b73c1d0ab9ea3f74a58a475e6b119f5293eee7ba"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35d78076736f4a668d57ade00c65d30a8ce28719d8a42471b2a06ccd1a2e3063"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:824dff4f9f4d0f59d0fa3577932ee9a20e09edec8a2f813e1d6b9f89ced8293f"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52b8b4e06fc15519019e128abedaeb56412b106ab88b3c452188ca47a25c4093"}, - {file = "aiohttp-3.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eae569fb1e7559d4f3919965617bb39f9e753967fae55ce13454bec2d1c54f09"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69b97aa5792428f321f72aeb2f118e56893371f27e0b7d05750bcad06fc42ca1"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d79aad0ad4b980663316f26d9a492e8fab2af77c69c0f33780a56843ad2f89e"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d6577140cd7db19e430661e4b2653680194ea8c22c994bc65b7a19d8ec834403"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:9860d455847cd98eb67897f5957b7cd69fbcb436dd3f06099230f16a66e66f79"}, - {file = "aiohttp-3.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69ff36d3f8f5652994e08bd22f093e11cfd0444cea310f92e01b45a4e46b624e"}, - {file = "aiohttp-3.9.4-cp311-cp311-win32.whl", hash = "sha256:e27d3b5ed2c2013bce66ad67ee57cbf614288bda8cdf426c8d8fe548316f1b5f"}, - {file = "aiohttp-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d6a67e26daa686a6fbdb600a9af8619c80a332556245fa8e86c747d226ab1a1e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c5ff8ff44825736a4065d8544b43b43ee4c6dd1530f3a08e6c0578a813b0aa35"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d12a244627eba4e9dc52cbf924edef905ddd6cafc6513849b4876076a6f38b0e"}, - {file = "aiohttp-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcad56c8d8348e7e468899d2fb3b309b9bc59d94e6db08710555f7436156097f"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7e69a7fd4b5ce419238388e55abd220336bd32212c673ceabc57ccf3d05b55"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4870cb049f10d7680c239b55428916d84158798eb8f353e74fa2c98980dcc0b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2feaf1b7031ede1bc0880cec4b0776fd347259a723d625357bb4b82f62687b"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939393e8c3f0a5bcd33ef7ace67680c318dc2ae406f15e381c0054dd658397de"}, - {file = "aiohttp-3.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d2334e387b2adcc944680bebcf412743f2caf4eeebd550f67249c1c3696be04"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0198ea897680e480845ec0ffc5a14e8b694e25b3f104f63676d55bf76a82f1a"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e40d2cd22914d67c84824045861a5bb0fb46586b15dfe4f046c7495bf08306b2"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:aba80e77c227f4234aa34a5ff2b6ff30c5d6a827a91d22ff6b999de9175d71bd"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:fb68dc73bc8ac322d2e392a59a9e396c4f35cb6fdbdd749e139d1d6c985f2527"}, - {file = "aiohttp-3.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f3460a92638dce7e47062cf088d6e7663adb135e936cb117be88d5e6c48c9d53"}, - {file = "aiohttp-3.9.4-cp312-cp312-win32.whl", hash = "sha256:32dc814ddbb254f6170bca198fe307920f6c1308a5492f049f7f63554b88ef36"}, - {file = "aiohttp-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:63f41a909d182d2b78fe3abef557fcc14da50c7852f70ae3be60e83ff64edba5"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c3770365675f6be220032f6609a8fbad994d6dcf3ef7dbcf295c7ee70884c9af"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:305edae1dea368ce09bcb858cf5a63a064f3bff4767dec6fa60a0cc0e805a1d3"}, - {file = "aiohttp-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6f121900131d116e4a93b55ab0d12ad72573f967b100e49086e496a9b24523ea"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b71e614c1ae35c3d62a293b19eface83d5e4d194e3eb2fabb10059d33e6e8cbf"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:419f009fa4cfde4d16a7fc070d64f36d70a8d35a90d71aa27670bba2be4fd039"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b39476ee69cfe64061fd77a73bf692c40021f8547cda617a3466530ef63f947"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b33f34c9c7decdb2ab99c74be6443942b730b56d9c5ee48fb7df2c86492f293c"}, - {file = "aiohttp-3.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c78700130ce2dcebb1a8103202ae795be2fa8c9351d0dd22338fe3dac74847d9"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:268ba22d917655d1259af2d5659072b7dc11b4e1dc2cb9662fdd867d75afc6a4"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:17e7c051f53a0d2ebf33013a9cbf020bb4e098c4bc5bce6f7b0c962108d97eab"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7be99f4abb008cb38e144f85f515598f4c2c8932bf11b65add0ff59c9c876d99"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d58a54d6ff08d2547656356eea8572b224e6f9bbc0cf55fa9966bcaac4ddfb10"}, - {file = "aiohttp-3.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7673a76772bda15d0d10d1aa881b7911d0580c980dbd16e59d7ba1422b2d83cd"}, - {file = "aiohttp-3.9.4-cp38-cp38-win32.whl", hash = "sha256:e4370dda04dc8951012f30e1ce7956a0a226ac0714a7b6c389fb2f43f22a250e"}, - {file = "aiohttp-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:eb30c4510a691bb87081192a394fb661860e75ca3896c01c6d186febe7c88530"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:84e90494db7df3be5e056f91412f9fa9e611fbe8ce4aaef70647297f5943b276"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d4845f8501ab28ebfdbeab980a50a273b415cf69e96e4e674d43d86a464df9d"}, - {file = "aiohttp-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69046cd9a2a17245c4ce3c1f1a4ff8c70c7701ef222fce3d1d8435f09042bba1"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b73a06bafc8dcc508420db43b4dd5850e41e69de99009d0351c4f3007960019"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:418bb0038dfafeac923823c2e63226179976c76f981a2aaad0ad5d51f2229bca"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a8f241456b6c2668374d5d28398f8e8cdae4cce568aaea54e0f39359cd928d"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935c369bf8acc2dc26f6eeb5222768aa7c62917c3554f7215f2ead7386b33748"}, - {file = "aiohttp-3.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4e48c8752d14ecfb36d2ebb3d76d614320570e14de0a3aa7a726ff150a03c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:916b0417aeddf2c8c61291238ce25286f391a6acb6f28005dd9ce282bd6311b6"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9b6787b6d0b3518b2ee4cbeadd24a507756ee703adbac1ab6dc7c4434b8c572a"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:221204dbda5ef350e8db6287937621cf75e85778b296c9c52260b522231940ed"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:10afd99b8251022ddf81eaed1d90f5a988e349ee7d779eb429fb07b670751e8c"}, - {file = "aiohttp-3.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2506d9f7a9b91033201be9ffe7d89c6a54150b0578803cce5cb84a943d075bc3"}, - {file = "aiohttp-3.9.4-cp39-cp39-win32.whl", hash = "sha256:e571fdd9efd65e86c6af2f332e0e95dad259bfe6beb5d15b3c3eca3a6eb5d87b"}, - {file = "aiohttp-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:7d29dd5319d20aa3b7749719ac9685fbd926f71ac8c77b2477272725f882072d"}, - {file = "aiohttp-3.9.4.tar.gz", hash = "sha256:6ff71ede6d9a5a58cfb7b6fffc83ab5d4a63138276c771ac91ceaaddf5459644"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -230,24 +230,14 @@ files = [ frozenlist = ">=1.1.0" [[package]] -name = "antlr4-python3-runtime" -version = "4.9.2" -description = "ANTLR 4.9.2 runtime for Python 3.7" -optional = false -python-versions = "*" -files = [ - {file = "antlr4-python3-runtime-4.9.2.tar.gz", hash = "sha256:31f5abdc7faf16a1a6e9bf2eb31565d004359b821b09944436a34361929ae85a"}, -] - -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [[package]] @@ -312,56 +302,35 @@ files = [ avro = ">=1.10" six = "*" -[[package]] -name = "beautifulsoup4" -version = "4.12.3" -description = "Screen-scraping library" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, -] - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -cchardet = ["cchardet"] -chardet = ["chardet"] -charset-normalizer = ["charset-normalizer"] -html5lib = ["html5lib"] -lxml = ["lxml"] - [[package]] name = "black" -version = "24.4.0" +version = "24.4.2" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-24.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436"}, - {file = "black-24.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf"}, - {file = "black-24.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad"}, - {file = "black-24.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb"}, - {file = "black-24.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8"}, - {file = "black-24.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745"}, - {file = "black-24.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070"}, - {file = "black-24.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397"}, - {file = "black-24.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"}, - {file = "black-24.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33"}, - {file = "black-24.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965"}, - {file = "black-24.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd"}, - {file = "black-24.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1"}, - {file = "black-24.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8"}, - {file = "black-24.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d"}, - {file = "black-24.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3"}, - {file = "black-24.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665"}, - {file = "black-24.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6"}, - {file = "black-24.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e"}, - {file = "black-24.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702"}, - {file = "black-24.4.0-py3-none-any.whl", hash = "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e"}, - {file = "black-24.4.0.tar.gz", hash = "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641"}, + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [package.dependencies] @@ -377,44 +346,6 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] -[[package]] -name = "boto3" -version = "1.34.84" -description = "The AWS SDK for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "boto3-1.34.84-py3-none-any.whl", hash = "sha256:7a02f44af32095946587d748ebeb39c3fa15b9d7275307ff612a6760ead47e04"}, - {file = "boto3-1.34.84.tar.gz", hash = "sha256:91e6343474173e9b82f603076856e1d5b7b68f44247bdd556250857a3f16b37b"}, -] - -[package.dependencies] -botocore = ">=1.34.84,<1.35.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.10.0,<0.11.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.34.84" -description = "Low-level, data-driven core of boto 3." -optional = false -python-versions = ">=3.8" -files = [ - {file = "botocore-1.34.84-py3-none-any.whl", hash = "sha256:da1ae0a912e69e10daee2a34dafd6c6c106450d20b8623665feceb2d96c173eb"}, - {file = "botocore-1.34.84.tar.gz", hash = "sha256:a2b309bf5594f0eb6f63f355ade79ba575ce8bf672e52e91da1a7933caa245e6"}, -] - -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} - -[package.extras] -crt = ["awscrt (==0.19.19)"] - [[package]] name = "cached-property" version = "1.5.2" @@ -426,17 +357,6 @@ files = [ {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] -[[package]] -name = "cachetools" -version = "5.3.3" -description = "Extensible memoizing collections and decorators" -optional = false -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, -] - [[package]] name = "certifi" version = "2024.2.2" @@ -523,17 +443,6 @@ files = [ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] -[[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, -] - [[package]] name = "charset-normalizer" version = "3.3.2" @@ -678,26 +587,6 @@ files = [ [package.extras] test = ["click", "pytest", "six"] -[[package]] -name = "collate-sqllineage" -version = "1.2.7" -description = "Collate SQL Lineage for Analysis Tool powered by Python and sqlfluff based on sqllineage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "collate-sqllineage-1.2.7.tar.gz", hash = "sha256:a00b07323c5e6a8547375a87d0ac755c3beb3e797bc2f95dbb18e23de9f1a37f"}, - {file = "collate_sqllineage-1.2.7-py3-none-any.whl", hash = "sha256:6fad9e0fcae0fc458b9d25ad242f5d01033551dce8b1a9bc84b3b5f6f6673063"}, -] - -[package.dependencies] -networkx = ">=2.4" -sqlfluff = ">=2.1.4,<3" -sqlparse = ">=0.4.4,<0.5" - -[package.extras] -ci = ["bandit", "black", "flake8", "flake8-blind-except", "flake8-builtins", "flake8-import-order", "flake8-logging-format", "mypy", "pytest", "pytest-cov", "tox", "twine", "wheel"] -docs = ["Sphinx (>=3.2.0)", "sphinx-rtd-theme (>=0.5.0)"] - [[package]] name = "colorama" version = "0.4.6" @@ -711,136 +600,68 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:432949a32c3e3f820af808db1833d6d1631664d53dd3ce487aa25d574e18ad1c"}, + {file = "coverage-7.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bd7065249703cbeb6d4ce679c734bef0ee69baa7bff9724361ada04a15b7e3b"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbfe6389c5522b99768a93d89aca52ef92310a96b99782973b9d11e80511f932"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39793731182c4be939b4be0cdecde074b833f6171313cf53481f869937129ed3"}, + {file = "coverage-7.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a5dbe1ba1bf38d6c63b6d2c42132d45cbee6d9f0c51b52c59aa4afba057517"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:357754dcdfd811462a725e7501a9b4556388e8ecf66e79df6f4b988fa3d0b39a"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a81eb64feded34f40c8986869a2f764f0fe2db58c0530d3a4afbcde50f314880"}, + {file = "coverage-7.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:51431d0abbed3a868e967f8257c5faf283d41ec882f58413cf295a389bb22e58"}, + {file = "coverage-7.5.0-cp310-cp310-win32.whl", hash = "sha256:f609ebcb0242d84b7adeee2b06c11a2ddaec5464d21888b2c8255f5fd6a98ae4"}, + {file = "coverage-7.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6782cd6216fab5a83216cc39f13ebe30adfac2fa72688c5a4d8d180cd52e8f6a"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e768d870801f68c74c2b669fc909839660180c366501d4cc4b87efd6b0eee375"}, + {file = "coverage-7.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:84921b10aeb2dd453247fd10de22907984eaf80901b578a5cf0bb1e279a587cb"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710c62b6e35a9a766b99b15cdc56d5aeda0914edae8bb467e9c355f75d14ee95"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c379cdd3efc0658e652a14112d51a7668f6bfca7445c5a10dee7eabecabba19d"}, + {file = "coverage-7.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea9d3ca80bcf17edb2c08a4704259dadac196fe5e9274067e7a20511fad1743"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:41327143c5b1d715f5f98a397608f90ab9ebba606ae4e6f3389c2145410c52b1"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:565b2e82d0968c977e0b0f7cbf25fd06d78d4856289abc79694c8edcce6eb2de"}, + {file = "coverage-7.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf3539007202ebfe03923128fedfdd245db5860a36810136ad95a564a2fdffff"}, + {file = "coverage-7.5.0-cp311-cp311-win32.whl", hash = "sha256:bf0b4b8d9caa8d64df838e0f8dcf68fb570c5733b726d1494b87f3da85db3a2d"}, + {file = "coverage-7.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c6384cc90e37cfb60435bbbe0488444e54b98700f727f16f64d8bfda0b84656"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fed7a72d54bd52f4aeb6c6e951f363903bd7d70bc1cad64dd1f087980d309ab9"}, + {file = "coverage-7.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cbe6581fcff7c8e262eb574244f81f5faaea539e712a058e6707a9d272fe5b64"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad97ec0da94b378e593ef532b980c15e377df9b9608c7c6da3506953182398af"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd4bacd62aa2f1a1627352fe68885d6ee694bdaebb16038b6e680f2924a9b2cc"}, + {file = "coverage-7.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf032b6c105881f9d77fa17d9eebe0ad1f9bfb2ad25777811f97c5362aa07f2"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ba01d9ba112b55bfa4b24808ec431197bb34f09f66f7cb4fd0258ff9d3711b1"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f0bfe42523893c188e9616d853c47685e1c575fe25f737adf473d0405dcfa7eb"}, + {file = "coverage-7.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a9a7ef30a1b02547c1b23fa9a5564f03c9982fc71eb2ecb7f98c96d7a0db5cf2"}, + {file = "coverage-7.5.0-cp312-cp312-win32.whl", hash = "sha256:3c2b77f295edb9fcdb6a250f83e6481c679335ca7e6e4a955e4290350f2d22a4"}, + {file = "coverage-7.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:427e1e627b0963ac02d7c8730ca6d935df10280d230508c0ba059505e9233475"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9dd88fce54abbdbf4c42fb1fea0e498973d07816f24c0e27a1ecaf91883ce69e"}, + {file = "coverage-7.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a898c11dca8f8c97b467138004a30133974aacd572818c383596f8d5b2eb04a9"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07dfdd492d645eea1bd70fb1d6febdcf47db178b0d99161d8e4eed18e7f62fe7"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3d117890b6eee85887b1eed41eefe2e598ad6e40523d9f94c4c4b213258e4a4"}, + {file = "coverage-7.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6afd2e84e7da40fe23ca588379f815fb6dbbb1b757c883935ed11647205111cb"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a9960dd1891b2ddf13a7fe45339cd59ecee3abb6b8326d8b932d0c5da208104f"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ced268e82af993d7801a9db2dbc1d2322e786c5dc76295d8e89473d46c6b84d4"}, + {file = "coverage-7.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7c211f25777746d468d76f11719e64acb40eed410d81c26cefac641975beb88"}, + {file = "coverage-7.5.0-cp38-cp38-win32.whl", hash = "sha256:262fffc1f6c1a26125d5d573e1ec379285a3723363f3bd9c83923c9593a2ac25"}, + {file = "coverage-7.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:eed462b4541c540d63ab57b3fc69e7d8c84d5957668854ee4e408b50e92ce26a"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0194d654e360b3e6cc9b774e83235bae6b9b2cac3be09040880bb0e8a88f4a1"}, + {file = "coverage-7.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33c020d3322662e74bc507fb11488773a96894aa82a622c35a5a28673c0c26f5"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbdf2cae14a06827bec50bd58e49249452d211d9caddd8bd80e35b53cb04631"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3235d7c781232e525b0761730e052388a01548bd7f67d0067a253887c6e8df46"}, + {file = "coverage-7.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2de4e546f0ec4b2787d625e0b16b78e99c3e21bc1722b4977c0dddf11ca84e"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0e206259b73af35c4ec1319fd04003776e11e859936658cb6ceffdeba0f5be"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2055c4fb9a6ff624253d432aa471a37202cd8f458c033d6d989be4499aed037b"}, + {file = "coverage-7.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:075299460948cd12722a970c7eae43d25d37989da682997687b34ae6b87c0ef0"}, + {file = "coverage-7.5.0-cp39-cp39-win32.whl", hash = "sha256:280132aada3bc2f0fac939a5771db4fbb84f245cb35b94fae4994d4c1f80dae7"}, + {file = "coverage-7.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:c58536f6892559e030e6924896a44098bc1290663ea12532c78cef71d0df8493"}, + {file = "coverage-7.5.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:2b57780b51084d5223eee7b59f0d4911c31c16ee5aa12737c7a02455829ff067"}, + {file = "coverage-7.5.0.tar.gz", hash = "sha256:cf62d17310f34084c59c01e027259076479128d11e4661bb6c9acb38c5e19bb8"}, ] [package.extras] toml = ["tomli"] -[[package]] -name = "croniter" -version = "1.3.15" -description = "croniter provides iteration for datetime object with cron like format" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "croniter-1.3.15-py2.py3-none-any.whl", hash = "sha256:f17f877be1d93b9e3191151584a19d8b367b017ab0febc8c5472b9300da61c4c"}, - {file = "croniter-1.3.15.tar.gz", hash = "sha256:924a38fda88f675ec6835667e1d32ac37ff0d65509c2152729d16ff205e32a65"}, -] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "cryptography" -version = "42.0.5" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - [[package]] name = "deepdiff" version = "6.7.1" @@ -876,26 +697,6 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] -[[package]] -name = "diff-cover" -version = "9.0.0" -description = "Run coverage and linting reports on diffs" -optional = false -python-versions = "<4.0.0,>=3.8.10" -files = [ - {file = "diff_cover-9.0.0-py3-none-any.whl", hash = "sha256:31b308259b79e2cab5f30aff499a3ea3ba9475f0d495d82ba9b6caa7487bca03"}, - {file = "diff_cover-9.0.0.tar.gz", hash = "sha256:1dc851d3f3f320c048d03618e4c0d9861fa4a1506b425d2d09a564b20c95674a"}, -] - -[package.dependencies] -chardet = ">=3.0.0" -Jinja2 = ">=2.7.1" -pluggy = ">=0.13.1,<2" -Pygments = ">=2.9.0,<3.0.0" - -[package.extras] -toml = ["tomli (>=1.2.1)"] - [[package]] name = "distlib" version = "0.3.8" @@ -940,26 +741,6 @@ files = [ [package.extras] dev = ["nox", "pre-commit"] -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - [[package]] name = "docker" version = "7.0.0" @@ -981,39 +762,6 @@ urllib3 = ">=1.26.0" ssh = ["paramiko (>=2.4.3)"] websockets = ["websocket-client (>=1.3.0)"] -[[package]] -name = "ecdsa" -version = "0.19.0" -description = "ECDSA cryptographic signature library (pure python)" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6" -files = [ - {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"}, - {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"}, -] - -[package.dependencies] -six = ">=1.9.0" - -[package.extras] -gmpy = ["gmpy"] -gmpy2 = ["gmpy2"] - -[[package]] -name = "email-validator" -version = "2.1.1" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"}, - {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "expandvars" version = "0.12.0" @@ -1044,13 +792,13 @@ python-dateutil = ">=2.4" [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [package.extras] @@ -1076,13 +824,13 @@ pyflakes = ">=3.1.0,<3.2.0" [[package]] name = "freezegun" -version = "1.4.0" +version = "1.5.0" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" files = [ - {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, - {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, + {file = "freezegun-1.5.0-py3-none-any.whl", hash = "sha256:ec3f4ba030e34eb6cf7e1e257308aee2c60c3d038ff35996d7475760c9ff3719"}, + {file = "freezegun-1.5.0.tar.gz", hash = "sha256:200a64359b363aa3653d8aac289584078386c7c3da77339d257e46a01fb5c77c"}, ] [package.dependencies] @@ -1174,248 +922,6 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] -[[package]] -name = "google" -version = "3.0.0" -description = "Python bindings to the Google search engine." -optional = false -python-versions = "*" -files = [ - {file = "google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935"}, - {file = "google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe"}, -] - -[package.dependencies] -beautifulsoup4 = "*" - -[[package]] -name = "google-auth" -version = "2.29.0" -description = "Google Authentication Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"}, - {file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "grpcio" -version = "1.62.1" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"}, - {file = "grpcio-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:48611e4fa010e823ba2de8fd3f77c1322dd60cb0d180dc6630a7e157b205f7ea"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b2a0e71b0a2158aa4bce48be9f8f9eb45cbd17c78c7443616d00abbe2a509f6d"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbe80577c7880911d3ad65e5ecc997416c98f354efeba2f8d0f9112a67ed65a5"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f6c693d446964e3292425e1d16e21a97a48ba9172f2d0df9d7b640acb99243"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:77c339403db5a20ef4fed02e4d1a9a3d9866bf9c0afc77a42234677313ea22f3"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b5a4ea906db7dec694098435d84bf2854fe158eb3cd51e1107e571246d4d1d70"}, - {file = "grpcio-1.62.1-cp310-cp310-win32.whl", hash = "sha256:4187201a53f8561c015bc745b81a1b2d278967b8de35f3399b84b0695e281d5f"}, - {file = "grpcio-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:844d1f3fb11bd1ed362d3fdc495d0770cfab75761836193af166fee113421d66"}, - {file = "grpcio-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:833379943d1728a005e44103f17ecd73d058d37d95783eb8f0b28ddc1f54d7b2"}, - {file = "grpcio-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:c7fcc6a32e7b7b58f5a7d27530669337a5d587d4066060bcb9dee7a8c833dfb7"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:fa7d28eb4d50b7cbe75bb8b45ed0da9a1dc5b219a0af59449676a29c2eed9698"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48f7135c3de2f298b833be8b4ae20cafe37091634e91f61f5a7eb3d61ec6f660"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f11fd63365ade276c9d4a7b7df5c136f9030e3457107e1791b3737a9b9ed6a"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b49fd8fe9f9ac23b78437da94c54aa7e9996fbb220bac024a67469ce5d0825f"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:482ae2ae78679ba9ed5752099b32e5fe580443b4f798e1b71df412abf43375db"}, - {file = "grpcio-1.62.1-cp311-cp311-win32.whl", hash = "sha256:1faa02530b6c7426404372515fe5ddf66e199c2ee613f88f025c6f3bd816450c"}, - {file = "grpcio-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bd90b8c395f39bc82a5fb32a0173e220e3f401ff697840f4003e15b96d1befc"}, - {file = "grpcio-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b134d5d71b4e0837fff574c00e49176051a1c532d26c052a1e43231f252d813b"}, - {file = "grpcio-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d1f6c96573dc09d50dbcbd91dbf71d5cf97640c9427c32584010fbbd4c0e0037"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:359f821d4578f80f41909b9ee9b76fb249a21035a061a327f91c953493782c31"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a485f0c2010c696be269184bdb5ae72781344cb4e60db976c59d84dd6354fac9"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50b09b4dc01767163d67e1532f948264167cd27f49e9377e3556c3cba1268e1"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3227c667dccbe38f2c4d943238b887bac588d97c104815aecc62d2fd976e014b"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3952b581eb121324853ce2b191dae08badb75cd493cb4e0243368aa9e61cfd41"}, - {file = "grpcio-1.62.1-cp312-cp312-win32.whl", hash = "sha256:83a17b303425104d6329c10eb34bba186ffa67161e63fa6cdae7776ff76df73f"}, - {file = "grpcio-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:6696ffe440333a19d8d128e88d440f91fb92c75a80ce4b44d55800e656a3ef1d"}, - {file = "grpcio-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:e3393b0823f938253370ebef033c9fd23d27f3eae8eb9a8f6264900c7ea3fb5a"}, - {file = "grpcio-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83e7ccb85a74beaeae2634f10eb858a0ed1a63081172649ff4261f929bacfd22"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:882020c87999d54667a284c7ddf065b359bd00251fcd70279ac486776dbf84ec"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a10383035e864f386fe096fed5c47d27a2bf7173c56a6e26cffaaa5a361addb1"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:960edebedc6b9ada1ef58e1c71156f28689978188cd8cff3b646b57288a927d9"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:23e2e04b83f347d0aadde0c9b616f4726c3d76db04b438fd3904b289a725267f"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978121758711916d34fe57c1f75b79cdfc73952f1481bb9583399331682d36f7"}, - {file = "grpcio-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9084086190cc6d628f282e5615f987288b95457292e969b9205e45b442276407"}, - {file = "grpcio-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:22bccdd7b23c420a27fd28540fb5dcbc97dc6be105f7698cb0e7d7a420d0e362"}, - {file = "grpcio-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8999bf1b57172dbc7c3e4bb3c732658e918f5c333b2942243f10d0d653953ba9"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d9e52558b8b8c2f4ac05ac86344a7417ccdd2b460a59616de49eb6933b07a0bd"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1714e7bc935780bc3de1b3fcbc7674209adf5208ff825799d579ffd6cd0bd505"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8842ccbd8c0e253c1f189088228f9b433f7a93b7196b9e5b6f87dba393f5d5d"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f1e7b36bdff50103af95a80923bf1853f6823dd62f2d2a2524b66ed74103e49"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bba97b8e8883a8038606480d6b6772289f4c907f6ba780fa1f7b7da7dfd76f06"}, - {file = "grpcio-1.62.1-cp38-cp38-win32.whl", hash = "sha256:a7f615270fe534548112a74e790cd9d4f5509d744dd718cd442bf016626c22e4"}, - {file = "grpcio-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6c8c8693df718c5ecbc7babb12c69a4e3677fd11de8886f05ab22d4e6b1c43b"}, - {file = "grpcio-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:73db2dc1b201d20ab7083e7041946910bb991e7e9761a0394bbc3c2632326483"}, - {file = "grpcio-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:407b26b7f7bbd4f4751dbc9767a1f0716f9fe72d3d7e96bb3ccfc4aace07c8de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f8de7c8cef9261a2d0a62edf2ccea3d741a523c6b8a6477a340a1f2e417658de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd5c8a1af40ec305d001c60236308a67e25419003e9bb3ebfab5695a8d0b369"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0477cb31da67846a33b1a75c611f88bfbcd427fe17701b6317aefceee1b96f"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:60dcd824df166ba266ee0cfaf35a31406cd16ef602b49f5d4dfb21f014b0dedd"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:973c49086cabab773525f6077f95e5a993bfc03ba8fc32e32f2c279497780585"}, - {file = "grpcio-1.62.1-cp39-cp39-win32.whl", hash = "sha256:12859468e8918d3bd243d213cd6fd6ab07208195dc140763c00dfe901ce1e1b4"}, - {file = "grpcio-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7209117bbeebdfa5d898205cc55153a51285757902dd73c47de498ad4d11332"}, - {file = "grpcio-1.62.1.tar.gz", hash = "sha256:6c455e008fa86d9e9a9d85bb76da4277c0d7d9668a3bfa70dbe86e9f3c759947"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.62.1)"] - -[[package]] -name = "grpcio-tools" -version = "1.62.1" -description = "Protobuf code generator for gRPC" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-tools-1.62.1.tar.gz", hash = "sha256:a4991e5ee8a97ab791296d3bf7e8700b1445635cc1828cc98df945ca1802d7f2"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:f2b404bcae7e2ef9b0b9803b2a95119eb7507e6dc80ea4a64a78be052c30cebc"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:fdd987a580b4474769adfd40144486f54bcc73838d5ec5d3647a17883ea78e76"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:07af1a6442e2313cff22af93c2c4dd37ae32b5239b38e0d99e2cbf93de65429f"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41384c9ee18e61ef20cad2774ef71bd8854b63efce263b5177aa06fccb84df1f"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c38006f7702d2ff52122e4c77a47348709374050c76216e84b30a9f06e45afa"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08fecc3c5b4e6dd3278f2b9d12837e423c7dcff551ca1e587018b4a0fc5f8019"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a01e8dcd0f041f6fa6d815c54a2017d032950e310c41d514a8bc041e872c4d12"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-win32.whl", hash = "sha256:dd933b8e0b3c13fe3543d58f849a6a5e0d7987688cb6801834278378c724f695"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b04844a9382f1bde4b4174e476e654ab3976168d2469cb4b29e352f4f35a5aa"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:024380536ba71a96cdf736f0954f6ad03f5da609c09edbcc2ca02fdd639e0eed"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:21f14b99e0cd38ad56754cc0b62b2bf3cf75f9f7fc40647da54669e0da0726fe"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:975ac5fb482c23f3608c16e06a43c8bab4d79c2e2564cdbc25cf753c6e998775"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50739aaab0c8076ad5957204e71f2e0c9876e11fd8338f7f09de12c2d75163c5"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598c54318f0326cf5020aa43fc95a15e933aba4a71943d3bff2677d2d21ddfa1"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f309bdb33a61f8e049480d41498ee2e525cfb5e959958b326abfdf552bf9b9cb"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f358effd3c11d66c150e0227f983d54a5cd30e14038566dadcf25f9f6844e6e8"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-win32.whl", hash = "sha256:b76aead9b73f1650a091870fe4e9ed15ac4d8ed136f962042367255199c23594"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:d66a5d47eaa427039752fa0a83a425ff2a487b6a0ac30556fd3be2f3a27a0130"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:575535d039b97d63e6a9abee626d6c7cd47bd8cb73dd00a5c84a98254a2164a4"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:22644c90e43d1a888477899af917979e17364fdd6e9bbb92679cd6a54c4d36c3"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:156d3e1b227c16e903003a56881dbe60e40f2b4bd66f0bc3b27c53e466e6384d"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ad7c5691625a85327e5b683443baf73ae790fd5afc938252041ed5cd665e377"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e140bbc08eea8abf51c0274f45fb1e8350220e64758998d7f3c7f985a0b2496"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7444fcab861911525470d398e5638b70d5cbea3b4674a3de92b5c58c5c515d4d"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e643cd14a5d1e59865cba68a5a6f0175d987f36c5f4cb0db80dee9ed60b4c174"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-win32.whl", hash = "sha256:1344a773d2caa9bb7fbea7e879b84f33740c808c34a5bd2a2768e526117a6b44"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:2eea1db3748b2f37b4dce84d8e0c15d9bc811094807cabafe7b0ea47f424dfd5"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45d2e6cf04d27286b6f73e6e20ba3f0a1f6d8f5535e5dcb1356200419bb457f4"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:46ae58e6926773e7315e9005f0f17aacedbc0895a8752bec087d24efa2f1fb21"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c28086df31478023a36f45e50767872ab3aed2419afff09814cb61c88b77db4"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fba5b339f4797548591036c9481e6895bf920fab7d3dc664d2697f8fb7c0bf"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23eb3d47f78f509fcd201749b1f1e44b76f447913f7fbb3b8bae20f109086295"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fd5d47707bd6bc2b707ece765c362d2a1d2e8f6cd92b04c99fab49a929f3610c"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1924a6a943df7c73b9ef0048302327c75962b567451479710da729ead241228"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:fe71ca30aabe42591e84ecb9694c0297dc699cc20c5b24d2cb267fb0fc01f947"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1819fd055c1ae672d1d725ec75eefd1f700c18acba0ed9332202be31d69c401d"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:5dbe1f7481dd14b6d477b4bace96d275090bc7636b9883975a08b802c94e7b78"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:771c051c5ece27ad03e4f2e33624a925f0ad636c01757ab7dbb04a37964af4ba"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98209c438b38b6f1276dbc27b1c04e346a75bfaafe72a25a548f2dc5ce71d226"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2152308e5321cb90fb45aaa84d03d6dedb19735a8779aaf36c624f97b831842d"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ed1f27dc2b2262c8b8d9036276619c1bb18791311c16ccbf1f31b660f2aad7cf"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2744947b6c5e907af21133431809ccca535a037356864e32c122efed8cb9de1f"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-win32.whl", hash = "sha256:13b20e269d14ad629ff9a2c9a2450f3dbb119d5948de63b27ffe624fa7aea85a"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:999823758e9eacd0095863d06cd6d388be769f80c9abb65cdb11c4f2cfce3fea"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:941f8a5c31986053e75fa466bcfa743c2bf1b513b7978cf1f4ab4e96a8219d27"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b9c02c88c77ef6057c6cbeea8922d7c2424aabf46bfc40ddf42a32765ba91061"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6abd4eb3ccb444383a40156139acc3aaa73745d395139cb6bc8e2a3429e1e627"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:449503213d142f8470b331a1c2f346f8457f16c7fe20f531bc2500e271f7c14c"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a11bcf609d00cfc9baed77ab308223cabc1f0b22a05774a26dd4c94c0c80f1f"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5d7bdea33354b55acf40bb4dd3ba7324d6f1ef6b4a1a4da0807591f8c7e87b9a"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d03b645852d605f43003020e78fe6d573cae6ee6b944193e36b8b317e7549a20"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-win32.whl", hash = "sha256:52b185dfc3bf32e70929310367dbc66185afba60492a6a75a9b1141d407e160c"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:63a273b70896d3640b7a883eb4a080c3c263d91662d870a2e9c84b7bbd978e7b"}, -] - -[package.dependencies] -grpcio = ">=1.62.1" -protobuf = ">=4.21.6,<5.0dev" -setuptools = "*" - [[package]] name = "gunicorn" version = "21.2.0" @@ -1463,13 +969,13 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -1477,13 +983,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "2.10" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" files = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1584,25 +1090,6 @@ files = [ {file = "ijson-3.2.3.tar.gz", hash = "sha256:10294e9bf89cb713da05bc4790bdff616610432db561964827074898e174f917"}, ] -[[package]] -name = "importlib-metadata" -version = "7.1.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, - {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1628,34 +1115,6 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - [[package]] name = "joblib" version = "1.4.0" @@ -1667,31 +1126,6 @@ files = [ {file = "joblib-1.4.0.tar.gz", hash = "sha256:1eb0dc091919cd384490de890cb5dfd538410a6d4b3b54eef09fb8c50b409b1c"}, ] -[[package]] -name = "jsonpatch" -version = "1.32" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"}, - {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] - [[package]] name = "jsonref" version = "1.1.0" @@ -1705,13 +1139,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.21.1" +version = "4.22.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, - {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, + {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, + {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, ] [package.dependencies] @@ -1767,75 +1201,6 @@ files = [ [package.dependencies] markdown = "*" -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - [[package]] name = "mccabe" version = "0.7.0" @@ -1847,36 +1212,24 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] -[[package]] -name = "memory-profiler" -version = "0.61.0" -description = "A module for monitoring memory usage of a python program" -optional = false -python-versions = ">=3.5" -files = [ - {file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84"}, - {file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0"}, -] - -[package.dependencies] -psutil = "*" - [[package]] name = "ministryofjustice-data-platform-catalogue" -version = "0.24.2" +version = "1.0.0" description = "Library to integrate the MoJ data platform with the catalogue component." optional = false -python-versions = "<4.0,>=3.10" -files = [ - {file = "ministryofjustice_data_platform_catalogue-0.24.2-py3-none-any.whl", hash = "sha256:e8f79b414cf40bf55a5cb9b92dc9c9f8e3a15bce6de312dcb9c6465adc5072ce"}, - {file = "ministryofjustice_data_platform_catalogue-0.24.2.tar.gz", hash = "sha256:d85458a1dd077fac86c0094045f6470d9954870822480f53c2f4f7f8df1dc52c"}, -] +python-versions = "^3.10" +files = [] +develop = true [package.dependencies] -acryl-datahub = {version = ">=0.12.1.3,<0.13.0.0", extras = ["datahub-rest"]} -deepdiff = ">=6.7.1,<7.0.0" -freezegun = ">=1.4.0,<2.0.0" -openmetadata-ingestion = ">=1.2.0.1,<1.3.0.0" +acryl-datahub = {version = "^0.12.1.3", extras = ["datahub-rest"]} +deepdiff = "^6.7.1" +freezegun = "^1.4.0" +pydantic = "^2.7.1" + +[package.source] +type = "directory" +url = "lib/datahub-client" [[package]] name = "mixpanel" @@ -2004,24 +1357,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "networkx" -version = "3.3" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.10" -files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, -] - -[package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - [[package]] name = "nltk" version = "3.8.1" @@ -2061,117 +1396,6 @@ files = [ [package.dependencies] setuptools = "*" -[[package]] -name = "openmetadata-ingestion" -version = "1.2.5.3" -description = "Ingestion Framework for OpenMetadata" -optional = false -python-versions = ">=3.8" -files = [ - {file = "openmetadata-ingestion-1.2.5.3.tar.gz", hash = "sha256:92fa2105ac79cf8aebd9aba7acb8f08d67a0aaac4f97725093dcf19c521b430a"}, - {file = "openmetadata_ingestion-1.2.5.3-py3-none-any.whl", hash = "sha256:76d2b446d9757c759f0e80c9df70b3d6cae2ab5fdb1e19c4e1355a048b0e0e71"}, -] - -[package.dependencies] -antlr4-python3-runtime = "4.9.2" -avro = ">=1.11,<2.0" -boto3 = ">=1.20,<2.0" -cached-property = "1.5.2" -chardet = "4.0.0" -collate-sqllineage = ">=1.0.4,<1.3" -croniter = ">=1.3.0,<1.4.0" -cryptography = "*" -email-validator = ">=1.0.3" -google = ">=3.0.0" -google-auth = ">=1.33.0" -grpcio-tools = ">=1.47.2" -idna = ">=2.5,<3" -importlib-metadata = ">=4.13.0" -Jinja2 = ">=2.11.3" -jsonpatch = "1.32" -jsonschema = "*" -memory-profiler = "*" -mypy-extensions = ">=0.4.3" -pydantic = ">=1.10,<2.0" -pymysql = ">=1.0.2" -python-dateutil = ">=2.8.1" -python-jose = ">=3.3,<4.0" -PyYAML = ">=6.0,<7.0" -requests = ">=2.23" -requests-aws4auth = ">=1.1,<2.0" -setuptools = ">=66.0.0,<66.1.0" -sqlalchemy = ">=1.4.0,<2" -tabulate = "0.9.0" -typing-compat = ">=0.1.0,<0.2.0" -typing-inspect = "*" -wheel = ">=0.38.4,<0.39.0" - -[package.extras] -airflow = ["apache-airflow (==2.6.3)", "attrs"] -all = ["GeoAlchemy2 (>=0.12,<1.0)", "Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "adlfs (>=2022.2.0)", "alembic (>=1.10.2,<1.11.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "azure-identity", "azure-identity (>=1.12,<2.0)", "azure-storage-blob", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "cachetools", "chardet (==4.0.0)", "clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)", "collate-sqllineage (>=1.0.4,<1.3)", "confluent-kafka (==2.1.1)", "couchbase (>=4.1,<5.0)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "cx-Oracle (>=8.3.0,<9)", "dagster-graphql (>=1.1,<2.0)", "databricks-sdk (==0.16.0)", "dbt-artifacts-parser", "delta-spark (<=2.3.0)", "elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)", "email-validator (>=1.0.3)", "fastavro (>=1.2.0)", "gcsfs (==2022.11.0)", "gitpython (>=3.1.34,<3.2.0)", "giturlparse", "google (>=3.0.0)", "google-auth (>=1.33.0)", "google-cloud", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "google-cloud-storage (==1.43.0)", "grpcio-tools (>=1.47.2)", "hdbcli", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "impyla (>=0.18.0,<0.19.0)", "impyla[kerberos] (>=0.18.0,<0.19.0)", "jsonpatch (==1.32)", "jsonschema", "ldap3 (==2.9.1)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "memory-profiler", "mlflow-skinny (>=2.3.0)", "msal (>=1.2,<2.0)", "mypy-extensions (>=0.4.3)", "neo4j (>=5.3.0,<5.4.0)", "okta (>=2.3,<3.0)", "oracledb (>=1.2,<2.0)", "packaging (==21.3)", "pandas (==1.3.5)", "pinotdb (>=0.3,<1.0)", "presidio-analyzer (==2.2.32)", "presto-types-parser (>=0.0.2)", "protobuf", "psycopg2-binary", "pyarrow (>=10.0,<11.0)", "pyathena (==3.0.8)", "pydantic (>=1.10,<2.0)", "pydomo (>=0.3,<1.0)", "pydruid (>=0.6.5)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pymssql (>=2.2.0,<2.3.0)", "pymysql (>=1.0.2)", "pyodbc (>=4.0.35,<5)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "python-on-whales (==0.55.0)", "python-snappy (>=0.6.1,<0.7.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "s3fs (==0.4.2)", "sasl (>=0.3,<1.0)", "scikit-learn (>=1.0,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "simple-salesforce (==1.11.4)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy (>=1.4.0,<2)", "sqlalchemy-bigquery (>=1.2.2)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-hana", "sqlalchemy-pgspider", "sqlalchemy-pytds (>=0.3,<1.0)", "sqlalchemy-redshift (==0.8.12)", "sqlalchemy-vertica[vertica-python] (>=0.0.5)", "tableau-api-lib (>=0.1,<1.0)", "tabulate (==0.9.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)", "trino[sqlalchemy]", "typing-compat (>=0.1.0,<0.2.0)", "typing-inspect", "websocket-client (>=1.6.1,<1.7.0)", "wheel (>=0.38.4,<0.39.0)"] -amundsen = ["neo4j (>=5.3.0,<5.4.0)"] -athena = ["pyathena (==3.0.8)"] -azure-sso = ["msal (>=1.2,<2.0)"] -azuresql = ["pyodbc (>=4.0.35,<5)"] -backup = ["azure-identity", "azure-storage-blob", "boto3 (>=1.20,<2.0)"] -base = ["Jinja2 (>=2.11.3)", "PyYAML (>=6.0,<7.0)", "antlr4-python3-runtime (==4.9.2)", "avro (>=1.11,<2.0)", "boto3 (>=1.20,<2.0)", "cached-property (==1.5.2)", "chardet (==4.0.0)", "collate-sqllineage (>=1.0.4,<1.3)", "croniter (>=1.3.0,<1.4.0)", "cryptography", "email-validator (>=1.0.3)", "google (>=3.0.0)", "google-auth (>=1.33.0)", "grpcio-tools (>=1.47.2)", "idna (>=2.5,<3)", "importlib-metadata (>=4.13.0)", "jsonpatch (==1.32)", "jsonschema", "memory-profiler", "mypy-extensions (>=0.4.3)", "pydantic (>=1.10,<2.0)", "pymysql (>=1.0.2)", "python-dateutil (>=2.8.1)", "python-jose (>=3.3,<4.0)", "requests (>=2.23)", "requests-aws4auth (>=1.1,<2.0)", "setuptools (>=66.0.0,<66.1.0)", "sqlalchemy (>=1.4.0,<2)", "tabulate (==0.9.0)", "typing-compat (>=0.1.0,<0.2.0)", "typing-inspect", "wheel (>=0.38.4,<0.39.0)"] -bigquery = ["cachetools", "google-cloud-datacatalog (>=3.6.2)", "google-cloud-logging", "pyarrow (>=10.0,<11.0)", "sqlalchemy-bigquery (>=1.2.2)"] -clickhouse = ["clickhouse-driver (>=0.2,<1.0)", "clickhouse-sqlalchemy (>=0.2,<1.0)"] -couchbase = ["couchbase (>=4.1,<5.0)"] -dagster = ["GeoAlchemy2 (>=0.12,<1.0)", "dagster-graphql (>=1.1,<2.0)", "psycopg2-binary", "pymysql (>=1.0.2)"] -data-insight = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] -databricks = ["databricks-sdk (==0.16.0)", "sqlalchemy-databricks (>=0.1,<1.0)"] -datalake-azure = ["adlfs (>=2022.2.0)", "azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] -datalake-gcs = ["boto3 (>=1.20,<2.0)", "gcsfs (==2022.11.0)", "google-cloud-storage (==1.43.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)"] -datalake-s3 = ["boto3 (>=1.20,<2.0)", "pandas (==1.3.5)", "pyarrow (>=10.0,<11.0)", "python-snappy (>=0.6.1,<0.7.0)", "s3fs (==0.4.2)"] -db2 = ["ibm-db-sa (>=0.3,<1.0)"] -dbt = ["azure-identity (>=1.12,<2.0)", "azure-storage-blob (>=12.14,<13.0)", "boto3 (>=1.20,<2.0)", "dbt-artifacts-parser", "google-cloud", "google-cloud-storage (==1.43.0)"] -deltalake = ["delta-spark (<=2.3.0)"] -dev = ["black (==22.3.0)", "datamodel-code-generator (==0.22.0)", "docker", "isort", "pre-commit", "pycln", "pylint (>=3.0.0,<3.1.0)", "twine"] -docker = ["python-on-whales (==0.55.0)"] -domo = ["pydomo (>=0.3,<1.0)"] -druid = ["pydruid (>=0.6.5)"] -dynamodb = ["boto3 (>=1.20,<2.0)"] -e2e-test = ["pytest-base-url", "pytest-playwright"] -elasticsearch = ["elasticsearch (==7.13.1)", "elasticsearch8 (>=8.9.0,<8.10.0)"] -glue = ["boto3 (>=1.20,<2.0)"] -great-expectations = ["great-expectations (>=0.17.0,<0.18.0)"] -hive = ["impyla (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] -impala = ["impyla[kerberos] (>=0.18.0,<0.19.0)", "presto-types-parser (>=0.0.2)", "sasl (>=0.3,<1.0)", "thrift (>=0.13,<1)", "thrift-sasl (>=0.4,<1.0)"] -kafka = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] -kinesis = ["boto3 (>=1.20,<2.0)"] -ldap-users = ["ldap3 (==2.9.1)"] -looker = ["gitpython (>=3.1.34,<3.2.0)", "giturlparse", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)"] -mlflow = ["alembic (>=1.10.2,<1.11.0)", "mlflow-skinny (>=2.3.0)"] -mongo = ["pandas (==1.3.5)", "pymongo (>=4.3,<5.0)"] -mssql = ["sqlalchemy-pytds (>=0.3,<1.0)"] -mssql-odbc = ["pyodbc (>=4.0.35,<5)"] -mysql = ["pymysql (>=1.0.2)"] -okta = ["okta (>=2.3,<3.0)"] -oracle = ["cx-Oracle (>=8.3.0,<9)", "oracledb (>=1.2,<2.0)"] -pgspider = ["psycopg2-binary", "sqlalchemy-pgspider"] -pii-processor = ["pandas (==1.3.5)", "presidio-analyzer (==2.2.32)", "spacy (==3.5.0)"] -pinotdb = ["pinotdb (>=0.3,<1.0)"] -postgres = ["GeoAlchemy2 (>=0.12,<1.0)", "packaging (==21.3)", "psycopg2-binary", "pymysql (>=1.0.2)"] -powerbi = ["msal (>=1.2,<2.0)"] -presto = ["presto-types-parser (>=0.0.2)", "pyhive (>=0.7,<1.0)"] -pymssql = ["pymssql (>=2.2.0,<2.3.0)"] -qliksense = ["websocket-client (>=1.6.1,<1.7.0)"] -quicksight = ["boto3 (>=1.20,<2.0)"] -redash = ["packaging (==21.3)"] -redpanda = ["avro (>=1.11,<2.0)", "confluent-kafka (==2.1.1)", "fastavro (>=1.2.0)", "grpcio-tools (>=1.47.2)", "protobuf"] -redshift = ["GeoAlchemy2 (>=0.12,<1.0)", "psycopg2-binary", "sqlalchemy-redshift (==0.8.12)"] -sagemaker = ["boto3 (>=1.20,<2.0)"] -salesforce = ["simple-salesforce (==1.11.4)"] -sap-hana = ["hdbcli", "sqlalchemy-hana"] -singlestore = ["pymysql (>=1.0.2)"] -sklearn = ["scikit-learn (>=1.0,<2.0)"] -snowflake = ["snowflake-sqlalchemy (>=1.4,<2.0)"] -tableau = ["tableau-api-lib (>=0.1,<1.0)"] -test = ["apache-airflow (==2.6.3)", "coverage", "databricks-sdk (==0.16.0)", "dbt-artifacts-parser", "elasticsearch8 (>=8.9.0,<8.10.0)", "giturlparse", "google (>=3.0.0)", "great-expectations (>=0.17.0,<0.18.0)", "lkml (>=1.3,<2.0)", "looker-sdk (>=22.20.0)", "moto (==4.0.8)", "pyarrow (>=10.0,<11.0)", "pydomo (>=0.3,<1.0)", "pyhive (>=0.7,<1.0)", "pymongo (>=4.3,<5.0)", "pytest (==7.0.0)", "pytest-cov", "pytest-order", "scikit-learn (>=1.0,<2.0)", "snowflake-sqlalchemy (>=1.4,<2.0)", "spacy (==3.5.0)", "sqlalchemy-databricks (>=0.1,<1.0)", "sqlalchemy-redshift (==0.8.12)", "tableau-api-lib (>=0.1,<1.0)", "trino[sqlalchemy]"] -trino = ["trino[sqlalchemy]"] -vertica = ["sqlalchemy-vertica[vertica-python] (>=0.0.5)"] - [[package]] name = "ordered-set" version = "4.1.0" @@ -2224,28 +1448,29 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -2288,26 +1513,6 @@ python-utils = ">=3.8.1" docs = ["sphinx (>=1.8.5)", "sphinx-autodoc-typehints (>=1.6.0)"] tests = ["dill (>=0.3.6)", "flake8 (>=3.7.7)", "freezegun (>=0.3.11)", "pytest (>=4.6.9)", "pytest-cov (>=2.6.1)", "pytest-mypy", "pywin32", "sphinx (>=1.8.5)"] -[[package]] -name = "protobuf" -version = "4.25.3" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, - {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, - {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, - {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, - {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, - {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, - {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, - {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, - {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, -] - [[package]] name = "psutil" version = "5.9.8" @@ -2336,31 +1541,6 @@ files = [ [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] -[[package]] -name = "pyasn1" -version = "0.6.0" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, - {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.4.0" -description = "A collection of ASN.1-based protocols modules" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, - {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.7.0" - [[package]] name = "pycodestyle" version = "2.11.1" @@ -2385,55 +1565,113 @@ files = [ [[package]] name = "pydantic" -version = "1.10.15" -description = "Data validation and settings management using python type hints" +version = "2.7.1" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.18.2" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyflakes" @@ -2446,36 +1684,6 @@ files = [ {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pymysql" -version = "1.1.0" -description = "Pure Python MySQL Driver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, - {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, -] - -[package.extras] -ed25519 = ["PyNaCl (>=1.4.0)"] -rsa = ["cryptography"] - [[package]] name = "pyproject-flake8" version = "6.1.0" @@ -2515,23 +1723,23 @@ files = [ [[package]] name = "pytest" -version = "8.1.1" +version = "8.2.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, - {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.4,<2.0" +pluggy = ">=1.5,<2.0" [package.extras] -testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" @@ -2597,27 +1805,6 @@ files = [ [package.extras] cli = ["click (>=5.0)"] -[[package]] -name = "python-jose" -version = "3.3.0" -description = "JOSE implementation in Python" -optional = false -python-versions = "*" -files = [ - {file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"}, - {file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"}, -] - -[package.dependencies] -ecdsa = "!=0.15" -pyasn1 = "*" -rsa = "*" - -[package.extras] -cryptography = ["cryptography (>=3.4.0)"] -pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"] -pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"] - [[package]] name = "python-utils" version = "3.8.2" @@ -2685,7 +1872,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2722,13 +1908,13 @@ files = [ [[package]] name = "referencing" -version = "0.34.0" +version = "0.35.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"}, - {file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"}, + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, ] [package.dependencies] @@ -2737,104 +1923,90 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2023.12.25" +version = "2024.4.28" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, - {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, - {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, - {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, - {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, - {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, - {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, - {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, - {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, - {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, - {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, - {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, - {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, - {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, - {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, - {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, - {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, - {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, - {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, - {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, - {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, - {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, - {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, - {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, - {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, - {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, - {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, - {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, - {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, - {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, - {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, - {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, + {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, + {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, + {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, + {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, + {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, + {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, + {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, + {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, + {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, + {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, + {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, + {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, + {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, + {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, + {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, + {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, + {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, + {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, + {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, + {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, + {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, + {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, + {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, + {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, + {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, + {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, ] [[package]] @@ -2858,24 +2030,6 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "requests-aws4auth" -version = "1.2.3" -description = "AWS4 authentication for Requests" -optional = false -python-versions = ">=3.3" -files = [ - {file = "requests-aws4auth-1.2.3.tar.gz", hash = "sha256:d4c73c19f37f80d4aa9c5bd4fa376cfd0c69299c48b00a8eb2ae6b0416164fb8"}, - {file = "requests_aws4auth-1.2.3-py2.py3-none-any.whl", hash = "sha256:8070a5207e95fa5fe88e87d9a75f34e768cbab35bb3557ef20cbbf9426dee4d5"}, -] - -[package.dependencies] -requests = "*" -six = "*" - -[package.extras] -httpx = ["httpx"] - [[package]] name = "requests-file" version = "2.0.0" @@ -2998,20 +2152,6 @@ files = [ {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, ] -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = false -python-versions = ">=3.6,<4" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - [[package]] name = "ruamel-yaml" version = "0.18.6" @@ -3089,32 +2229,15 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] -[[package]] -name = "s3transfer" -version = "0.10.1" -description = "An Amazon S3 Transfer Manager" -optional = false -python-versions = ">= 3.8" -files = [ - {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, - {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, -] - -[package.dependencies] -botocore = ">=1.33.2,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] - [[package]] name = "selenium" -version = "4.19.0" +version = "4.17.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.19.0-py3-none-any.whl", hash = "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9"}, - {file = "selenium-4.19.0.tar.gz", hash = "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853"}, + {file = "selenium-4.17.2-py3-none-any.whl", hash = "sha256:5aee79026c07985dc1b0c909f34084aa996dfe5b307602de9016d7a621a473f2"}, + {file = "selenium-4.17.2.tar.gz", hash = "sha256:d43d6972e516855fb242ef9ce4ce759057b115070e702e7b1c1032fe7b38d87b"}, ] [package.dependencies] @@ -3126,18 +2249,18 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]} [[package]] name = "sentry-sdk" -version = "1.45.0" +version = "2.0.1" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"}, - {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"}, + {file = "sentry_sdk-2.0.1-py2.py3-none-any.whl", hash = "sha256:b54c54a2160f509cf2757260d0cf3885b608c6192c2555a3857e3a4d0f84bdb3"}, + {file = "sentry_sdk-2.0.1.tar.gz", hash = "sha256:c278e0f523f6f0ee69dc43ad26dcdb1202dffe5ac326ae31472e012d941bee21"}, ] [package.dependencies] certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] @@ -3173,19 +2296,19 @@ tornado = ["tornado (>=5)"] [[package]] name = "setuptools" -version = "66.0.0" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-66.0.0-py3-none-any.whl", hash = "sha256:a78d01d1e2c175c474884671dde039962c9d74c7223db7369771fcf6e29ceeab"}, - {file = "setuptools-66.0.0.tar.gz", hash = "sha256:bd6eb2d6722568de6d14b87c44a96fac54b2a45ff5e940e639979a3d1792adb6"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -3220,137 +2343,20 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] -[[package]] -name = "soupsieve" -version = "2.5" -description = "A modern CSS selector implementation for Beautiful Soup." -optional = false -python-versions = ">=3.8" -files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, -] - -[[package]] -name = "sqlalchemy" -version = "1.4.52" -description = "Database Abstraction Library" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "SQLAlchemy-1.4.52-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f68016f9a5713684c1507cc37133c28035f29925c75c0df2f9d0f7571e23720a"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24bb0f81fbbb13d737b7f76d1821ec0b117ce8cbb8ee5e8641ad2de41aa916d3"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e93983cc0d2edae253b3f2141b0a3fb07e41c76cd79c2ad743fc27eb79c3f6db"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84e10772cfc333eb08d0b7ef808cd76e4a9a30a725fb62a0495877a57ee41d81"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:427988398d2902de042093d17f2b9619a5ebc605bf6372f7d70e29bde6736842"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-win32.whl", hash = "sha256:1296f2cdd6db09b98ceb3c93025f0da4835303b8ac46c15c2136e27ee4d18d94"}, - {file = "SQLAlchemy-1.4.52-cp310-cp310-win_amd64.whl", hash = "sha256:80e7f697bccc56ac6eac9e2df5c98b47de57e7006d2e46e1a3c17c546254f6ef"}, - {file = "SQLAlchemy-1.4.52-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2f251af4c75a675ea42766880ff430ac33291c8d0057acca79710f9e5a77383d"}, - {file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8f9e4c4718f111d7b530c4e6fb4d28f9f110eb82e7961412955b3875b66de0"}, - {file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afb1672b57f58c0318ad2cff80b384e816735ffc7e848d8aa51e0b0fc2f4b7bb"}, - {file = "SQLAlchemy-1.4.52-cp311-cp311-win32.whl", hash = "sha256:6e41cb5cda641f3754568d2ed8962f772a7f2b59403b95c60c89f3e0bd25f15e"}, - {file = "SQLAlchemy-1.4.52-cp311-cp311-win_amd64.whl", hash = "sha256:5bed4f8c3b69779de9d99eb03fd9ab67a850d74ab0243d1be9d4080e77b6af12"}, - {file = "SQLAlchemy-1.4.52-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:49e3772eb3380ac88d35495843daf3c03f094b713e66c7d017e322144a5c6b7c"}, - {file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:618827c1a1c243d2540314c6e100aee7af09a709bd005bae971686fab6723554"}, - {file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de9acf369aaadb71a725b7e83a5ef40ca3de1cf4cdc93fa847df6b12d3cd924b"}, - {file = "SQLAlchemy-1.4.52-cp312-cp312-win32.whl", hash = "sha256:763bd97c4ebc74136ecf3526b34808c58945023a59927b416acebcd68d1fc126"}, - {file = "SQLAlchemy-1.4.52-cp312-cp312-win_amd64.whl", hash = "sha256:f12aaf94f4d9679ca475975578739e12cc5b461172e04d66f7a3c39dd14ffc64"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:853fcfd1f54224ea7aabcf34b227d2b64a08cbac116ecf376907968b29b8e763"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f98dbb8fcc6d1c03ae8ec735d3c62110949a3b8bc6e215053aa27096857afb45"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e135fff2e84103bc15c07edd8569612ce317d64bdb391f49ce57124a73f45c5"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5de6af8852500d01398f5047d62ca3431d1e29a331d0b56c3e14cb03f8094c"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3491c85df263a5c2157c594f54a1a9c72265b75d3777e61ee13c556d9e43ffc9"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-win32.whl", hash = "sha256:427c282dd0deba1f07bcbf499cbcc9fe9a626743f5d4989bfdfd3ed3513003dd"}, - {file = "SQLAlchemy-1.4.52-cp36-cp36m-win_amd64.whl", hash = "sha256:ca5ce82b11731492204cff8845c5e8ca1a4bd1ade85e3b8fcf86e7601bfc6a39"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:29d4247313abb2015f8979137fe65f4eaceead5247d39603cc4b4a610936cd2b"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a752bff4796bf22803d052d4841ebc3c55c26fb65551f2c96e90ac7c62be763a"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7ea11727feb2861deaa293c7971a4df57ef1c90e42cb53f0da40c3468388000"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d913f8953e098ca931ad7f58797f91deed26b435ec3756478b75c608aa80d139"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a251146b921725547ea1735b060a11e1be705017b568c9f8067ca61e6ef85f20"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-win32.whl", hash = "sha256:1f8e1c6a6b7f8e9407ad9afc0ea41c1f65225ce505b79bc0342159de9c890782"}, - {file = "SQLAlchemy-1.4.52-cp37-cp37m-win_amd64.whl", hash = "sha256:346ed50cb2c30f5d7a03d888e25744154ceac6f0e6e1ab3bc7b5b77138d37710"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:4dae6001457d4497736e3bc422165f107ecdd70b0d651fab7f731276e8b9e12d"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d2e08d79f5bf250afb4a61426b41026e448da446b55e4770c2afdc1e200fce"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbce5dd7c7735e01d24f5a60177f3e589078f83c8a29e124a6521b76d825b85"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bdb7b4d889631a3b2a81a3347c4c3f031812eb4adeaa3ee4e6b0d028ad1852b5"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c294ae4e6bbd060dd79e2bd5bba8b6274d08ffd65b58d106394cb6abbf35cf45"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-win32.whl", hash = "sha256:bcdfb4b47fe04967669874fb1ce782a006756fdbebe7263f6a000e1db969120e"}, - {file = "SQLAlchemy-1.4.52-cp38-cp38-win_amd64.whl", hash = "sha256:7d0dbc56cb6af5088f3658982d3d8c1d6a82691f31f7b0da682c7b98fa914e91"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a551d5f3dc63f096ed41775ceec72fdf91462bb95abdc179010dc95a93957800"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab773f9ad848118df7a9bbabca53e3f1002387cdbb6ee81693db808b82aaab0"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2de46f5d5396d5331127cfa71f837cca945f9a2b04f7cb5a01949cf676db7d1"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7027be7930a90d18a386b25ee8af30514c61f3852c7268899f23fdfbd3107181"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99224d621affbb3c1a4f72b631f8393045f4ce647dd3262f12fe3576918f8bf3"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-win32.whl", hash = "sha256:c124912fd4e1bb9d1e7dc193ed482a9f812769cb1e69363ab68e01801e859821"}, - {file = "SQLAlchemy-1.4.52-cp39-cp39-win_amd64.whl", hash = "sha256:2c286fab42e49db23c46ab02479f328b8bdb837d3e281cae546cc4085c83b680"}, - {file = "SQLAlchemy-1.4.52.tar.gz", hash = "sha256:80e63bbdc5217dad3485059bdf6f65a7d43f33c8bde619df5c220edf03d87296"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "sqlfluff" -version = "2.3.5" -description = "The SQL Linter for Humans" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sqlfluff-2.3.5-py3-none-any.whl", hash = "sha256:2ca095a2f2cced5573a83fc8a6d2aceb36edd21a8c4c5d8099b6544e386d86a1"}, - {file = "sqlfluff-2.3.5.tar.gz", hash = "sha256:fd77ef5a41ebfa798235a5b28bcfa71f7521e9336159a08b40b545953e84c3c3"}, -] - -[package.dependencies] -appdirs = "*" -chardet = "*" -click = "*" -colorama = ">=0.3" -diff-cover = ">=2.5.0" -Jinja2 = "*" -pathspec = "*" -pytest = "*" -pyyaml = ">=5.1" -regex = "*" -tblib = "*" -tqdm = "*" -typing-extensions = "*" - [[package]] name = "sqlparse" -version = "0.4.4" +version = "0.5.0" description = "A non-validating SQL parser." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, - {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, + {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"}, + {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"}, ] [package.extras] -dev = ["build", "flake8"] +dev = ["build", "hatch"] doc = ["sphinx"] -test = ["pytest", "pytest-cov"] [[package]] name = "tabulate" @@ -3366,17 +2372,6 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tblib" -version = "3.0.0" -description = "Traceback serialization library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"}, - {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"}, -] - [[package]] name = "termcolor" version = "2.4.0" @@ -3456,17 +2451,6 @@ files = [ trio = ">=0.11" wsproto = ">=0.14" -[[package]] -name = "typing-compat" -version = "0.1.0" -description = "Python typing compatibility library" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "typing-compat-0.1.0.tar.gz", hash = "sha256:a7159db80406de342bc128ab315fae3afe1ddc87cbc2df7a023763090fdfe5d2"}, - {file = "typing_compat-0.1.0-py2.py3-none-any.whl", hash = "sha256:a8b94eb34c95982fbd34b8daa46fd78c43db33c075b98c79b6e1d77e8f7dd4d5"}, -] - [[package]] name = "typing-extensions" version = "4.11.0" @@ -3526,13 +2510,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [package.dependencies] @@ -3541,23 +2525,9 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] -[[package]] -name = "wheel" -version = "0.38.4" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, -] - -[package.extras] -test = ["pytest (>=3.0.0)"] - [[package]] name = "whitenoise" version = "6.6.0" @@ -3768,22 +2738,7 @@ files = [ idna = ">=2.0" multidict = ">=4.0" -[[package]] -name = "zipp" -version = "3.18.1" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "002ce1725f0a0579216ac5c088ba971ea3a24106e2cedc78bd735c78908be49c" +content-hash = "9822ded7cc6b8c9077d5f01d0476c263e8a3832d6d4bb29a02108ce7c58487ea" diff --git a/pyproject.toml b/pyproject.toml index 7ffe9a72..254fdc14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,16 +11,16 @@ django = "^5.0.1" pyyaml = "^6.0.1" gunicorn = "^21.2.0" whitenoise = "^6.6.0" -ministryofjustice-data-platform-catalogue = "^0.24.2" markdown = "^3.5.2" python-dotenv = "^1.0.1" markdown-headdown = "^0.1.3" nltk = "^3.8.1" +ministryofjustice-data-platform-catalogue = { path = "lib/datahub-client", develop = true } [tool.poetry.group.dev.dependencies] black = "^24.4.0" pre-commit = "^3.6.0" -selenium = "^4.17.2" +selenium = "~=4.17.0" flake8 = ">=6.1.0" pytest-django = "^4.8.0" pytest-cov = "^4.1.0" From 335ad0d6ad0676989fc9edf0d1896ffd504b0833 Mon Sep 17 00:00:00 2001 From: Murdo Moyse Date: Tue, 30 Apr 2024 16:51:55 +0100 Subject: [PATCH 54/64] Starting to fix from refactor --- .vscode/launch.json | 1 + .vscode/settings.json | 5 +++ home/service/base.py | 5 +-- home/service/details.py | 56 +--------------------------- home/service/search.py | 3 +- home/urls.py | 2 +- home/views.py | 35 +++++------------ templates/base/base.html | 2 +- templates/details_data_product.html | 6 +-- templates/details_database.html | 6 +-- templates/details_table.html | 10 ++--- templates/partial/search_result.html | 30 ++------------- tests/conftest.py | 14 +++---- tests/home/service/test_glossary.py | 6 +-- 14 files changed, 48 insertions(+), 133 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json index fbe6f6b8..9f959439 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,7 @@ "request": "launch", "program": "${workspaceFolder}/manage.py", "args": ["runserver"], + "justMyCode": false, "django": true } ] diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..d969f962 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.testing.pytestArgs": ["tests"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/home/service/base.py b/home/service/base.py index 69945e65..7ead3f9c 100644 --- a/home/service/base.py +++ b/home/service/base.py @@ -1,11 +1,10 @@ -from data_platform_catalogue.client import BaseCatalogueClient -from data_platform_catalogue.client.datahub import DataHubCatalogueClient +from data_platform_catalogue.client.datahub_client import DataHubCatalogueClient from django.conf import settings class GenericService: @staticmethod - def _get_catalogue_client() -> BaseCatalogueClient: + def _get_catalogue_client() -> DataHubCatalogueClient: return DataHubCatalogueClient( jwt_token=settings.CATALOGUE_TOKEN, api_url=settings.CATALOGUE_URL ) diff --git a/home/service/details.py b/home/service/details.py index 135a26c7..5428da7c 100644 --- a/home/service/details.py +++ b/home/service/details.py @@ -5,54 +5,6 @@ from .base import GenericService -class DataProductDetailsService(GenericService): - def __init__(self, urn: str): - self.urn = urn - self.client = self._get_catalogue_client() - - filter_value = [MultiSelectFilter("urn", [urn])] - search_results = self.client.search(query="", page=None, filters=filter_value) - - if not search_results.page_results: - raise ObjectDoesNotExist(urn) - - self.result = search_results.page_results[0] - self.assets_in_data_product = self._get_data_product_entities() - self.context = self._get_context() - - def _get_data_product_entities(self): - # we might want to implement pagination for data product children - # details at some point - data_product_search = self.client.list_data_product_assets( - urn=self.urn, count=500 - ).page_results - - assets_in_data_product = [] - for result in data_product_search: - assets_in_data_product.append( - { - "name": result.name, - "urn": result.id, - "description": result.description, - "type": "TABLE", - } - ) - - assets_in_data_product = sorted(assets_in_data_product, key=lambda d: d["name"]) - - return assets_in_data_product - - def _get_context(self): - context = { - "result": self.result, - "result_type": "Data product", - "tables": self.assets_in_data_product, - "h1_value": "Details", - } - - return context - - class DatabaseDetailsService(GenericService): def __init__(self, urn: str): self.urn = urn @@ -80,7 +32,7 @@ def _get_database_entities(self): entities_in_database.append( { "name": result.name, - "urn": result.id, + "urn": result.urn, "description": result.description, "type": "TABLE", } @@ -122,11 +74,7 @@ def __init__(self, urn: str): # v0.12, assigning to multiple data products is not possible and we don't # have datasets with multiple parent containers. self.parent_entity = parents[0] - self.dataset_parent_type = ( - ResultType.DATABASE.name.lower() - if "container" in self.parent_entity.id.split(":") - else ResultType.DATA_PRODUCT.name.lower() - ) + self.dataset_parent_type = ResultType.DATABASE.name.lower() else: self.parent_entity = None self.dataset_parent_type = None diff --git a/home/service/search.py b/home/service/search.py index 12f16fde..54e8f95a 100644 --- a/home/service/search.py +++ b/home/service/search.py @@ -5,6 +5,7 @@ from data_platform_catalogue.search_types import ( MultiSelectFilter, ResultType, + SearchResponse, SortOption, ) from django.core.paginator import Paginator @@ -62,7 +63,7 @@ def _build_entity_types(_, entity_types: list[str]) -> tuple[ResultType]: ) return chosen_entities if chosen_entities else default_entities - def _get_search_results(self, page: str, items_per_page: int): + def _get_search_results(self, page: str, items_per_page: int) -> SearchResponse: if self.form.is_bound: form_data = self.form.cleaned_data else: diff --git a/home/urls.py b/home/urls.py index 06b85782..64f80151 100644 --- a/home/urls.py +++ b/home/urls.py @@ -9,7 +9,7 @@ path("search", views.search_view, name="search"), path("glossary", views.glossary_view, name="glossary"), path( - "details//", + "details//", views.details_view, name="details", ), diff --git a/home/views.py b/home/views.py index 7735251b..ae285e31 100644 --- a/home/views.py +++ b/home/views.py @@ -6,7 +6,6 @@ from home.service.details import ( ChartDetailsService, DatabaseDetailsService, - DataProductDetailsService, DatasetDetailsService, ) from home.service.glossary import GlossaryService @@ -19,24 +18,21 @@ def home_view(request): return render(request, "home.html", context) -def details_view(request, result_type, id): - if result_type == "data_product": - context = data_product_details(id) - return render(request, "details_data_product.html", context) +def details_view(request, result_type, urn): if result_type == "table": - context = dataset_details(id) + context = dataset_details(urn) return render(request, "details_table.html", context) if result_type == "database": - context = database_details(id) + context = database_details(urn) return render(request, "details_database.html", context) if result_type == "chart": - context = chart_details(id) + context = chart_details(urn) return render(request, "details_chart.html", context) -def data_product_details(id): +def database_details(urn): try: - service = DataProductDetailsService(id) + service = DatabaseDetailsService(urn) except ObjectDoesNotExist: raise Http404("Asset does not exist") @@ -45,9 +41,9 @@ def data_product_details(id): return context -def database_details(id): +def dataset_details(urn): try: - service = DatabaseDetailsService(id) + service = DatasetDetailsService(urn) except ObjectDoesNotExist: raise Http404("Asset does not exist") @@ -56,20 +52,9 @@ def database_details(id): return context -def dataset_details(id): +def chart_details(urn): try: - service = DatasetDetailsService(id) - except ObjectDoesNotExist: - raise Http404("Asset does not exist") - - context = service.context - - return context - - -def chart_details(id): - try: - service = ChartDetailsService(id) + service = ChartDetailsService(urn) except ObjectDoesNotExist: raise Http404("Asset does not exist") diff --git a/templates/base/base.html b/templates/base/base.html index dedfd337..aba4f625 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -11,7 +11,7 @@ {% block skiplinks %} Skip to main content {% endblock %} - + {% include "base/navigation.html" %}

diff --git a/templates/details_data_product.html b/templates/details_data_product.html index ae0ef3c8..de2d92d8 100644 --- a/templates/details_data_product.html +++ b/templates/details_data_product.html @@ -35,8 +35,8 @@

  • Last updated date: - {% if result.last_updated %} - {{result.last_updated | date:"jS F Y"}} ({{result.last_updated|naturaltime}}) + {% if result.last_modified %} + {{result.last_modified | date:"jS F Y"}} ({{result.last_modified|naturaltime}}) {% endif %}
  • @@ -93,7 +93,7 @@

    {{ table.description|markdown:3 }} {% endif %} - Table details + Table details {% endwith %} {% endfor %} diff --git a/templates/details_database.html b/templates/details_database.html index 3b057df8..34ac1199 100644 --- a/templates/details_database.html +++ b/templates/details_database.html @@ -35,8 +35,8 @@

    • Last updated date: - {% if result.last_updated %} - {{result.last_updated | date:"jS F Y"}} ({{result.last_updated|naturaltime}}) + {% if result.last_modified %} + {{result.last_modified | date:"jS F Y"}} ({{result.last_modified|naturaltime}}) {% endif %}
    • @@ -93,7 +93,7 @@

      {{ table.description|markdown:3 }} {% endif %} - Table details + Table details {% endwith %} {% endfor %} diff --git a/templates/details_table.html b/templates/details_table.html index f83a3242..2b180602 100644 --- a/templates/details_table.html +++ b/templates/details_table.html @@ -11,7 +11,7 @@ {% if parent_entity %}
    • - {{parent_entity.name}} + {{parent_entity.name}}
    • {% endif %}
    • @@ -42,8 +42,8 @@

      • Last updated date: - {% if table.last_updated %} - {{table.last_updated | date:"jS F Y"}} ({{table.last_updated|naturaltime}}) + {% if table.last_modified %} + {{table.last_modified | date:"jS F Y"}} ({{table.last_modified|naturaltime}}) {% endif %}
      • @@ -59,7 +59,7 @@

      • Domain: - {{table.domain}} + {{table.domain.display_name}}
      • Tags: @@ -69,7 +69,7 @@

- {% include "partial/contact_info.html" with data_owner=table.owner data_owner_email=table.owner_email %} + {% include "partial/contact_info.html" with data_owner=table..governance.data_owner.display_name data_owner_email=table..governance.data_owner.email %}
diff --git a/templates/partial/search_result.html b/templates/partial/search_result.html index a2da703e..27a46ea3 100644 --- a/templates/partial/search_result.html +++ b/templates/partial/search_result.html @@ -6,13 +6,9 @@

{% with result_type=result.result_type.name|lower %} - {{result.fully_qualified_name}} + {{result.fully_qualified_name}} {% endwith %} - {% if result.result_type.name == "DATA_PRODUCT" %} - - Data product - - {% elif result.result_type.name == "TABLE" %} + {% if result.result_type.name == "TABLE" %} Table @@ -38,43 +34,23 @@

{{result.metadata.maintainer_display_name}}

-
-
First created:
-
- {% if result.metadata.creation_date %} - {{result.metadata.creation_date | date:"jS F Y"}} ({{result.metadata.creation_date|naturaltime}}) - {% endif %} -
-
Refresh period:
TBC
-
-
Retention period:
-
- {{result.metadata.retention_period_in_days}} -
-
Domain name
{{result.metadata.domain_name}}
-
-
Tags
-
- {{ result.tags |join:", " }} -
-
{% if result.matches %}
Matched fields
- {{ result.matches|lookup:readable_match_reasons|join:", " }} + {{ result.matches|join:", " }}
{% endif %} diff --git a/tests/conftest.py b/tests/conftest.py index dd231345..f8b9d444 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import pytest from data_platform_catalogue.client import BaseCatalogueClient -from data_platform_catalogue.entities import RelationshipType, TableMetadata +from data_platform_catalogue.entities import RelationshipType, Table from data_platform_catalogue.search_types import ( FacetOption, ResultType, @@ -66,22 +66,22 @@ def generate_table_metadata( relations=None, domain_name=None, tags=None, - last_updated=None, + last_modified=None, owner=None, owner_email=None, -) -> TableMetadata: +) -> Table: """ Generate a fake table metadata object """ - return TableMetadata( + return Table( + urn="urn:li:table:fake", name=name or fake.unique.name(), description=description or fake.paragraph(), column_details=columns_details or [], - retention_period_in_days=retention_period_in_days or 123, relationships=relations or {RelationshipType.PARENT: []}, domain=domain_name, tags=tags, - last_updated=last_updated, + last_modified=last_modified, owner=owner, owner_email=owner_email, ) @@ -155,7 +155,7 @@ def mock_list_database_tables_response(mock_catalogue, total_results, page_resul def mock_get_table_details_response(mock_catalogue): - mock_catalogue.get_table_details.return_value = TableMetadata( + mock_catalogue.get_table_details.return_value = Table( name="abc", description="abc", retention_period_in_days=0, diff --git a/tests/home/service/test_glossary.py b/tests/home/service/test_glossary.py index bd272b25..3df21e6b 100644 --- a/tests/home/service/test_glossary.py +++ b/tests/home/service/test_glossary.py @@ -29,7 +29,7 @@ def test_get_context(self): ] }, tags=[], - last_updated=None, + last_modified=None, ), SearchResult( id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", @@ -48,7 +48,7 @@ def test_get_context(self): ] }, tags=[], - last_updated=None, + last_modified=None, ), ], "description": "Data protection terms", @@ -64,7 +64,7 @@ def test_get_context(self): matches={}, metadata={"parentNodes": []}, tags=[], - last_updated=None, + last_modified=None, ) ], "description": "", From 851c5b4ef7d43c9c39772dceb8f407df066366ed Mon Sep 17 00:00:00 2001 From: Murdo Moyse Date: Wed, 1 May 2024 15:41:45 +0100 Subject: [PATCH 55/64] Fixing/deleting tests --- home/service/details.py | 2 +- tests/conftest.py | 164 +++++++++++++----------- tests/home/service/test_details.py | 63 ++++----- tests/home/service/test_glossary.py | 6 +- tests/selenium/test_search_scenarios.py | 21 +-- tests/test_views.py | 29 +---- 6 files changed, 120 insertions(+), 165 deletions(-) diff --git a/home/service/details.py b/home/service/details.py index 5428da7c..4525b792 100644 --- a/home/service/details.py +++ b/home/service/details.py @@ -21,7 +21,7 @@ def __init__(self, urn: str): self.context = self._get_context() def _get_database_entities(self): - # we might want to implement pagination for data product children + # we might want to implement pagination for database children # details at some point database_search = self.client.list_database_tables( urn=self.urn, count=500 diff --git a/tests/conftest.py b/tests/conftest.py index f8b9d444..86a03e8a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,11 @@ +from datetime import datetime, timezone from random import choice from typing import Any from unittest.mock import MagicMock, patch import pytest -from data_platform_catalogue.client import BaseCatalogueClient -from data_platform_catalogue.entities import RelationshipType, Table +from data_platform_catalogue.client.datahub_client import DataHubCatalogueClient +from data_platform_catalogue.entities import RelationshipType, Table, DomainRef, Governance, OwnerRef, TagRef, Column, ColumnRef, UsageRestrictions, EntityRef, CustomEntityProperties, DataSummary, AccessInformation from data_platform_catalogue.search_types import ( FacetOption, ResultType, @@ -20,7 +21,7 @@ from faker import Faker from home.forms.search import SearchForm -from home.service.details import DatabaseDetailsService, DataProductDetailsService +from home.service.details import DatabaseDetailsService from home.service.glossary import GlossaryService from home.service.search import SearchService @@ -37,7 +38,7 @@ def chromedriver_path(request): def generate_search_result( - result_type: ResultType | None = None, id=None, metadata=None + result_type: ResultType | None = None, urn=None, metadata=None ) -> SearchResult: """ Generate a random search result @@ -45,9 +46,9 @@ def generate_search_result( name = fake.name() return SearchResult( - id=id or fake.unique.name(), + urn=urn or fake.unique.name(), result_type=( - choice((ResultType.DATA_PRODUCT, ResultType.TABLE)) + choice((ResultType.DATABASE, ResultType.TABLE)) if result_type is None else result_type ), @@ -61,30 +62,55 @@ def generate_search_result( def generate_table_metadata( name=None, description=None, - columns_details=None, - retention_period_in_days=None, relations=None, - domain_name=None, - tags=None, - last_modified=None, - owner=None, - owner_email=None, ) -> Table: """ Generate a fake table metadata object """ return Table( - urn="urn:li:table:fake", - name=name or fake.unique.name(), - description=description or fake.paragraph(), - column_details=columns_details or [], - relationships=relations or {RelationshipType.PARENT: []}, - domain=domain_name, - tags=tags, - last_modified=last_modified, - owner=owner, - owner_email=owner_email, - ) + urn="urn:li:table:fake", + display_name="Foo.Dataset", + name=name or fake.unique.name(), + fully_qualified_name="Foo.Dataset", + description=description or fake.paragraph(), + relationships=relations or {RelationshipType.PARENT: []}, + domain=DomainRef(display_name="LAA", urn="LAA"), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, + column_details=[ + Column( + name="urn", + display_name="urn", + type="string", + description="The primary identifier for the dataset entity.", + nullable=False, + is_primary_key=True, + foreign_keys=[ + ColumnRef( + name="urn", + display_name="urn", + table=EntityRef( + urn="urn:li:dataset:(urn:li:dataPlatform:datahub,Dataset,PROD)", + display_name="Dataset", + ), + ) + ], + ), + ], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + custom_properties=CustomEntityProperties(), + ) def generate_page(page_size=20, result_type: ResultType | None = None): @@ -123,18 +149,13 @@ def client(): def mock_catalogue(): patcher = patch("home.service.base.GenericService._get_catalogue_client") mock_fn = patcher.start() - mock_catalogue = MagicMock(spec=BaseCatalogueClient) + mock_catalogue = MagicMock(spec=DataHubCatalogueClient) mock_fn.return_value = mock_catalogue mock_search_response( mock_catalogue, page_results=generate_page(), total_results=100 ) mock_search_facets_response(mock_catalogue, domains=generate_options()) mock_get_glossary_terms_response(mock_catalogue) - mock_list_data_product_response( - mock_catalogue, - page_results=generate_page(page_size=1, result_type=ResultType.TABLE), - total_results=1, - ) mock_list_database_tables_response( mock_catalogue, page_results=generate_page(page_size=1, result_type=ResultType.TABLE), @@ -156,17 +177,39 @@ def mock_list_database_tables_response(mock_catalogue, total_results, page_resul def mock_get_table_details_response(mock_catalogue): mock_catalogue.get_table_details.return_value = Table( - name="abc", - description="abc", - retention_period_in_days=0, - column_details=[ - { - "name": "foo", - "description": "description **with markdown**", - "type": "string", - } - ], - ) + urn="urn:li:table:fake", + display_name="abc", + name="abc", + fully_qualified_name="abc", + description="abc", + relationships={}, + domain=DomainRef(display_name="LAA", urn="LAA"), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + tags=[TagRef(display_name="some-tag", urn="urn:li:tag:Entity")], + last_modified=datetime(2024, 3, 5, 6, 16, 47, 814000, tzinfo=timezone.utc), + created=None, + column_details=[ + Column( + name="foo", + display_name="foo", + type="string", + description="description **with markdown**", + nullable=False, + is_primary_key=True, + foreign_keys=None + ), + ], + platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), + ) def mock_search_response(mock_catalogue, total_results=0, page_results=()): @@ -180,31 +223,12 @@ def mock_search_facets_response(mock_catalogue, domains): mock_catalogue.search_facets.return_value = SearchFacets({"domains": domains}) -def mock_list_data_product_response(mock_catalogue, total_results, page_results=()): - search_response = SearchResponse( - total_results=total_results, page_results=page_results - ) - mock_catalogue.list_data_product_assets.return_value = search_response - - -def mock_get_dataproduct_aspect(mock_catalogue): - data_product_association = DataProductAssociationClass( - destinationUrn="urn:li:dataset:(urn:li:dataPlatform:glue,test.test,PROD)", - sourceUrn="urn:li:dataProduct:test", - ) - - response = DataProductPropertiesClass( - name="test", assets=[data_product_association], description="test" - ) - mock_catalogue.graph.get_aspect.return_value = response - - def mock_get_glossary_terms_response(mock_catalogue): mock_catalogue.get_glossary_terms.return_value = SearchResponse( total_results=3, page_results=[ SearchResult( - id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", name="IAO", description="Information asset owner.\n", metadata={ @@ -220,7 +244,7 @@ def mock_get_glossary_terms_response(mock_catalogue): result_type="GLOSSARY_TERM", ), SearchResult( - id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", name="Other term", description="Term description to test groupings work", metadata={ @@ -236,7 +260,7 @@ def mock_get_glossary_terms_response(mock_catalogue): result_type="GLOSSARY_TERM", ), SearchResult( - id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", + urn="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", name="Security classification", description="Only data that is 'official'", metadata={"parentNodes": []}, @@ -274,18 +298,6 @@ def search_context(search_service): return search_service.context -@pytest.fixture -def detail_dataproduct_context(mock_catalogue): - mock_catalogue.search.return_value = SearchResponse( - total_results=1, - page_results=generate_page(page_size=1, result_type=ResultType.DATA_PRODUCT), - ) - - details_service = DataProductDetailsService(urn="urn:li:dataProduct:test") - context = details_service._get_context() - return context - - @pytest.fixture def detail_database_context(mock_catalogue): mock_catalogue.search.return_value = SearchResponse( @@ -313,7 +325,7 @@ def dataset_with_parent(mock_catalogue) -> dict[str, Any]: page_results=[ generate_search_result( result_type=ResultType.TABLE, - id="dataset-abc", + urn="dataset-abc", metadata={"data_products": [data_product]}, ) ], diff --git a/tests/home/service/test_details.py b/tests/home/service/test_details.py index aeb78e73..463b58eb 100644 --- a/tests/home/service/test_details.py +++ b/tests/home/service/test_details.py @@ -1,7 +1,10 @@ from data_platform_catalogue.entities import ( - ChartMetadata, - RelatedEntity, + Chart, + EntityRef, RelationshipType, + Governance, + DomainRef, + OwnerRef, ) from data_platform_catalogue.search_types import ResultType @@ -9,36 +12,6 @@ from tests.conftest import generate_table_metadata -class TestDetailsDataProductService: - def test_get_context_data_product(self, detail_dataproduct_context, mock_catalogue): - assert ( - detail_dataproduct_context["result"] - == mock_catalogue.search().page_results[0] - ) - result_type = ( - "Data product" - if mock_catalogue.search().page_results[0].result_type - == ResultType.DATA_PRODUCT - else "Table" - ) - assert detail_dataproduct_context["result_type"] == result_type - assert detail_dataproduct_context["h1_value"] == "Details" - - def test_get_context_data_product_tables( - self, detail_dataproduct_context, mock_catalogue - ): - name = mock_catalogue.list_data_product_assets().page_results[0].name - mock_table = { - "name": name, - "urn": mock_catalogue.list_data_product_assets().page_results[0].id, - "description": mock_catalogue.list_data_product_assets() - .page_results[0] - .description, - "type": "TABLE", - } - assert detail_dataproduct_context["tables"][0] == mock_table - - class TestDetailsDatasetService: def test_get_context_contains_table_metadata(self, dataset_with_parent): service = DatasetDetailsService(dataset_with_parent["urn"]) @@ -48,7 +21,7 @@ def test_get_context_contains_table_metadata(self, dataset_with_parent): def test_get_context_contains_parent(self, mock_catalogue): parent = { RelationshipType.PARENT: [ - RelatedEntity(id="urn:li:container:parent", name="parent") + EntityRef(urn="urn:li:container:parent", display_name="parent") ], } mock_table = generate_table_metadata(relations=parent) @@ -56,8 +29,8 @@ def test_get_context_contains_parent(self, mock_catalogue): service = DatasetDetailsService("urn:li:datsset:test") context = service.context - assert context["parent_entity"] == RelatedEntity( - id="urn:li:container:parent", name="parent" + assert context["parent_entity"] == EntityRef( + urn="urn:li:container:parent", display_name="parent" ) @@ -79,7 +52,7 @@ def test_get_context_database_tables(self, detail_database_context, mock_catalog name = mock_catalogue.list_database_tables().page_results[0].name mock_table = { "name": name, - "urn": mock_catalogue.list_database_tables().page_results[0].id, + "urn": mock_catalogue.list_database_tables().page_results[0].urn, "description": mock_catalogue.list_database_tables() .page_results[0] .description, @@ -90,8 +63,22 @@ def test_get_context_database_tables(self, detail_database_context, mock_catalog class TestDetailsChartService: def test_get_context(self, mock_catalogue): - chart_metadata = ChartMetadata( - name="test", description="test", external_url="https://www.test.com" + chart_metadata = Chart( + name="test", + description="test", + external_url="https://www.test.com", + domain=DomainRef(urn="LAA", display_name="LAA"), + governance=Governance( + data_owner=OwnerRef( + display_name="", email="Contact email for the user", urn="" + ), + data_stewards=[ + OwnerRef( + display_name="", email="Contact email for the user", urn="" + ) + ], + ), + platform=EntityRef(urn="", display_name="") ) mock_catalogue.get_chart_details.return_value = chart_metadata diff --git a/tests/home/service/test_glossary.py b/tests/home/service/test_glossary.py index 3df21e6b..bd31a55c 100644 --- a/tests/home/service/test_glossary.py +++ b/tests/home/service/test_glossary.py @@ -13,7 +13,7 @@ def test_get_context(self): "name": "Data protection terms", "members": [ SearchResult( - id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", result_type="GLOSSARY_TERM", name="IAO", description="Information asset owner.\n", @@ -32,7 +32,7 @@ def test_get_context(self): last_modified=None, ), SearchResult( - id="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", + urn="urn:li:glossaryTerm:022b9b68-c211-47ae-aef0-2db13acfeca8", result_type="GLOSSARY_TERM", name="Other term", description="Term description to test groupings work", @@ -57,7 +57,7 @@ def test_get_context(self): "name": "Unsorted", "members": [ SearchResult( - id="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", + urn="urn:li:glossaryTerm:0eb7af28-62b4-4149-a6fa-72a8f1fea1e6", result_type="GLOSSARY_TERM", name="Security classification", description="Only data that is 'official'", diff --git a/tests/selenium/test_search_scenarios.py b/tests/selenium/test_search_scenarios.py index abc64d11..f7f221cf 100644 --- a/tests/selenium/test_search_scenarios.py +++ b/tests/selenium/test_search_scenarios.py @@ -43,25 +43,6 @@ def verify_glossary_link_from_homepage_works(self): self.click_on_the_glossary_link() self.verify_i_am_on_the_glossary_page() - def test_browse_to_first_item_data_product(self, mock_catalogue): - """ - Browses from the home page -> search -> details page - """ - # we need to mock search response to be data products - mock_search_response( - mock_catalogue=mock_catalogue, - page_results=generate_page(result_type=ResultType.DATA_PRODUCT), - total_results=100, - ) - - self.start_on_the_home_page() - self.click_on_the_search_link() - - self.verify_i_am_on_the_search_page() - self.verify_i_have_results() - item_name = self.click_on_the_first_result() - self.verify_i_am_on_the_details_page(item_name) - def test_search_with_query(self): """ Types a search query and press enter @@ -211,7 +192,7 @@ def test_search_to_details(self, mock_catalogue): """ mock_search_response( mock_catalogue=mock_catalogue, - page_results=generate_page(result_type=ResultType.DATA_PRODUCT), + page_results=generate_page(result_type=ResultType.DATABASE), total_results=100, ) self.start_on_the_search_page() diff --git a/tests/test_views.py b/tests/test_views.py index b8e34e0d..cc9cd82d 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -31,35 +31,10 @@ def test_bad_form(self, client): assert response.status_code == 400 -class TestDetailsView: - def test_details_data_product(self, client): - response = client.get( - reverse( - "home:details", - kwargs={ - "id": "urn:li:dataProduct:common-platform", - "result_type": "data_product", - }, - ) - ) - assert response.status_code == 200 - - def test_details_data_product_not_found(self, client, mock_catalogue): - mock_catalogue.search.return_value = SearchResponse( - total_results=0, page_results=[] - ) - response = client.get( - reverse( - "home:details", kwargs={"id": "fake", "result_type": "data_product"} - ) - ) - assert response.status_code == 404 - - class TestTableView: def test_table(self, client): response = client.get( - reverse("home:details", kwargs={"id": "fake", "result_type": "table"}) + reverse("home:details", kwargs={"urn": "fake", "result_type": "table"}) ) assert response.status_code == 200 @@ -67,7 +42,7 @@ def test_table(self, client): class TestChartView: def test_chart(self, client): response = client.get( - reverse("home:details", kwargs={"id": "fake", "result_type": "chart"}) + reverse("home:details", kwargs={"urn": "fake", "result_type": "chart"}) ) assert response.status_code == 200 From e363a258635d3c56cd8aa7a6c3aa76e3fbe3dbad Mon Sep 17 00:00:00 2001 From: Murdo Moyse Date: Wed, 1 May 2024 17:08:08 +0100 Subject: [PATCH 56/64] Foreign keys should be an empty list --- tests/conftest.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 86a03e8a..c4e69ce7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,10 +13,6 @@ SearchResponse, SearchResult, ) -from datahub.metadata.schema_classes import ( - DataProductAssociationClass, - DataProductPropertiesClass, -) from django.test import Client from faker import Faker @@ -205,7 +201,7 @@ def mock_get_table_details_response(mock_catalogue): description="description **with markdown**", nullable=False, is_primary_key=True, - foreign_keys=None + foreign_keys=[] ), ], platform=EntityRef(urn="urn:li:dataPlatform:athena", display_name="athena"), From 484fa29ee2bcb5faa20be51ebd3775579857438a Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Thu, 2 May 2024 14:47:59 +0100 Subject: [PATCH 57/64] Ensure the library is available to the builder The library was not being installed properly because it wasn't copied into the builder image. This results in an import error for data_platform_catalogue. Also, change the working dir for the runtime image - previously it was sticking everything in the root directory which might cause issues. --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 65c03dc4..6802994d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ ENV POETRY_NO_INTERACTION=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache COPY pyproject.toml poetry.lock ./ +COPY lib ./lib RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR RUN poetry run python -m nltk.downloader punkt @@ -31,6 +32,8 @@ RUN poetry run python -m nltk.downloader punkt # The runtime image, used to just run the code provided its virtual environment FROM python:3.11-slim-buster as runtime +WORKDIR /app + ENV VIRTUAL_ENV=/app/.venv \ PATH="/app/.venv/bin:$PATH" From 0cf31207a7b18b0c34d44c63432cebcdfab87888 Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Thu, 2 May 2024 16:37:27 +0100 Subject: [PATCH 58/64] Clean up README --- lib/datahub-client/README.md | 123 ++-------------- .../data_platform_catalogue/client/README.md | 133 ------------------ lib/datahub-client/pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 247 deletions(-) delete mode 100644 lib/datahub-client/data_platform_catalogue/client/README.md diff --git a/lib/datahub-client/README.md b/lib/datahub-client/README.md index a06df70e..6fc254d0 100644 --- a/lib/datahub-client/README.md +++ b/lib/datahub-client/README.md @@ -1,12 +1,8 @@ -# Data platform catalogue +# Datahub client for Find MOJ Data -This library is part of the Ministry of Justice data platform. +This library is part of the Ministry of Justice "Find MOJ Data" service. -It publishes object metadata to a data catalogue, so that the -metadata can be made discoverable by consumers. - -Broadly speaking, a catalogue stores a _metadata graph_, consisting of -_data assets_. Data assets could be **tables**, **schemas** or **databases**. +It pushes metadata to (and retrieves metadata from) a Datahub catalogue. ## How to install @@ -16,118 +12,19 @@ To install the package using `pip`, run: pip install ministryofjustice-data-platform-catalogue ``` -## Terminology - -- **Data assets** - Any databases, tables, or schemas within the metadata graph -- **Domains** - allow metadata to be grouped into different service areas that have - their own governance, like HMCTS, HMPPS, OPG, etc. - -## Example usage +## Getting started ```python from data_platform_catalogue import ( DataHubCatalogueClient, - BaseCatalogueClient, DataLocation, CatalogueMetadata, - DataProductMetadata, TableMetadata, - CatalogueError + Table ) -client: BaseCatalogueClient = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - -data_product = DataProductMetadata( - name = "my_data_product", - description = "bla bla", - version = "v1.0.0", - owner = "7804c127-d677-4900-82f9-83517e51bb94", - email = "justice@justice.gov.uk", - retention_period_in_days = 365, - domain = "LAA", - subdomain = "Legal Aid", - dpia_required = False -) - -table = TableMetadata( - name = "my_table", - description = "bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days = 365, - major_version = 1 -) - -try: - table_fqn = client.upsert_table( - metadata=table, - data_product_metadata=data_product, - location=DataLocation("test_data_product_v1"), - ) -except CatalogueError: - print("oh no") -``` - -## Search example - -```python -response = client.search() - -# Total results across all pages -print(response.total_results) +client = DataHubCatalogueClient(jwt_token='datahub-personal-token', api_url='https://your-datahub-instance') -# Iterate over search results -for item in response.page_results: - print(item) +# Search +client.search() -# Iterate over facet options -for option in response.facets.options('domains'): - print(option.label) - print(option.value) - print(option.count) - -# Include a filter and sort -client.search( - filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])], - sort=SortOption(field="name", ascending=False) -) +# Ingest a table +client.upsert_table(Table(name="Some table", description="A special table I want to share") ``` - -## Search filters - -### Datahub - -Basic filters: - -- urn -- customProperties -- browsePaths / browsePathsV2 -- deprecated (boolean) -- removed (boolean) -- typeNames -- name, qualifiedName -- description, hasDescription - -Timestamps: - -- lastOperationTime (datetime) -- createdAt (timestamp) -- lastModifiedAt (timestamp) - -URNs: - -- platform / platformInstance -- tags, hasTags -- glossaryTerms, hasGlossaryTerms -- domains, hasDomain -- siblings -- owners, hasOwners -- roles, hasRoles -- container - -## Catalogue Implementations - -### DataHub - -- Each table is created as a dataset in DataHub -- Tables that reside in the same athena database (data_product_v1) should - be placed within the same DataHub container. diff --git a/lib/datahub-client/data_platform_catalogue/client/README.md b/lib/datahub-client/data_platform_catalogue/client/README.md deleted file mode 100644 index 8a3d715c..00000000 --- a/lib/datahub-client/data_platform_catalogue/client/README.md +++ /dev/null @@ -1,133 +0,0 @@ -# Data platform catalogue - -This library is part of the Ministry of Justice data platform. - -It publishes object metadata to a data catalogue, so that the -metadata can be made discoverable by consumers. - -Broadly speaking, a catalogue stores a _metadata graph_, consisting of -_data assets_. Data assets could be **tables**, **schemas** or **databases**. - -## How to install - -To install the package using `pip`, run: - -```shell -pip install ministryofjustice-data-platform-catalogue -``` - -## Terminology - -- **Data assets** - Any databases, tables, or schemas within the metadata graph -- **Domains** - allow metadata to be grouped into different service areas that have - their own governance, like HMCTS, HMPPS, OPG, etc. - -## Example usage - -```python -from data_platform_catalogue import ( - DataHubCatalogueClient, - BaseCatalogueClient, DataLocation, CatalogueMetadata, - DataProductMetadata, TableMetadata, - CatalogueError -) - -client: BaseCatalogueClient = DataHubCatalogueClient(jwt_token=jwt_token, api_url=api_url) - -data_product = DataProductMetadata( - name = "my_data_product", - description = "bla bla", - version = "v1.0.0", - owner = "7804c127-d677-4900-82f9-83517e51bb94", - email = "justice@justice.gov.uk", - retention_period_in_days = 365, - domain = "LAA", - subdomain = "Legal Aid", - dpia_required = False -) - -table = TableMetadata( - name = "my_table", - description = "bla bla", - column_details=[ - {"name": "foo", "type": "string", "description": "a"}, - {"name": "bar", "type": "int", "description": "b"}, - ], - retention_period_in_days = 365, - major_version = 1 -) - -try: - table_fqn = client.upsert_table( - table=table, - data_product_metadata=data_product, - location=DataLocation("test_data_product_v1"), - ) -except CatalogueError: - print("oh no") -``` - -## Search example - -```python -response = client.search() - -# Total results across all pages -print(response.total_results) - -# Iterate over search results -for item in response.page_results: - print(item) - -# Iterate over facet options -for option in response.facets.options('domains'): - print(option.label) - print(option.value) - print(option.count) - -# Include a filter and sort -client.search( - filters=[MultiSelectFilter("domains", [response.facets['domains'][0].value])], - sort=SortOption(field="name", ascending=False) -) -``` - -## Search filters - -### Datahub - -Basic filters: - -- urn -- customProperties -- browsePaths / browsePathsV2 -- deprecated (boolean) -- removed (boolean) -- typeNames -- name, qualifiedName -- description, hasDescription - -Timestamps: - -- lastOperationTime (datetime) -- createdAt (timestamp) -- lastModifiedAt (timestamp) - -URNs: - -- platform / platformInstance -- tags, hasTags -- glossaryTerms, hasGlossaryTerms -- domains, hasDomain -- siblings -- owners, hasOwners -- roles, hasRoles -- container - -## Catalogue Implementations - -### DataHub - -- Each table is created as a dataset in DataHub -- Tables that reside in the same athena database (data_product_v1) should - be placed within the same DataHub container. diff --git a/lib/datahub-client/pyproject.toml b/lib/datahub-client/pyproject.toml index d676a12b..57b071c2 100644 --- a/lib/datahub-client/pyproject.toml +++ b/lib/datahub-client/pyproject.toml @@ -11,7 +11,7 @@ max-line-length = 120 [tool.poetry] name = "ministryofjustice-data-platform-catalogue" version = "1.0.0" -description = "Library to integrate the MoJ data platform with the catalogue component." +description = "Wrapper around Datahub supporting custom properties for the Find MOJ Data service." authors = ["MoJ Data Platform Team "] license = "MIT" readme = "README.md" From a893d750790aa4254b15024b1d04d1d4b0fedac9 Mon Sep 17 00:00:00 2001 From: Mat Moore Date: Thu, 2 May 2024 17:02:24 +0100 Subject: [PATCH 59/64] Handle the correct exception type --- home/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/home/views.py b/home/views.py index ae285e31..dc6ecf0b 100644 --- a/home/views.py +++ b/home/views.py @@ -1,4 +1,4 @@ -from django.core.exceptions import ObjectDoesNotExist +from data_platform_catalogue.client.exceptions import EntityDoesNotExist from django.http import Http404, HttpResponseBadRequest from django.shortcuts import render @@ -33,7 +33,7 @@ def details_view(request, result_type, urn): def database_details(urn): try: service = DatabaseDetailsService(urn) - except ObjectDoesNotExist: + except EntityDoesNotExist: raise Http404("Asset does not exist") context = service.context @@ -44,7 +44,7 @@ def database_details(urn): def dataset_details(urn): try: service = DatasetDetailsService(urn) - except ObjectDoesNotExist: + except EntityDoesNotExist: raise Http404("Asset does not exist") context = service.context @@ -55,7 +55,7 @@ def dataset_details(urn): def chart_details(urn): try: service = ChartDetailsService(urn) - except ObjectDoesNotExist: + except EntityDoesNotExist: raise Http404("Asset does not exist") context = service.context From 8e5a46b7e83ba2e8a014f30d00e2ec59889b939f Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Fri, 3 May 2024 10:47:39 +0100 Subject: [PATCH 60/64] Update .vscode/launch.json --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9f959439..5716235a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "program": "${workspaceFolder}/manage.py", "args": ["runserver"], - "justMyCode": false, + // "justMyCode": false, "django": true } ] From 5ba49dc924ef208bf52889931cacc4be53daca73 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Fri, 3 May 2024 10:47:44 +0100 Subject: [PATCH 61/64] Update templates/partial/search_result.html --- templates/partial/search_result.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/partial/search_result.html b/templates/partial/search_result.html index 27a46ea3..44056a42 100644 --- a/templates/partial/search_result.html +++ b/templates/partial/search_result.html @@ -50,7 +50,7 @@

Matched fields
- {{ result.matches|join:", " }} + {{ result.matches|lookup:readable_match_reasons|join:", " }}
{% endif %} From a33943bfe076d9f3da880fe089a01d0482b9c8a2 Mon Sep 17 00:00:00 2001 From: Murdo <109604278+murdo-moj@users.noreply.github.com> Date: Fri, 3 May 2024 11:03:44 +0100 Subject: [PATCH 62/64] Update templates/details_table.html --- templates/details_table.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/details_table.html b/templates/details_table.html index 2b180602..df83d040 100644 --- a/templates/details_table.html +++ b/templates/details_table.html @@ -69,7 +69,7 @@

- {% include "partial/contact_info.html" with data_owner=table..governance.data_owner.display_name data_owner_email=table..governance.data_owner.email %} + {% include "partial/contact_info.html" with data_owner=table.governance.data_owner.display_name data_owner_email=table.governance.data_owner.email %}
From 377f1e7f8691c8a11947b1931be3a5e9c14d3b35 Mon Sep 17 00:00:00 2001 From: Murdo Moyse Date: Fri, 3 May 2024 11:11:18 +0100 Subject: [PATCH 63/64] Added vscode default settings --- .gitignore | 2 ++ .vscode/launch.json.default | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 .vscode/launch.json.default diff --git a/.gitignore b/.gitignore index 647dea36..e0a21305 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +.vscode/** +!.vscode/launch.json.default # PyInstaller # Usually these files are written by a python script from a template diff --git a/.vscode/launch.json.default b/.vscode/launch.json.default new file mode 100644 index 00000000..1c83283c --- /dev/null +++ b/.vscode/launch.json.default @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Django", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/manage.py", + "args": ["runserver"], + "django": true + } + ] +} From 1bf464641b1ce9bf47dd83ff96e2720d485888b4 Mon Sep 17 00:00:00 2001 From: Murdo Moyse Date: Fri, 3 May 2024 11:13:39 +0100 Subject: [PATCH 64/64] Removed setting from git --- .vscode/launch.json | 17 ----------------- .vscode/settings.json | 5 ----- 2 files changed, 22 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 5716235a..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python Debugger: Django", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/manage.py", - "args": ["runserver"], - // "justMyCode": false, - "django": true - } - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d969f962..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "python.testing.pytestArgs": ["tests"], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -}