From af852125f87acfc1851ce100a2d620fc786e398d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:58:00 -0600 Subject: [PATCH 01/65] Template --- src/Pipfile | 1 + src/Pipfile.lock | 1712 +++++++++++++++++------------ src/epplibwrapper/client.py | 15 +- src/epplibwrapper/socket.py | 23 +- src/epplibwrapper/utility/pool.py | 25 + src/requirements.txt | 92 +- 6 files changed, 1131 insertions(+), 737 deletions(-) create mode 100644 src/epplibwrapper/utility/pool.py diff --git a/src/Pipfile b/src/Pipfile index 6900b0bcf..5f84ac448 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -26,6 +26,7 @@ boto3 = "*" typing-extensions ='*' django-login-required-middleware = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} +gsocketpool = "*" [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 3e7ae367d..e01528f1d 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1242c67b31261243a35128410d4a928fca3729ddac13b8c8e25adf31445c6328" + "sha256": "49cd54bd0c272b04889898edc62b2a314d9675409d862a93e257b3f79e09f84e" }, "pipfile-spec": 6, "requires": {}, @@ -14,6 +14,14 @@ ] }, "default": { + "annotated-types": { + "hashes": [ + "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", + "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", @@ -24,19 +32,20 @@ }, "boto3": { "hashes": [ - "sha256:30f8ab1cf89d5864a80ba2d5eb5316dbd2a63c9469877e0cffb522630438aa85", - "sha256:77e8fa7c257f9ed8bfe0c3ffc2ccc47b1cfa27058f99415b6003699d1202e0c0" + "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2", + "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0" ], "index": "pypi", - "version": "==1.26.145" + "markers": "python_version >= '3.7'", + "version": "==1.28.62" }, "botocore": { "hashes": [ - "sha256:264a3f19ed280d80711b7e278be09acff7ed379a96432fdf179b4e6e3a687e6a", - "sha256:65e2a2b1cc70583225f87d6d63736215f93c6234721967bdab872270ba7a1f45" + "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c", + "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7" ], "markers": "python_version >= '3.7'", - "version": "==1.29.145" + "version": "==1.31.62" }, "cachetools": { "hashes": [ @@ -44,15 +53,16 @@ "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==5.3.1" }, "certifi": { "hashes": [ - "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", - "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.7" + "version": "==2023.7.22" }, "cfenv": { "hashes": [ @@ -64,178 +74,186 @@ }, "cffi": { "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "version": "==1.15.1" + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "python_version >= '3.8'", + "version": "==1.16.0" }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843", + "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786", + "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e", + "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8", + "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4", + "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa", + "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d", + "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82", + "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7", + "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895", + "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d", + "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a", + "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382", + "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678", + "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b", + "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e", + "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741", + "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4", + "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596", + "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9", + "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69", + "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c", + "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77", + "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13", + "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459", + "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e", + "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7", + "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908", + "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a", + "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f", + "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8", + "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482", + "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d", + "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d", + "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545", + "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34", + "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86", + "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6", + "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe", + "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e", + "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc", + "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7", + "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd", + "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c", + "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557", + "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a", + "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89", + "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078", + "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e", + "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4", + "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403", + "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0", + "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89", + "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115", + "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9", + "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05", + "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a", + "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec", + "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56", + "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38", + "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479", + "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c", + "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e", + "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd", + "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186", + "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455", + "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c", + "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65", + "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78", + "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287", + "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df", + "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43", + "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1", + "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7", + "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989", + "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a", + "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63", + "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884", + "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649", + "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810", + "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828", + "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4", + "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2", + "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd", + "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5", + "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe", + "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293", + "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e", + "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e", + "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.1.0" + "version": "==3.3.0" }, "cryptography": { "hashes": [ - "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db", - "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a", - "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039", - "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c", - "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3", - "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485", - "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c", - "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca", - "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5", - "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5", - "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3", - "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb", - "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43", - "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31", - "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc", - "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b", - "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006", - "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a", - "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699" + "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67", + "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311", + "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8", + "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13", + "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143", + "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f", + "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829", + "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd", + "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397", + "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac", + "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d", + "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a", + "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839", + "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e", + "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6", + "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9", + "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860", + "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca", + "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91", + "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d", + "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714", + "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb", + "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f" ], "markers": "python_version >= '3.7'", - "version": "==41.0.1" + "version": "==41.0.4" }, "defusedxml": { "hashes": [ @@ -247,10 +265,10 @@ }, "dj-database-url": { "hashes": [ - "sha256:9c9e5f7224f62635a787e9cc3c6762c9be2b19541a21e3c08fa573bd01609b4b", - "sha256:a35a9f0f43775ca6f90d819dc456233ef7bcc76b47377d5d908b75c7eb320624" + "sha256:04bc34b248d4c21aaa13e4ab419ae6575ef5f10f3df735ce7da97722caa356e0", + "sha256:f2042cefe1086e539c9da39fad5ad7f61173bf79665e69bf7e4de55fa88b135f" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "dj-email-url": { "hashes": [ @@ -261,19 +279,20 @@ }, "django": { "hashes": [ - "sha256:066b6debb5ac335458d2a713ed995570536c8b59a580005acb0732378d5eb1ee", - "sha256:7efa6b1f781a6119a10ac94b4794ded90db8accbe7802281cd26f8664ffed59c" + "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f", + "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215" ], "index": "pypi", - "version": "==4.2.1" + "markers": "python_version >= '3.8'", + "version": "==4.2.6" }, "django-allow-cidr": { "hashes": [ - "sha256:24b71f70257e97bab9fdb5ad8342c96eeea1d45bc06a36332978574252219401", - "sha256:6709f4581dfd2a00476a134741a738a7f67714ec4f8596c55b22cf3b2ac5a12e" + "sha256:11126c5bb9df3a61ff9d97304856ba7e5b26d46c6d456709a6d9e28483bff47f", + "sha256:382c5d7a9807279e3e96e4f4892b59163a2b30128c596902bf5f80e133e1ccbb" ], "index": "pypi", - "version": "==0.6.0" + "version": "==0.7.1" }, "django-auditlog": { "hashes": [ @@ -281,6 +300,7 @@ "sha256:b9d3acebb64f3f2785157efe3f2f802e0929aafc579d85bbfb9827db4adab532" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==2.3.0" }, "django-cache-url": { @@ -318,19 +338,20 @@ "phonenumberslite" ], "hashes": [ - "sha256:4eaab35fe9a163046dc3a47188771385c56a788e0e11b7bbcc662e1e6b7b9104", - "sha256:63721dbdc7424cd594a08d80f550e790cf6e7c903cbc0fb4dd9d86baac8b8c51" + "sha256:16778f2717ea2aecc6178beb0d6bc431c78c6a8b0474e1fa8face040efeb6e9e", + "sha256:20c7c5c449e33eed5fd45ef8d3dc668faabaeff3277eddd1892b262d686ba381" ], - "index": "pypi", - "version": "==7.1.0" + "markers": "python_version >= '3.8'", + "version": "==7.2.0" }, "django-widget-tweaks": { "hashes": [ - "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", - "sha256:fe6b17d5d595c63331f300917980db2afcf71f240ab9341b954aea8f45d25b9a" + "sha256:1c2180681ebb994e922c754804c7ffebbe1245014777ac47897a81f57cc629c7", + "sha256:a41b7b2f05bd44d673d11ebd6c09a96f1d013ee98121cb98c384fe84e33b881e" ], "index": "pypi", - "version": "==1.4.12" + "markers": "python_version >= '3.8'", + "version": "==1.5.0" }, "environs": { "extras": [ @@ -340,16 +361,16 @@ "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124", "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9" ], - "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==9.5.0" }, "faker": { "hashes": [ - "sha256:a70de9ec7a14a02d278755a11134baa5a297bb82600f115022d0d07080a9e77a", - "sha256:dd15fa165ced55f668fbb0ad20ece98ab78ddacd58dc056950d66980ff61fa79" + "sha256:85468e16d4a9a8712bfdb98ba55aaf17c60658266a76958d099aee6a18c0a6c5", + "sha256:d75401c631a991b32d3595f26250f42c007cc32653ac3e522b626f3d80770571" ], - "index": "pypi", - "version": "==18.10.0" + "markers": "python_version >= '3.8'", + "version": "==19.9.0" }, "fred-epplib": { "git": "https://github.com/cisagov/epplib.git", @@ -369,13 +390,135 @@ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.18.3" }, + "gevent": { + "hashes": [ + "sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a", + "sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2", + "sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535", + "sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e", + "sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653", + "sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1", + "sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c", + "sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648", + "sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599", + "sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea", + "sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6", + "sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f", + "sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9", + "sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e", + "sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34", + "sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397", + "sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507", + "sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b", + "sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd", + "sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe", + "sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a", + "sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b", + "sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771", + "sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e", + "sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69", + "sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a", + "sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011", + "sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7", + "sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71", + "sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5", + "sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae", + "sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7", + "sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39", + "sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d", + "sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599", + "sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07", + "sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904", + "sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a", + "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543", + "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303" + ], + "markers": "python_version >= '3.8'", + "version": "==23.9.1" + }, + "greenlet": { + "hashes": [ + "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a", + "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c", + "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9", + "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d", + "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14", + "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383", + "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b", + "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99", + "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7", + "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17", + "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314", + "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66", + "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed", + "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c", + "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f", + "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464", + "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b", + "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c", + "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4", + "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362", + "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692", + "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365", + "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9", + "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e", + "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb", + "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06", + "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695", + "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f", + "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04", + "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f", + "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b", + "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7", + "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9", + "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce", + "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c", + "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35", + "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b", + "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4", + "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51", + "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a", + "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355", + "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7", + "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625", + "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99", + "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779", + "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd", + "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0", + "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705", + "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c", + "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f", + "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c", + "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870", + "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353", + "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2", + "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423", + "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a", + "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6", + "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1", + "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947", + "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810", + "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f", + "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a" + ], + "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'", + "version": "==3.0.0" + }, + "gsocketpool": { + "hashes": [ + "sha256:f2e2749aceadce6b27ca52e2b0a64af99797746a8681e1a2963f72007c14cb14" + ], + "index": "pypi", + "version": "==0.1.6" + }, "gunicorn": { "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", + "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033" ], "index": "pypi", - "version": "==20.1.0" + "markers": "python_version >= '3.5'", + "version": "==21.2.0" }, "idna": { "hashes": [ @@ -395,86 +538,101 @@ }, "lxml": { "hashes": [ - "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7", - "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726", - "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03", - "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140", - "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a", - "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05", - "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03", - "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419", - "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4", - "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e", - "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67", - "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50", - "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894", - "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf", - "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947", - "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1", - "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd", - "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3", - "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92", - "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3", - "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457", - "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74", - "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf", - "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1", - "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4", - "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975", - "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5", - "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe", - "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7", - "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1", - "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2", - "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409", - "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f", - "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f", - "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5", - "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24", - "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e", - "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4", - "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a", - "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c", - "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de", - "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f", - "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b", - "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5", - "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7", - "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a", - "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c", - "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9", - "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e", - "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab", - "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941", - "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5", - "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45", - "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7", - "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892", - "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746", - "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c", - "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53", - "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe", - "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184", - "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38", - "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df", - "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9", - "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b", - "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2", - "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0", - "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda", - "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b", - "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5", - "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380", - "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33", - "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8", - "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1", - "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889", - "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9", - "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f", - "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c" + "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3", + "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d", + "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a", + "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120", + "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305", + "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287", + "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23", + "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52", + "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f", + "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4", + "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584", + "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f", + "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693", + "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef", + "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5", + "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02", + "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc", + "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7", + "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da", + "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a", + "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40", + "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8", + "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd", + "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601", + "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c", + "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be", + "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2", + "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c", + "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129", + "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc", + "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2", + "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1", + "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7", + "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d", + "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477", + "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d", + "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e", + "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7", + "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2", + "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574", + "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf", + "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b", + "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98", + "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12", + "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42", + "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35", + "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d", + "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce", + "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d", + "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f", + "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db", + "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4", + "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694", + "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac", + "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2", + "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7", + "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96", + "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d", + "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b", + "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a", + "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13", + "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340", + "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6", + "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458", + "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c", + "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c", + "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9", + "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432", + "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991", + "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69", + "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf", + "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb", + "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b", + "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833", + "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76", + "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85", + "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e", + "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50", + "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8", + "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4", + "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b", + "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5", + "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190", + "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7", + "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa", + "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0", + "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9", + "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0", + "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b", + "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5", + "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7", + "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.9.2" + "version": "==4.9.3" }, "mako": { "hashes": [ @@ -486,75 +644,86 @@ }, "markupsafe": { "hashes": [ - "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", - "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", - "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", - "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", - "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", - "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", - "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", - "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", - "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", - "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", - "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", - "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", - "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", - "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", - "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", - "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", - "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", - "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", - "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", - "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", - "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", - "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", - "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", - "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", - "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", - "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", - "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", - "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", - "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", - "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", - "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", - "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", - "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", - "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", - "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", - "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", - "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", - "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", - "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", - "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", - "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", - "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", - "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", - "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", - "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", - "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", - "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", - "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", - "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", - "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", - "version": "==2.1.2" + "version": "==2.1.3" }, "marshmallow": { "hashes": [ - "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78", - "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b" + "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889", + "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c" ], - "markers": "python_version >= '3.7'", - "version": "==3.19.0" + "markers": "python_version >= '3.8'", + "version": "==3.20.1" }, "oic": { "hashes": [ - "sha256:2de3b83f1299dda8ed0460baad8bb2d4c6ac8bfc08a220c768b7e6e754caf9e7", - "sha256:f82e087e0ffaba2194ebd24694721a25167d5d467fb06bf4f4da9f48d43e0cc6" + "sha256:385a1f64bb59519df1e23840530921bf416740240f505ea6d161e331d3d39fad", + "sha256:fcbf948a22e4d4df66f6bf57d327933f32a7b539640d9b42883457634360ba78" ], "index": "pypi", - "version": "==1.6.0" + "markers": "python_version ~= '3.7'", + "version": "==1.6.1" }, "orderedmultidict": { "hashes": [ @@ -565,86 +734,94 @@ }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "phonenumberslite": { "hashes": [ - "sha256:670a3e1ea775a9fad89d843d2d18a0e2bb0c3719e1a6bb2b96fb12f7ddd579a0", - "sha256:d1d23707dbde2b8b6940f80b181638a3e10334ff2f122c7165ce43e91c6639fe" + "sha256:8d1e5f2adfee2a634ccdb54b251dec32c5308fbca3d7f6ae6058f4adee4594a3", + "sha256:98684f21804c6df2e7d224e72d60defee20eddf9e144d57f24cbd9db0df450e0" ], - "version": "==8.13.13" + "version": "==8.13.22" }, "psycopg2-binary": { "hashes": [ - "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514", - "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe", - "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be", - "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3", - "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d", - "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e", - "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081", - "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb", - "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c", - "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee", - "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b", - "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8", - "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5", - "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc", - "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0", - "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0", - "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963", - "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f", - "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503", - "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2", - "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b", - "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9", - "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f", - "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303", - "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b", - "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d", - "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70", - "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2", - "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0", - "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141", - "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896", - "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763", - "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1", - "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c", - "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b", - "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e", - "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e", - "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f", - "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19", - "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb", - "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6", - "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b", - "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8", - "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3", - "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848", - "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365", - "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3", - "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e", - "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1", - "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb", - "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827", - "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7", - "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd", - "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a", - "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820", - "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54", - "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df", - "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4", - "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1", - "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249", - "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232", - "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7" + "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9", + "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77", + "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e", + "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84", + "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3", + "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2", + "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67", + "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876", + "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152", + "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f", + "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a", + "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6", + "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503", + "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f", + "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493", + "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996", + "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f", + "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e", + "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59", + "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94", + "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7", + "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682", + "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420", + "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae", + "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291", + "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe", + "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980", + "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692", + "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119", + "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716", + "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472", + "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b", + "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2", + "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc", + "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c", + "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5", + "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984", + "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9", + "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf", + "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0", + "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f", + "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212", + "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb", + "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be", + "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90", + "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041", + "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7", + "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860", + "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245", + "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27", + "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417", + "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359", + "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202", + "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0", + "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7", + "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba", + "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1", + "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd", + "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07", + "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98", + "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55", + "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d", + "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972", + "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f", + "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e", + "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26", + "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957", + "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53", + "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52" ], "index": "pypi", - "version": "==2.9.6" + "markers": "python_version >= '3.7'", + "version": "==2.9.9" }, "pycparser": { "hashes": [ @@ -655,83 +832,170 @@ }, "pycryptodomex": { "hashes": [ - "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6", - "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c", - "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646", - "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470", - "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74", - "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a", - "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c", - "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422", - "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e", - "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37", - "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb", - "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8", - "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196", - "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef", - "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b", - "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb", - "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194", - "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7", - "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d", - "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff", - "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35", - "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7", - "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8", - "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f", - "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a", - "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e", - "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419", - "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222", - "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2", - "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278", - "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88", - "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5" + "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc", + "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975", + "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c", + "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865", + "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905", + "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8", + "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d", + "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644", + "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188", + "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2", + "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4", + "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002", + "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa", + "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338", + "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec", + "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761", + "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b", + "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464", + "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56", + "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139", + "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0", + "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6", + "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40", + "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb", + "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53", + "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d", + "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f", + "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3", + "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51", + "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c", + "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2", + "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f" ], "index": "pypi", - "version": "==3.18.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.19.0" }, "pydantic": { "hashes": [ - "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375", - "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277", - "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d", - "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4", - "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca", - "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c", - "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01", - "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18", - "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68", - "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887", - "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459", - "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4", - "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5", - "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e", - "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1", - "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33", - "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a", - "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56", - "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108", - "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2", - "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4", - "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878", - "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0", - "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e", - "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6", - "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f", - "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800", - "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea", - "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f", - "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b", - "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1", - "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd", - "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319", - "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab", - "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85", - "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f" + "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7", + "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1" ], "markers": "python_version >= '3.7'", - "version": "==1.10.8" + "version": "==2.4.2" + }, + "pydantic-core": { + "hashes": [ + "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e", + "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33", + "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7", + "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7", + "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea", + "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4", + "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0", + "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7", + "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94", + "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff", + "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82", + "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd", + "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893", + "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e", + "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d", + "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901", + "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9", + "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c", + "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7", + "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891", + "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f", + "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a", + "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9", + "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5", + "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e", + "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a", + "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c", + "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f", + "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514", + "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b", + "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302", + "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096", + "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0", + "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27", + "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884", + "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a", + "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357", + "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430", + "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221", + "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325", + "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4", + "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05", + "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55", + "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875", + "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970", + "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc", + "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6", + "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f", + "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b", + "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d", + "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15", + "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118", + "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee", + "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e", + "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6", + "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208", + "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede", + "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3", + "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e", + "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada", + "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175", + "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a", + "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c", + "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f", + "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58", + "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f", + "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a", + "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a", + "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921", + "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e", + "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904", + "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776", + "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52", + "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf", + "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8", + "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f", + "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b", + "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63", + "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c", + "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f", + "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468", + "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e", + "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab", + "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2", + "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb", + "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb", + "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132", + "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b", + "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607", + "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934", + "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698", + "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e", + "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561", + "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de", + "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b", + "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a", + "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595", + "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402", + "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881", + "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429", + "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5", + "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7", + "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c", + "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531", + "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6", + "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521" + ], + "markers": "python_version >= '3.7'", + "version": "==2.10.1" + }, + "pydantic-settings": { + "hashes": [ + "sha256:962dc3672495aad6ae96a4390fac7e593591e144625e5112d359f8f67fb75945", + "sha256:ddd907b066622bd67603b75e2ff791875540dc485b7307c4fffc015719da8625" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.3" }, "pyjwkest": { "hashes": [ @@ -762,23 +1026,24 @@ "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==2.31.0" }, "s3transfer": { "hashes": [ - "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346", - "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9" + "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a", + "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e" ], "markers": "python_version >= '3.7'", - "version": "==0.6.1" + "version": "==0.7.0" }, "setuptools": { "hashes": [ - "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f", - "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102" + "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", + "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a" ], - "markers": "python_version >= '3.7'", - "version": "==67.8.0" + "markers": "python_version >= '3.8'", + "version": "==68.2.2" }, "six": { "hashes": [ @@ -798,27 +1063,79 @@ }, "typing-extensions": { "hashes": [ - "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26", - "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], "index": "pypi", - "version": "==4.6.3" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" }, "urllib3": { "hashes": [ - "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", - "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" + "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", + "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.16" + "markers": "python_version >= '3.7'", + "version": "==2.0.6" }, "whitenoise": { "hashes": [ - "sha256:599dc6ca57e48929dfeffb2e8e187879bfe2aed0d49ca419577005b7f2cc930b", - "sha256:a02d6660ad161ff17e3042653c8e3f5ecbb2a2481a006bde125b9efb9a30113a" + "sha256:8998f7370973447fac1e8ef6e8ded2c5209a7b1f67c1012866dbcd09681c3251", + "sha256:b1f9db9bf67dc183484d760b99f4080185633136a273a03f6436034a41064146" ], "index": "pypi", - "version": "==6.4.0" + "markers": "python_version >= '3.8'", + "version": "==6.6.0" + }, + "zope.event": { + "hashes": [ + "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", + "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd" + ], + "markers": "python_version >= '3.7'", + "version": "==5.0" + }, + "zope.interface": { + "hashes": [ + "sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff", + "sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c", + "sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac", + "sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f", + "sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d", + "sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309", + "sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736", + "sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179", + "sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb", + "sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941", + "sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d", + "sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92", + "sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b", + "sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41", + "sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f", + "sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3", + "sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d", + "sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8", + "sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3", + "sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1", + "sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1", + "sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40", + "sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d", + "sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1", + "sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605", + "sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7", + "sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd", + "sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43", + "sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0", + "sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b", + "sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379", + "sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a", + "sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83", + "sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56", + "sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9", + "sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de" + ], + "markers": "python_version >= '3.7'", + "version": "==6.1" } }, "develop": { @@ -836,6 +1153,7 @@ "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==1.7.5" }, "beautifulsoup4": { @@ -848,50 +1166,49 @@ }, "black": { "hashes": [ - "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", - "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", - "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", - "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", - "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", - "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", - "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", - "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", - "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", - "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", - "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", - "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", - "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", - "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", - "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", - "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", - "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", - "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", - "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", - "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", - "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", - "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", - "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", - "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", - "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" + "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f", + "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7", + "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100", + "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573", + "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d", + "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f", + "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9", + "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300", + "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948", + "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325", + "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9", + "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71", + "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186", + "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f", + "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe", + "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855", + "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80", + "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393", + "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c", + "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204", + "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377", + "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" ], "index": "pypi", - "version": "==23.3.0" + "markers": "python_version >= '3.8'", + "version": "==23.9.1" }, "blinker": { "hashes": [ - "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213", - "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0" + "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d", + "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa" ], "markers": "python_version >= '3.7'", - "version": "==1.6.2" + "version": "==1.6.3" }, "boto3": { "hashes": [ - "sha256:30f8ab1cf89d5864a80ba2d5eb5316dbd2a63c9469877e0cffb522630438aa85", - "sha256:77e8fa7c257f9ed8bfe0c3ffc2ccc47b1cfa27058f99415b6003699d1202e0c0" + "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2", + "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0" ], "index": "pypi", - "version": "==1.26.145" + "markers": "python_version >= '3.7'", + "version": "==1.28.62" }, "boto3-mocking": { "hashes": [ @@ -899,55 +1216,59 @@ "sha256:d0273366d3cb86c5bf3d6f31d1c6e40c11b714d49b5f2d5125234d0d59aa9378" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.1.1" }, "boto3-stubs": { "hashes": [ - "sha256:9413cb395c803d5b85e9ec7b16fba855a613ecd78b2e0011e2f6b62cf0b4fc1e", - "sha256:be2007f92138781288c7a22eba30b7d60742466fc28edd04637b31fabee854a5" + "sha256:22a08e27d2ede1849dd0d75e8501099240b34bd70adb606584a2af2e12f3a22b", + "sha256:f5ae08d2abae7709fff3e7cacea66c41cb43236527cfaf3975e506c6c67439a0" ], "index": "pypi", - "version": "==1.26.145" + "markers": "python_version >= '3.7'", + "version": "==1.28.62" }, "botocore": { "hashes": [ - "sha256:264a3f19ed280d80711b7e278be09acff7ed379a96432fdf179b4e6e3a687e6a", - "sha256:65e2a2b1cc70583225f87d6d63736215f93c6234721967bdab872270ba7a1f45" + "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c", + "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7" ], "markers": "python_version >= '3.7'", - "version": "==1.29.145" + "version": "==1.31.62" }, "botocore-stubs": { "hashes": [ - "sha256:80ffab72ad428d20cb1cf538ee55fcd94f7d81315b77d84fec99e218c3974e8b", - "sha256:928c58a434dd83bef956e3b5bb1e96278fff5eee9f8b8ab08d916cef1e9a2014" + "sha256:2ce555e5dff2e91fc22bd67106534bf3e0593b838d87f8a49d3b8e87fa83a440", + "sha256:d30217d8f6a0888616a44c83150490c5fbc899550ffe1896a2cd15a2205fd648" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.29.145" + "version": "==1.31.62" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.7" }, "django": { "hashes": [ - "sha256:066b6debb5ac335458d2a713ed995570536c8b59a580005acb0732378d5eb1ee", - "sha256:7efa6b1f781a6119a10ac94b4794ded90db8accbe7802281cd26f8664ffed59c" + "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f", + "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215" ], "index": "pypi", - "version": "==4.2.1" + "markers": "python_version >= '3.8'", + "version": "==4.2.6" }, "django-debug-toolbar": { "hashes": [ - "sha256:a0b532ef5d52544fd745d1dcfc0557fa75f6f0d1962a8298bd568427ef2fa436", - "sha256:f57882e335593cb8e74c2bda9f1116bbb9ca8fc0d81b50a75ace0f83de5173c7" + "sha256:af99128c06e8e794479e65ab62cc6c7d1e74e1c19beb44dcbf9bad7a9c017327", + "sha256:bc7fdaafafcdedefcc67a4a5ad9dac96efd6e41db15bc74d402a54a2ba4854dc" ], "index": "pypi", - "version": "==4.1.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.0" }, "django-model2puml": { "hashes": [ @@ -958,35 +1279,37 @@ }, "django-stubs": { "hashes": [ - "sha256:66477bdba25407623f4079205e58f3c7265a4f0d8f7c9f540a6edc16f8883a5b", - "sha256:8c15d5f7b05926805cfb25f2bfbf3509c37792fbd8aec5aedea358b85d8bccd5" + "sha256:7d4a132c381519815e865c27a89eca41bcbd06056832507224816a43d75c601c", + "sha256:834b60fd81510cce6b56c1c6c28bec3c504a418bc90ff7d0063fabe8ab9a7868" ], "index": "pypi", - "version": "==4.2.1" + "markers": "python_version >= '3.8'", + "version": "==4.2.4" }, "django-stubs-ext": { "hashes": [ - "sha256:2696d6f7d8538341b060cffa9565c72ea797e866687e040b86d29cad8799e5fe", - "sha256:4b6b63e49f4ba30d93ec46f87507648c99c9de6911e651ad69db7084fd5b2f4e" + "sha256:c69d1cc46f1c4c3b7894b685a5022c29b2a36c7cfb52e23762eaf357ebfc2c98", + "sha256:fdacc65a14d2d4b97334b58ff178a5853ec8c8c76cec406e417916ad67536ce4" ], "markers": "python_version >= '3.8'", - "version": "==4.2.1" + "version": "==4.2.2" }, "django-webtest": { "hashes": [ - "sha256:c8c32041791cdae468e443097c432c67cf17cad339e1ab88b01a6c4841ee4c74", - "sha256:ef075e98b38fe3836dc533c2924d3e37c6bb3483008c40567115518a0303b1af" + "sha256:9597d26ced599bc5d4d9366bb451469fc9707b4779f79543cdf401ae6c5aeb35", + "sha256:e29baf8337e7fe7db41ce63ca6661f7b5c77fe56f506f48b305e09313f5475b4" ], "index": "pypi", - "version": "==1.9.10" + "version": "==1.9.11" }, "flake8": { "hashes": [ - "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7", - "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181" + "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23", + "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5" ], "index": "pypi", - "version": "==6.0.0" + "markers": "python_full_version >= '3.8.1'", + "version": "==6.1.0" }, "gitdb": { "hashes": [ @@ -998,11 +1321,11 @@ }, "gitpython": { "hashes": [ - "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573", - "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d" + "sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33", + "sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54" ], "markers": "python_version >= '3.7'", - "version": "==3.1.31" + "version": "==3.1.37" }, "jmespath": { "hashes": [ @@ -1014,11 +1337,11 @@ }, "markdown-it-py": { "hashes": [ - "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", - "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1" + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" ], - "markers": "python_version >= '3.7'", - "version": "==2.2.0" + "markers": "python_version >= '3.8'", + "version": "==3.0.0" }, "mccabe": { "hashes": [ @@ -1038,35 +1361,37 @@ }, "mypy": { "hashes": [ - "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703", - "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf", - "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4", - "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85", - "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd", - "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae", - "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd", - "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca", - "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305", - "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409", - "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c", - "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb", - "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee", - "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a", - "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228", - "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897", - "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d", - "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f", - "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152", - "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf", - "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8", - "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11", - "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017", - "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929", - "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e", - "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a" + "sha256:091f53ff88cb093dcc33c29eee522c087a438df65eb92acd371161c1f4380ff0", + "sha256:1a69db3018b87b3e6e9dd28970f983ea6c933800c9edf8c503c3135b3274d5ad", + "sha256:24f3de8b9e7021cd794ad9dfbf2e9fe3f069ff5e28cb57af6f873ffec1cb0425", + "sha256:31eba8a7a71f0071f55227a8057468b8d2eb5bf578c8502c7f01abaec8141b2f", + "sha256:3c8835a07b8442da900db47ccfda76c92c69c3a575872a5b764332c4bacb5a0a", + "sha256:3df87094028e52766b0a59a3e46481bb98b27986ed6ded6a6cc35ecc75bb9182", + "sha256:49499cf1e464f533fc45be54d20a6351a312f96ae7892d8e9f1708140e27ce41", + "sha256:4c192445899c69f07874dabda7e931b0cc811ea055bf82c1ababf358b9b2a72c", + "sha256:4f3d27537abde1be6d5f2c96c29a454da333a2a271ae7d5bc7110e6d4b7beb3f", + "sha256:7469545380dddce5719e3656b80bdfbb217cfe8dbb1438532d6abc754b828fed", + "sha256:7807a2a61e636af9ca247ba8494031fb060a0a744b9fee7de3a54bed8a753323", + "sha256:856bad61ebc7d21dbc019b719e98303dc6256cec6dcc9ebb0b214b81d6901bd8", + "sha256:89513ddfda06b5c8ebd64f026d20a61ef264e89125dc82633f3c34eeb50e7d60", + "sha256:8e0db37ac4ebb2fee7702767dfc1b773c7365731c22787cb99f507285014fcaf", + "sha256:971104bcb180e4fed0d7bd85504c9036346ab44b7416c75dd93b5c8c6bb7e28f", + "sha256:9e1589ca150a51d9d00bb839bfeca2f7a04f32cd62fad87a847bc0818e15d7dc", + "sha256:9f8464ed410ada641c29f5de3e6716cbdd4f460b31cf755b2af52f2d5ea79ead", + "sha256:ab98b8f6fdf669711f3abe83a745f67f50e3cbaea3998b90e8608d2b459fd566", + "sha256:b19006055dde8a5425baa5f3b57a19fa79df621606540493e5e893500148c72f", + "sha256:c69051274762cccd13498b568ed2430f8d22baa4b179911ad0c1577d336ed849", + "sha256:d2dad072e01764823d4b2f06bc7365bb1d4b6c2f38c4d42fade3c8d45b0b4b67", + "sha256:dccd850a2e3863891871c9e16c54c742dba5470f5120ffed8152956e9e0a5e13", + "sha256:e28d7b221898c401494f3b77db3bac78a03ad0a0fff29a950317d87885c655d2", + "sha256:e4b7a99275a61aa22256bab5839c35fe8a6887781862471df82afb4b445daae6", + "sha256:eb7ff4007865833c470a601498ba30462b7374342580e2346bf7884557e40531", + "sha256:f8598307150b5722854f035d2e70a1ad9cc3c72d392c34fffd8c66d888c90f17", + "sha256:fea451a3125bf0bfe716e5d7ad4b92033c471e4b5b3e154c67525539d14dc15a" ], "index": "pypi", - "version": "==1.3.0" + "markers": "python_version >= '3.8'", + "version": "==1.6.0" }, "mypy-extensions": { "hashes": [ @@ -1086,19 +1411,19 @@ }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pathspec": { "hashes": [ - "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", - "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], "markers": "python_version >= '3.7'", - "version": "==0.11.1" + "version": "==0.11.2" }, "pbr": { "hashes": [ @@ -1110,35 +1435,35 @@ }, "platformdirs": { "hashes": [ - "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f", - "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5" + "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3", + "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e" ], "markers": "python_version >= '3.7'", - "version": "==3.5.1" + "version": "==3.11.0" }, "pycodestyle": { "hashes": [ - "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", - "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" + "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0", + "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8" ], - "markers": "python_version >= '3.6'", - "version": "==2.10.0" + "markers": "python_version >= '3.8'", + "version": "==2.11.0" }, "pyflakes": { "hashes": [ - "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf", - "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd" + "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774", + "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc" ], - "markers": "python_version >= '3.6'", - "version": "==3.0.1" + "markers": "python_version >= '3.8'", + "version": "==3.1.0" }, "pygments": { "hashes": [ - "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", - "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" + "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", + "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" ], "markers": "python_version >= '3.7'", - "version": "==2.15.1" + "version": "==2.16.1" }, "python-dateutil": { "hashes": [ @@ -1150,65 +1475,75 @@ }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "markers": "python_version >= '3.6'", - "version": "==6.0" + "version": "==6.0.1" }, "rich": { "hashes": [ - "sha256:76f6b65ea7e5c5d924ba80e322231d7cb5b5981aa60bfc1e694f1bc097fe6fe1", - "sha256:d204aadb50b936bf6b1a695385429d192bc1fdaf3e8b907e8e26f4c4e4b5bf75" + "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245", + "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.4.1" + "version": "==13.6.0" }, "s3transfer": { "hashes": [ - "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346", - "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9" + "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a", + "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e" ], "markers": "python_version >= '3.7'", - "version": "==0.6.1" + "version": "==0.7.0" }, "six": { "hashes": [ @@ -1220,19 +1555,19 @@ }, "smmap": { "hashes": [ - "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", - "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" + "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", + "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da" ], - "markers": "python_version >= '3.6'", - "version": "==5.0.0" + "markers": "python_version >= '3.7'", + "version": "==5.0.1" }, "soupsieve": { "hashes": [ - "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8", - "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea" + "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690", + "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7" ], - "markers": "python_version >= '3.7'", - "version": "==2.4.1" + "markers": "python_version >= '3.8'", + "version": "==2.5" }, "sqlparse": { "hashes": [ @@ -1260,72 +1595,67 @@ }, "types-awscrt": { "hashes": [ - "sha256:50fe7610aa40550a23d79d6167b2b8536281f038f42846eda0e520d6e3e01787", - "sha256:763d8d543f145d51cd16ea407d079608b126941d24525e16cb1de31d949ff563" + "sha256:477a14565909312fe1de70d0b301548e83c038f436b8a1d7c83729e87cdd0b85", + "sha256:d8c379420ba75b1e43687d12b0b772a5bb17f352859a2bef6aa8f0abde123f55" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.16.19" + "version": "==0.19.2" }, "types-cachetools": { "hashes": [ - "sha256:67fa46d51a650896770aee0ba80f0e61dc4a7d1373198eec1bc0622263eaa256", - "sha256:c0c5fa00199017d974c935bf043c467d5204e4f835141e489b48765b5ac1d960" + "sha256:595f0342d246c8ba534f5a762cf4c2f60ecb61e8002b8b2277fd5cf791d4e851", + "sha256:f7f8a25bfe306f2e6bc2ad0a2f949d9e72f2d91036d509c36d3810bf728bc6e1" ], "index": "pypi", - "version": "==5.3.0.5" + "version": "==5.3.0.6" }, "types-pytz": { "hashes": [ - "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3", - "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac" + "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf", + "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a" ], - "version": "==2023.3.0.0" + "version": "==2023.3.1.1" }, "types-pyyaml": { "hashes": [ - "sha256:662fa444963eff9b68120d70cda1af5a5f2aa57900003c2006d7626450eaae5f", - "sha256:ebab3d0700b946553724ae6ca636ea932c1b0868701d4af121630e78d695fc97" + "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062", + "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24" ], - "version": "==6.0.12.10" + "version": "==6.0.12.12" }, "types-requests": { "hashes": [ - "sha256:3de667cffa123ce698591de0ad7db034a5317457a596eb0b4944e5a9d9e8d1ac", - "sha256:afb06ef8f25ba83d59a1d424bd7a5a939082f94b94e90ab5e6116bd2559deaa3" + "sha256:39894cbca3fb3d032ed8bdd02275b4273471aa5668564617cc1734b0a65ffdf8", + "sha256:e1b325c687b3494a2f528ab06e411d7092cc546cc9245c000bacc2fca5ae96d4" ], "index": "pypi", - "version": "==2.31.0.1" + "markers": "python_version >= '3.7'", + "version": "==2.31.0.8" }, "types-s3transfer": { "hashes": [ - "sha256:6d1ac1dedac750d570428362acdf60fdd4f277b0788855c3894d3226756b2bfb", - "sha256:75ac1d7143d58c1e6af467cfd4a96c67ee058a3adf7c249d9309999e1f5f41e4" + "sha256:aca0f2486d0a3a5037cd5b8f3e20a4522a29579a8dd183281ff0aa1c4e2c8aa7", + "sha256:ae9ed9273465d9f43da8b96307383da410c6b59c3b2464c88d20b578768e97c6" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.6.1" - }, - "types-urllib3": { - "hashes": [ - "sha256:3300538c9dc11dad32eae4827ac313f5d986b8b21494801f1bf97a1ac6c03ae5", - "sha256:5dbd1d2bef14efee43f5318b5d36d805a489f6600252bb53626d4bfafd95e27c" - ], - "version": "==1.26.25.13" + "version": "==0.7.0" }, "typing-extensions": { "hashes": [ - "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26", - "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5" + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" ], "index": "pypi", - "version": "==4.6.3" + "markers": "python_version >= '3.8'", + "version": "==4.8.0" }, "urllib3": { "hashes": [ - "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f", - "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14" + "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", + "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.16" + "markers": "python_version >= '3.7'", + "version": "==2.0.6" }, "waitress": { "hashes": [ diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 0234ef6c6..a9ea30ecb 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -3,6 +3,8 @@ import logging from time import sleep +from epplibwrapper.utility.pool import EppConnectionPool + try: from epplib.client import Client from epplib import commands @@ -63,13 +65,22 @@ def __init__(self) -> None: # prepare a context manager which will connect and login when invoked # (it will also logout and disconnect when the context manager exits) self._connect = Socket(self._client, self._login) + options = { + # Pool size + "size": 10, + # Which errors the pool should look out for + "exc_classes": (LoginError, RegistryError,), + # Should we ping the connection on occassion to keep it alive? + "keep_alive": None, + } + self._pool = EppConnectionPool(client=self._client, login=self._login, options=options) def _send(self, command): """Helper function used by `send`.""" try: cmd_type = command.__class__.__name__ - with self._connect as wire: - response = wire.send(command) + with self._pool.get() as connection: + response = connection.send(command) except (ValueError, ParsingError) as err: message = "%s failed to execute due to some syntax error." logger.warning(message, cmd_type, exc_info=True) diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 5c9acce79..703ac9538 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -20,6 +20,14 @@ def __init__(self, client, login) -> None: self.login = login def __enter__(self): + """Runs connect(), which opens a connection with EPPLib.""" + self.connect() + + def __exit__(self, *args, **kwargs): + """Runs disconnect(), which closes a connection with EPPLib.""" + self.disconnect() + + def connect(self): """Use epplib to connect.""" self.client.connect() response = self.client.send(self.login) @@ -27,11 +35,22 @@ def __enter__(self): self.client.close() raise LoginError(response.msg) return self.client - - def __exit__(self, *args, **kwargs): + + def disconnect(self): """Close the connection.""" try: self.client.send(commands.Logout()) self.client.close() except Exception: logger.warning("Connection to registry was not cleanly closed.") + + def send(self, command): + logger.debug(f"command is this: {command}") + response = self.client.send(command) + # TODO - add some validation + """ + if response.code >= 2000: + self.client.close() + raise LoginError(response.msg) + """ + return response \ No newline at end of file diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py new file mode 100644 index 000000000..6773bc312 --- /dev/null +++ b/src/epplibwrapper/utility/pool.py @@ -0,0 +1,25 @@ +from geventconnpool import ConnectionPool +from epplibwrapper.socket import Socket + +class EppConnectionPool(ConnectionPool): + def __init__(self, client, login, options): + # For storing shared credentials + self._client = client + self._login = login + super().__init__(**options) + + def _new_connection(self): + socket = self.create_socket(self._client, self._login) + try: + connection = socket.connect() + return connection + except Exception as err: + raise err + + def _keepalive(self, connection): + pass + + def create_socket(self, client, login) -> Socket: + """Creates and returns a socket instance""" + socket = Socket(client, login) + return socket \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt index ae6ed90df..8617b6472 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,53 +1,61 @@ -i https://pypi.python.org/simple -asgiref==3.7.2 ; python_version >= '3.7' -boto3==1.26.145 -botocore==1.29.145 ; python_version >= '3.7' -cachetools==5.3.1 -certifi==2023.7.22 ; python_version >= '3.6' +annotated-types==0.6.0; python_version >= '3.8' +asgiref==3.7.2; python_version >= '3.7' +boto3==1.28.62; python_version >= '3.7' +botocore==1.31.62; python_version >= '3.7' +cachetools==5.3.1; python_version >= '3.7' +certifi==2023.7.22; python_version >= '3.6' cfenv==0.5.3 -cffi==1.15.1 -charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' -cryptography==41.0.4 ; python_version >= '3.7' -defusedxml==0.7.1 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -dj-database-url==2.0.0 +cffi==1.16.0; python_version >= '3.8' +charset-normalizer==3.3.0; python_full_version >= '3.7.0' +cryptography==41.0.4; python_version >= '3.7' +defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +dj-database-url==2.1.0 dj-email-url==1.0.6 -django==4.2.3 -django-allow-cidr==0.6.0 -django-auditlog==2.3.0 +django==4.2.6; python_version >= '3.8' +django-allow-cidr==0.7.1 +django-auditlog==2.3.0; python_version >= '3.7' django-cache-url==3.4.4 django-csp==3.7 django-fsm==2.8.1 django-login-required-middleware==0.9.0 -django-phonenumber-field[phonenumberslite]==7.1.0 -django-widget-tweaks==1.4.12 -environs[django]==9.5.0 -faker==18.10.0 -git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c#egg=fred-epplib +django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8' +django-widget-tweaks==1.5.0; python_version >= '3.8' +environs[django]==9.5.0; python_version >= '3.6' +faker==19.9.0; python_version >= '3.8' +fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c furl==2.1.3 -future==0.18.3 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' -gunicorn==20.1.0 -idna==3.4 ; python_version >= '3.5' -jmespath==1.0.1 ; python_version >= '3.7' -lxml==4.9.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -mako==1.2.4 ; python_version >= '3.7' -markupsafe==2.1.2 ; python_version >= '3.7' -marshmallow==3.19.0 ; python_version >= '3.7' -oic==1.6.0 +future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' +gevent==23.9.1; python_version >= '3.8' +greenlet==3.0.0; python_version < '3.11' and platform_python_implementation == 'CPython' +gsocketpool==0.1.6 +gunicorn==21.2.0; python_version >= '3.5' +idna==3.4; python_version >= '3.5' +jmespath==1.0.1; python_version >= '3.7' +lxml==4.9.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +mako==1.2.4; python_version >= '3.7' +markupsafe==2.1.3; python_version >= '3.7' +marshmallow==3.20.1; python_version >= '3.8' +oic==1.6.1; python_version ~= '3.7' orderedmultidict==1.0.1 -packaging==23.1 ; python_version >= '3.7' -phonenumberslite==8.13.13 -psycopg2-binary==2.9.6 +packaging==23.2; python_version >= '3.7' +phonenumberslite==8.13.22 +psycopg2-binary==2.9.9; python_version >= '3.7' pycparser==2.21 -pycryptodomex==3.18.0 -pydantic==1.10.8 ; python_version >= '3.7' +pycryptodomex==3.19.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +pydantic==2.4.2; python_version >= '3.7' +pydantic-core==2.10.1; python_version >= '3.7' +pydantic-settings==2.0.3; python_version >= '3.7' pyjwkest==1.4.2 -python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -python-dotenv==1.0.0 ; python_version >= '3.8' -requests==2.31.0 -s3transfer==0.6.1 ; python_version >= '3.7' -setuptools==67.8.0 ; python_version >= '3.7' -six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -sqlparse==0.4.4 ; python_version >= '3.5' -typing-extensions==4.6.3 -urllib3==1.26.17 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' -whitenoise==6.4.0 +python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +python-dotenv==1.0.0; python_version >= '3.8' +requests==2.31.0; python_version >= '3.7' +s3transfer==0.7.0; python_version >= '3.7' +setuptools==68.2.2; python_version >= '3.8' +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +sqlparse==0.4.4; python_version >= '3.5' +typing-extensions==4.8.0; python_version >= '3.8' +urllib3==2.0.6; python_version >= '3.7' +whitenoise==6.6.0; python_version >= '3.8' +zope.event==5.0; python_version >= '3.7' +zope.interface==6.1; python_version >= '3.7' From f22d72da4de9b67dcc91f097d42e37d95a34f783 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:48:37 -0600 Subject: [PATCH 02/65] Imports --- src/Pipfile | 2 +- src/Pipfile.lock | 19 ++++++++----------- src/epplibwrapper/client.py | 2 +- src/requirements.txt | 4 ++-- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 5f84ac448..8864248c1 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -26,7 +26,7 @@ boto3 = "*" typing-extensions ='*' django-login-required-middleware = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} -gsocketpool = "*" +geventconnpool = {git = "https://github.com/zandercymatics/geventconnpool.git", ref = "master"} [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index e01528f1d..6124b7096 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "49cd54bd0c272b04889898edc62b2a314d9675409d862a93e257b3f79e09f84e" + "sha256": "f9d7900daf9ca6d77fc9fe29c79b7620040cdaa50a34401bb2f84cef0862189e" }, "pipfile-spec": 6, "requires": {}, @@ -366,11 +366,11 @@ }, "faker": { "hashes": [ - "sha256:85468e16d4a9a8712bfdb98ba55aaf17c60658266a76958d099aee6a18c0a6c5", - "sha256:d75401c631a991b32d3595f26250f42c007cc32653ac3e522b626f3d80770571" + "sha256:63da90512d0cb3acdb71bd833bb3071cb8a196020d08b8567a01d232954f1820", + "sha256:f321e657ed61616fbfe14dbb9ccc6b2e8282652bbcfcb503c1bd0231ff834df6" ], "markers": "python_version >= '3.8'", - "version": "==19.9.0" + "version": "==19.10.0" }, "fred-epplib": { "git": "https://github.com/cisagov/epplib.git", @@ -436,6 +436,10 @@ "markers": "python_version >= '3.8'", "version": "==23.9.1" }, + "geventconnpool": { + "git": "https://github.com/zandercymatics/geventconnpool.git", + "ref": "e4f349117875fa70b1034b89c3bc7caafac6a9b4" + }, "greenlet": { "hashes": [ "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a", @@ -504,13 +508,6 @@ "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'", "version": "==3.0.0" }, - "gsocketpool": { - "hashes": [ - "sha256:f2e2749aceadce6b27ca52e2b0a64af99797746a8681e1a2963f72007c14cb14" - ], - "index": "pypi", - "version": "==0.1.6" - }, "gunicorn": { "hashes": [ "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0", diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index a9ea30ecb..fc5455f84 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -71,7 +71,7 @@ def __init__(self) -> None: # Which errors the pool should look out for "exc_classes": (LoginError, RegistryError,), # Should we ping the connection on occassion to keep it alive? - "keep_alive": None, + "keepalive": None, } self._pool = EppConnectionPool(client=self._client, login=self._login, options=options) diff --git a/src/requirements.txt b/src/requirements.txt index 8617b6472..2c76aca02 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -22,13 +22,13 @@ django-login-required-middleware==0.9.0 django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8' django-widget-tweaks==1.5.0; python_version >= '3.8' environs[django]==9.5.0; python_version >= '3.6' -faker==19.9.0; python_version >= '3.8' +faker==19.10.0; python_version >= '3.8' fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c furl==2.1.3 future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' gevent==23.9.1; python_version >= '3.8' +geventconnpool@ git+https://github.com/zandercymatics/geventconnpool.git@e4f349117875fa70b1034b89c3bc7caafac6a9b4 greenlet==3.0.0; python_version < '3.11' and platform_python_implementation == 'CPython' -gsocketpool==0.1.6 gunicorn==21.2.0; python_version >= '3.5' idna==3.4; python_version >= '3.5' jmespath==1.0.1; python_version >= '3.7' From a02d27aaecfc9126f55813064468dcbe86a843ca Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:31:13 -0600 Subject: [PATCH 03/65] More specific error returns --- src/epplibwrapper/client.py | 5 +---- src/epplibwrapper/utility/pool.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index fc5455f84..bd1740ec9 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -62,15 +62,12 @@ def __init__(self) -> None: password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, ) ) - # prepare a context manager which will connect and login when invoked - # (it will also logout and disconnect when the context manager exits) - self._connect = Socket(self._client, self._login) options = { # Pool size "size": 10, # Which errors the pool should look out for "exc_classes": (LoginError, RegistryError,), - # Should we ping the connection on occassion to keep it alive? + # Should we ping the connection on occasion to keep it alive? "keepalive": None, } self._pool = EppConnectionPool(client=self._client, login=self._login, options=options) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 6773bc312..6d5ab5d58 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,6 +1,11 @@ +import logging from geventconnpool import ConnectionPool +from epplibwrapper import RegistryError +from epplibwrapper.errors import LoginError from epplibwrapper.socket import Socket +logger = logging.getLogger(__name__) + class EppConnectionPool(ConnectionPool): def __init__(self, client, login, options): # For storing shared credentials @@ -13,8 +18,10 @@ def _new_connection(self): try: connection = socket.connect() return connection - except Exception as err: - raise err + except LoginError as err: + message = "_new_connection failed to execute due to a registry login error." + logger.warning(message, exc_info=True) + raise RegistryError(message) from err def _keepalive(self, connection): pass From 3a28a3a3625f526b7872d25aec3f237a17e2716a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:16:22 -0600 Subject: [PATCH 04/65] Add settings for pooling, keep_alive --- src/epplibwrapper/client.py | 7 +++---- src/epplibwrapper/utility/pool.py | 20 +++++++++++++++----- src/registrar/config/settings.py | 10 ++++++++++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index bd1740ec9..8cdb85fad 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -17,7 +17,6 @@ from .cert import Cert, Key from .errors import LoginError, RegistryError -from .socket import Socket logger = logging.getLogger(__name__) @@ -64,11 +63,11 @@ def __init__(self) -> None: ) options = { # Pool size - "size": 10, + "size": settings.EPP_CONNECTION_POOL_SIZE, # Which errors the pool should look out for "exc_classes": (LoginError, RegistryError,), - # Should we ping the connection on occasion to keep it alive? - "keepalive": None, + # Occasionally pings the registry to keep the connection alive + "keepalive": settings.POOL_KEEP_ALIVE, } self._pool = EppConnectionPool(client=self._client, login=self._login, options=options) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 6d5ab5d58..2ad9f82c2 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,9 +1,13 @@ import logging from geventconnpool import ConnectionPool -from epplibwrapper import RegistryError -from epplibwrapper.errors import LoginError +from epplibwrapper.errors import RegistryError, LoginError from epplibwrapper.socket import Socket +try: + from epplib.commands import Hello +except ImportError: + pass + logger = logging.getLogger(__name__) class EppConnectionPool(ConnectionPool): @@ -20,11 +24,17 @@ def _new_connection(self): return connection except LoginError as err: message = "_new_connection failed to execute due to a registry login error." - logger.warning(message, exc_info=True) + logger.error(message, exc_info=True) raise RegistryError(message) from err - def _keepalive(self, connection): - pass + def _keepalive(self, c): + """Sends a command to the server to keep the connection alive.""" + try: + # Sends a ping to EPPLib + c.send(Hello()) + except Exception as err: + logger.error("Failed to keep the connection alive.", exc_info=True) + raise RegistryError("Failed to keep the connection alive.") from err def create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index ceb215a4d..ee3b496bf 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -534,6 +534,16 @@ SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase SECRET_REGISTRY_HOSTNAME = secret_registry_hostname +# endregion +# region: Registry Connection Pool----------------------------------------------------------### + +# Use this variable to set the size of our connection pool in client.py +# WARNING: Setting this value too high could cause frequent app crashes! +EPP_CONNECTION_POOL_SIZE = 10 + +# Determines if we should ping open connections +POOL_KEEP_ALIVE = True + # endregion # region: Security and Privacy----------------------------------------------### From 542554959ea0089f65b512c1c95d2ed03bde19d4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:21:56 -0600 Subject: [PATCH 05/65] Run black linter --- src/epplibwrapper/client.py | 11 ++++++++--- src/epplibwrapper/socket.py | 6 +++--- src/epplibwrapper/utility/pool.py | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 8cdb85fad..66d3d696c 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -65,11 +65,16 @@ def __init__(self) -> None: # Pool size "size": settings.EPP_CONNECTION_POOL_SIZE, # Which errors the pool should look out for - "exc_classes": (LoginError, RegistryError,), - # Occasionally pings the registry to keep the connection alive + "exc_classes": ( + LoginError, + RegistryError, + ), + # Occasionally pings the registry to keep the connection alive "keepalive": settings.POOL_KEEP_ALIVE, } - self._pool = EppConnectionPool(client=self._client, login=self._login, options=options) + self._pool = EppConnectionPool( + client=self._client, login=self._login, options=options + ) def _send(self, command): """Helper function used by `send`.""" diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 703ac9538..d66589495 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -35,7 +35,7 @@ def connect(self): self.client.close() raise LoginError(response.msg) return self.client - + def disconnect(self): """Close the connection.""" try: @@ -43,7 +43,7 @@ def disconnect(self): self.client.close() except Exception: logger.warning("Connection to registry was not cleanly closed.") - + def send(self, command): logger.debug(f"command is this: {command}") response = self.client.send(command) @@ -53,4 +53,4 @@ def send(self, command): self.client.close() raise LoginError(response.msg) """ - return response \ No newline at end of file + return response diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 2ad9f82c2..6682f3bf6 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) + class EppConnectionPool(ConnectionPool): def __init__(self, client, login, options): # For storing shared credentials @@ -35,8 +36,8 @@ def _keepalive(self, c): except Exception as err: logger.error("Failed to keep the connection alive.", exc_info=True) raise RegistryError("Failed to keep the connection alive.") from err - + def create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" socket = Socket(client, login) - return socket \ No newline at end of file + return socket From 94a85aeea1a75f8f60ab3c475df2239057458eb5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:27:23 -0600 Subject: [PATCH 06/65] Linter changes --- src/registrar/config/settings.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index ee3b496bf..e153e32d5 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -534,9 +534,6 @@ SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase SECRET_REGISTRY_HOSTNAME = secret_registry_hostname -# endregion -# region: Registry Connection Pool----------------------------------------------------------### - # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! EPP_CONNECTION_POOL_SIZE = 10 From aeb3c73d3d649e3a116f9a5cd6761d440d71998a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:35:06 -0600 Subject: [PATCH 07/65] Increase alive interval --- src/registrar/config/settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index e153e32d5..0097c1f84 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -538,8 +538,9 @@ # WARNING: Setting this value too high could cause frequent app crashes! EPP_CONNECTION_POOL_SIZE = 10 -# Determines if we should ping open connections -POOL_KEEP_ALIVE = True +# Determines the interval in which we ping open connections in seconds +# Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE +POOL_KEEP_ALIVE = 600 # endregion # region: Security and Privacy----------------------------------------------### From a82f3c5ce963dbca01a1a1151902e7fd05981fb1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:48:48 -0600 Subject: [PATCH 08/65] Disable keep_alive --- src/registrar/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 0097c1f84..cbde49ba2 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -540,7 +540,7 @@ # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -POOL_KEEP_ALIVE = 600 +POOL_KEEP_ALIVE = None # endregion # region: Security and Privacy----------------------------------------------### From c4d2950ac957f675a7f853df7763c3e306922d6c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:07:05 -0600 Subject: [PATCH 09/65] Fix pool locally, cleanup --- src/epplibwrapper/client.py | 28 +++++++++++++++++----------- src/epplibwrapper/socket.py | 5 +---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 66d3d696c..f43ee41e4 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -3,8 +3,6 @@ import logging from time import sleep -from epplibwrapper.utility.pool import EppConnectionPool - try: from epplib.client import Client from epplib import commands @@ -17,6 +15,7 @@ from .cert import Cert, Key from .errors import LoginError, RegistryError +from .utility.pool import EppConnectionPool logger = logging.getLogger(__name__) @@ -78,25 +77,32 @@ def __init__(self) -> None: def _send(self, command): """Helper function used by `send`.""" + cmd_type = command.__class__.__name__ try: - cmd_type = command.__class__.__name__ + # We won't have an EPP connection locally, + # shortcut this and raise an err + # TODO - implement a timeout in _pool.get() + if settings.DEBUG: + raise LoginError with self._pool.get() as connection: response = connection.send(command) except (ValueError, ParsingError) as err: - message = "%s failed to execute due to some syntax error." - logger.warning(message, cmd_type, exc_info=True) + message = f"{cmd_type} failed to execute due to some syntax error." + logger.warning(message, exc_info=True) raise RegistryError(message) from err except TransportError as err: - message = "%s failed to execute due to a connection error." - logger.warning(message, cmd_type, exc_info=True) + message = f"{cmd_type} failed to execute due to a connection error." + logger.warning(message, exc_info=True) raise RegistryError(message) from err except LoginError as err: - message = "%s failed to execute due to a registry login error." - logger.warning(message, cmd_type, exc_info=True) + # For linter + text = "failed to execute due to a registry login error." + message = f"{cmd_type} {text}" + logger.warning(message, exc_info=True) raise RegistryError(message) from err except Exception as err: - message = "%s failed to execute due to an unknown error." % err - logger.warning(message, cmd_type, exc_info=True) + message = f"{cmd_type} failed to execute due to an unknown error." + logger.warning(message, exc_info=True) raise RegistryError(message) from err else: if response.code >= 2000: diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index d66589495..33db76a73 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -45,12 +45,9 @@ def disconnect(self): logger.warning("Connection to registry was not cleanly closed.") def send(self, command): - logger.debug(f"command is this: {command}") response = self.client.send(command) - # TODO - add some validation - """ if response.code >= 2000: self.client.close() raise LoginError(response.msg) - """ + return response From dfec8c200e61da5560dd77a915ee2443e6960dd5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:38:49 -0600 Subject: [PATCH 10/65] Check if registry connection can be made --- src/epplibwrapper/client.py | 33 +++++++++++++++++++++++++-------- src/epplibwrapper/socket.py | 32 +++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index f43ee41e4..1bbda80e7 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -15,6 +15,7 @@ from .cert import Cert, Key from .errors import LoginError, RegistryError +from .socket import Socket from .utility.pool import EppConnectionPool logger = logging.getLogger(__name__) @@ -41,7 +42,6 @@ class EPPLibWrapper: def __init__(self) -> None: """Initialize settings which will be used for all connections.""" - # prepare (but do not send) a Login command self._login = commands.Login( cl_id=settings.SECRET_REGISTRY_CL_ID, @@ -51,6 +51,7 @@ def __init__(self) -> None: "urn:ietf:params:xml:ns:contact-1.0", ], ) + # establish a client object with a TCP socket transport self._client = Client( SocketTransport( @@ -71,19 +72,23 @@ def __init__(self) -> None: # Occasionally pings the registry to keep the connection alive "keepalive": settings.POOL_KEEP_ALIVE, } - self._pool = EppConnectionPool( - client=self._client, login=self._login, options=options - ) + + self._pool = None + if not settings.DEBUG or self._test_registry_connection_success(): + self._pool = EppConnectionPool( + client=self._client, login=self._login, options=options + ) + else: + logger.warning("Cannot contact the Registry") + # TODO - signal that the app may need to restart? def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ try: - # We won't have an EPP connection locally, - # shortcut this and raise an err - # TODO - implement a timeout in _pool.get() - if settings.DEBUG: + if self._pool is None: raise LoginError + # TODO - add a timeout with self._pool.get() as connection: response = connection.send(command) except (ValueError, ParsingError) as err: @@ -127,6 +132,18 @@ def send(self, command, *, cleaned=False): else: # don't try again raise err + def _test_registry_connection_success(self): + """Check that determines if our login + credentials are valid, and/or if the Registrar + can be contacted + """ + socket = Socket(self._login, self._client) + can_login = False + # Something went wrong if this doesn't exist + if hasattr(socket, "test_connection_success"): + can_login = socket.test_connection_success() + return can_login + try: # Initialize epplib diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 33db76a73..d25d823f1 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -1,7 +1,9 @@ import logging +from time import sleep try: from epplib import commands + from epplib.client import Client except ImportError: pass @@ -14,7 +16,7 @@ class Socket: """Context manager which establishes a TCP connection with registry.""" - def __init__(self, client, login) -> None: + def __init__(self, client: commands.Login, login: Client) -> None: """Save the epplib client and login details.""" self.client = client self.login = login @@ -27,15 +29,39 @@ def __exit__(self, *args, **kwargs): """Runs disconnect(), which closes a connection with EPPLib.""" self.disconnect() - def connect(self): + def connect(self, pass_response_only=False): """Use epplib to connect.""" self.client.connect() response = self.client.send(self.login) - if response.code >= 2000: + if self.is_login_error(response.code): self.client.close() raise LoginError(response.msg) return self.client + def is_login_error(self, code): + return code >= 2000 + + def test_connection_success(self): + """Tests if a successful connection can be made with the registry""" + # Something went wrong if this doesn't exist + if not hasattr(self.client, "connect"): + return False + + counter = 0 # we'll try 3 times + while True: + try: + self.client.connect() + response = self.client.send(self.login) + except LoginError as err: + if err.should_retry() and counter < 3: + counter += 1 + sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms + else: # don't try again + return False + else: + self.disconnect() + return not self.is_login_error(response.code) + def disconnect(self): """Close the connection.""" try: From fa6ed6f31842490b666b04606a994b661a27c7b4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:55:01 -0600 Subject: [PATCH 11/65] Test fix for security scanner --- src/epplibwrapper/__init__.py | 3 +++ src/epplibwrapper/client.py | 2 ++ src/epplibwrapper/socket.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index dd6664a3a..5b3bdc55c 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -45,6 +45,9 @@ # Attn: these imports should NOT be at the top of the file try: from .client import CLIENT, commands +except ImportError: + pass +try: from .errors import RegistryError, ErrorCode from epplib.models import common, info from epplib.responses import extensions diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 1bbda80e7..3426dd486 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -74,6 +74,8 @@ def __init__(self) -> None: } self._pool = None + # Since we reuse the same creds for each pool, we can test on + # one socket, and if successful, then we know we can connect. if not settings.DEBUG or self._test_registry_connection_success(): self._pool = EppConnectionPool( client=self._client, login=self._login, options=options diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index d25d823f1..c6ffe20bf 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -42,7 +42,8 @@ def is_login_error(self, code): return code >= 2000 def test_connection_success(self): - """Tests if a successful connection can be made with the registry""" + """Tests if a successful connection can be made with the registry. + Tries 3 times""" # Something went wrong if this doesn't exist if not hasattr(self.client, "connect"): return False From a05d02bbfa56232e73ec2c716d75f338ac204a56 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:24:58 -0600 Subject: [PATCH 12/65] Expand exceptions --- src/epplibwrapper/__init__.py | 3 - src/epplibwrapper/client.py | 95 +++++++++++++++++++----- src/epplibwrapper/errors.py | 3 + src/epplibwrapper/utility/pool.py | 50 ++++++++++++- src/epplibwrapper/utility/pool_status.py | 6 ++ 5 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 src/epplibwrapper/utility/pool_status.py diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index 5b3bdc55c..dd6664a3a 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -45,9 +45,6 @@ # Attn: these imports should NOT be at the top of the file try: from .client import CLIENT, commands -except ImportError: - pass -try: from .errors import RegistryError, ErrorCode from epplib.models import common, info from epplib.responses import extensions diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 3426dd486..e38665e01 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -3,6 +3,8 @@ import logging from time import sleep +from epplibwrapper.utility.pool_status import PoolStatus + try: from epplib.client import Client from epplib import commands @@ -16,7 +18,7 @@ from .cert import Cert, Key from .errors import LoginError, RegistryError from .socket import Socket -from .utility.pool import EppConnectionPool +from .utility.pool import EPPConnectionPool logger = logging.getLogger(__name__) @@ -32,7 +34,6 @@ exc_info=True, ) - class EPPLibWrapper: """ A wrapper over epplib's client. @@ -61,35 +62,33 @@ def __init__(self) -> None: password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, ) ) - options = { + + self.pool_options = { # Pool size "size": settings.EPP_CONNECTION_POOL_SIZE, # Which errors the pool should look out for "exc_classes": ( - LoginError, - RegistryError, + TransportError, ), - # Occasionally pings the registry to keep the connection alive + # Occasionally pings the registry to keep the connection alive. + # Value in seconds => (keepalive / size) "keepalive": settings.POOL_KEEP_ALIVE, } self._pool = None - # Since we reuse the same creds for each pool, we can test on - # one socket, and if successful, then we know we can connect. - if not settings.DEBUG or self._test_registry_connection_success(): - self._pool = EppConnectionPool( - client=self._client, login=self._login, options=options - ) - else: - logger.warning("Cannot contact the Registry") - # TODO - signal that the app may need to restart? + # Tracks the status of the pool + self.pool_status = PoolStatus() + + self.start_connection_pool() def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ try: - if self._pool is None: - raise LoginError + if not self.pool_status.connection_success: + raise LoginError( + "Couldn't connect to the registry after three attempts" + ) # TODO - add a timeout with self._pool.get() as connection: response = connection.send(command) @@ -122,6 +121,21 @@ def send(self, command, *, cleaned=False): # try to prevent use of this method without appropriate safeguards if not cleaned: raise ValueError("Please sanitize user input before sending it.") + + # Reopen the pool if its closed + if not self.pool_status.pool_running: + # We want to reopen the connection pool, + # but we don't want the end user to wait while it opens. + # Raise syntax doesn't allow this, so we use a try/catch + # block. + try: + raise RegistryError( + "Can't contact the Registry. Please try again later" + ) + except RegistryError as err: + raise err + finally: + self.start_connection_pool() counter = 0 # we'll try 3 times while True: @@ -134,6 +148,53 @@ def send(self, command, *, cleaned=False): else: # don't try again raise err + def start_connection_pool(self, restart_pool_if_exists = True): + """Starts a connection pool for the registry. + + restart_pool_if_exists -> bool: + If an instance of the pool already exists, + then then that instance will be killed first. + It is generally recommended to keep this enabled.""" + + # Since we reuse the same creds for each pool, we can test on + # one socket, and if successful, then we know we can connect. + if settings.DEBUG or self._test_registry_connection_success(): + logger.warning("Cannot contact the Registry") + self.pool_status.connection_success = False + # Q: Should err be raised instead? + # Q2: Since we try to connect 3 times, + # this indicates that the Registry isn't responsive. + # What should we do in this case? + return + else: + self.pool_status.connection_success = True + + # If this function is reinvoked, then ensure + # that we don't have duplicate data sitting around. + if self._pool is not None and restart_pool_if_exists: + logger.info("Connection pool restarting...") + self.kill_pool() + + self._pool = EPPConnectionPool( + client=self._client, login=self._login, options=self.pool_options + ) + self.pool_status.pool_running = True + + logger.info("Connection pool started") + + def kill_pool(self): + """Kills the existing pool. Use this instead + of self._pool = None, as that doesn't clear + gevent instances.""" + if self._pool is not None: + self._pool.kill_all_connections() + self._pool = None + self.pool_status.pool_running = False + return + logger.info( + "kill_pool() was invoked but there was no pool to delete" + ) + def _test_registry_connection_success(self): """Check that determines if our login credentials are valid, and/or if the Registrar diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index d34ed5e91..91c8721d8 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -79,6 +79,9 @@ def is_server_error(self): def is_client_error(self): return self.code is not None and (self.code >= 2000 and self.code <= 2308) + + def is_not_retryable(self): + pass class LoginError(RegistryError): diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 6682f3bf6..3784f3fbc 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,4 +1,6 @@ +from collections import deque import logging +import gevent from geventconnpool import ConnectionPool from epplibwrapper.errors import RegistryError, LoginError from epplibwrapper.socket import Socket @@ -11,15 +13,23 @@ logger = logging.getLogger(__name__) -class EppConnectionPool(ConnectionPool): - def __init__(self, client, login, options): +class EPPConnectionPool(ConnectionPool): + """A connection pool for EPPLib. + + Args: + client (Client): The client + login (commands.Login): Login creds + options (dict): Options for the ConnectionPool + base class + """ + def __init__(self, client, login, options: dict): # For storing shared credentials self._client = client self._login = login super().__init__(**options) def _new_connection(self): - socket = self.create_socket(self._client, self._login) + socket = self._create_socket(self._client, self._login) try: connection = socket.connect() return connection @@ -37,7 +47,39 @@ def _keepalive(self, c): logger.error("Failed to keep the connection alive.", exc_info=True) raise RegistryError("Failed to keep the connection alive.") from err - def create_socket(self, client, login) -> Socket: + def _create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" socket = Socket(client, login) return socket + + def get_connections(self): + """Returns the connection queue""" + return self.conn + + def kill_all_connections(self): + """Kills all active connections in the pool.""" + try: + gevent.killall(self.conn) + self.conn.clear() + # Clear the semaphore + for i in range(self.lock.counter): + self.lock.release() + # TODO - connection pool err + except Exception as err: + logger.error( + "Could not kill all connections." + ) + raise err + + def repopulate_all_connections(self): + """Regenerates the connection pool. + If any connections exist, kill them first. + """ + if len(self.conn) > 0: + self.kill_all_connections() + for i in range(self.size): + self.lock.acquire() + for i in range(self.size): + gevent.spawn_later(self.SPAWN_FREQUENCY*i, self._addOne) + + diff --git a/src/epplibwrapper/utility/pool_status.py b/src/epplibwrapper/utility/pool_status.py new file mode 100644 index 000000000..82c032941 --- /dev/null +++ b/src/epplibwrapper/utility/pool_status.py @@ -0,0 +1,6 @@ +class PoolStatus: + """A list of Booleans to keep track of Pool Status""" + def __init__(self): + self.pool_running = False + self.connection_success = False + self.pool_hanging = False From 4cdf9794df7dd754a4c653071425215cada48fc7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:35:36 -0600 Subject: [PATCH 13/65] Test pool size of 1 --- src/registrar/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index cbde49ba2..e10c5fc13 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -536,7 +536,7 @@ # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! -EPP_CONNECTION_POOL_SIZE = 10 +EPP_CONNECTION_POOL_SIZE = 1 # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE From f6d287cf219e724a420413f361ea45745fb01ee3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:45:02 -0600 Subject: [PATCH 14/65] Setting tinkernig --- src/registrar/config/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index e10c5fc13..9ec3d0a5c 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -536,11 +536,11 @@ # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! -EPP_CONNECTION_POOL_SIZE = 1 +EPP_CONNECTION_POOL_SIZE = 3 # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -POOL_KEEP_ALIVE = None +POOL_KEEP_ALIVE = 60 # Ping every 20 seconds # endregion # region: Security and Privacy----------------------------------------------### From c165fd8401f606dfac40ba0af070e0d8edebfef3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:56:31 -0600 Subject: [PATCH 15/65] Update settings.py --- src/registrar/config/settings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 9ec3d0a5c..fe7ff57c4 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -534,9 +534,15 @@ SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase SECRET_REGISTRY_HOSTNAME = secret_registry_hostname +# Question for reviewers: For one client, the performance difference +# between a pool of size 1 vs a pool of size 10 isn't noticeable. +# The main performance increase comes from an open connection. +# We would need to do load testing to determine the ideal number, +# my recommendation now would be 3 as it is a good balance between +# overhead vs capacity. # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! -EPP_CONNECTION_POOL_SIZE = 3 +EPP_CONNECTION_POOL_SIZE = 1 # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE From 284b7ceadfbfecd4912605c525f967589fca68ce Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:13:51 -0600 Subject: [PATCH 16/65] Update piplock --- src/Pipfile | 2 +- src/Pipfile.lock | 54 ++++++++++++++++++++++---------------------- src/requirements.txt | 6 ++--- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 8864248c1..377252e05 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -26,7 +26,7 @@ boto3 = "*" typing-extensions ='*' django-login-required-middleware = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} -geventconnpool = {git = "https://github.com/zandercymatics/geventconnpool.git", ref = "master"} +geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"} [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 6124b7096..c8ca68d5c 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f9d7900daf9ca6d77fc9fe29c79b7620040cdaa50a34401bb2f84cef0862189e" + "sha256": "a3a996c98e72cee37bc89a3b95fab6ae4b396d5663eb4fe66a80684154bc90e0" }, "pipfile-spec": 6, "requires": {}, @@ -32,20 +32,20 @@ }, "boto3": { "hashes": [ - "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2", - "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0" + "sha256:65d052ec13197460586ee385aa2d6bba0e7378d2d2c7f3e93c044c43ae1ca782", + "sha256:94218aba2feb5b404b665b8d76c172dc654f79b4c5fa0e9e92459c098da87bf4" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.62" + "version": "==1.28.63" }, "botocore": { "hashes": [ - "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c", - "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7" + "sha256:6e582c811ea74f25bdb490ac372b2645de4a60286b42ddd8c69f3b6df82b6b12", + "sha256:cb9db5db5af865b1fc2e1405b967db5d78dd0f4d84e5dc1974e082733c1034b7" ], "markers": "python_version >= '3.7'", - "version": "==1.31.62" + "version": "==1.31.63" }, "cachetools": { "hashes": [ @@ -437,8 +437,8 @@ "version": "==23.9.1" }, "geventconnpool": { - "git": "https://github.com/zandercymatics/geventconnpool.git", - "ref": "e4f349117875fa70b1034b89c3bc7caafac6a9b4" + "git": "https://github.com/rasky/geventconnpool.git", + "ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4" }, "greenlet": { "hashes": [ @@ -1200,12 +1200,12 @@ }, "boto3": { "hashes": [ - "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2", - "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0" + "sha256:65d052ec13197460586ee385aa2d6bba0e7378d2d2c7f3e93c044c43ae1ca782", + "sha256:94218aba2feb5b404b665b8d76c172dc654f79b4c5fa0e9e92459c098da87bf4" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.62" + "version": "==1.28.63" }, "boto3-mocking": { "hashes": [ @@ -1218,28 +1218,28 @@ }, "boto3-stubs": { "hashes": [ - "sha256:22a08e27d2ede1849dd0d75e8501099240b34bd70adb606584a2af2e12f3a22b", - "sha256:f5ae08d2abae7709fff3e7cacea66c41cb43236527cfaf3975e506c6c67439a0" + "sha256:bd1becb0f8781d0a3022261a41d33f757a121117bf84ea6476b4761cb9e3cfd5", + "sha256:ecf4fb2b5b71be52cfc970ee059fe17439ed1904d0395508f5545c380d4d951d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.62" + "version": "==1.28.63" }, "botocore": { "hashes": [ - "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c", - "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7" + "sha256:6e582c811ea74f25bdb490ac372b2645de4a60286b42ddd8c69f3b6df82b6b12", + "sha256:cb9db5db5af865b1fc2e1405b967db5d78dd0f4d84e5dc1974e082733c1034b7" ], "markers": "python_version >= '3.7'", - "version": "==1.31.62" + "version": "==1.31.63" }, "botocore-stubs": { "hashes": [ - "sha256:2ce555e5dff2e91fc22bd67106534bf3e0593b838d87f8a49d3b8e87fa83a440", - "sha256:d30217d8f6a0888616a44c83150490c5fbc899550ffe1896a2cd15a2205fd648" + "sha256:873715a5c21d0f4593628393c78e47cf94e53a43e40863a9ef5f165fcdcf900f", + "sha256:e92b5bd8d2667e557ea25025b396613c9bcb33d18b1971f98ebc24fa54caf495" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.31.62" + "version": "==1.31.63" }, "click": { "hashes": [ @@ -1440,11 +1440,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0", - "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8" + "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f", + "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67" ], "markers": "python_version >= '3.8'", - "version": "==2.11.0" + "version": "==2.11.1" }, "pyflakes": { "hashes": [ @@ -1622,12 +1622,12 @@ }, "types-requests": { "hashes": [ - "sha256:39894cbca3fb3d032ed8bdd02275b4273471aa5668564617cc1734b0a65ffdf8", - "sha256:e1b325c687b3494a2f528ab06e411d7092cc546cc9245c000bacc2fca5ae96d4" + "sha256:140e323da742a0cd0ff0a5a83669da9ffcebfaeb855d367186b2ec3985ba2742", + "sha256:3bb11188795cc3aa39f9635032044ee771009370fb31c3a06ae952b267b6fcd7" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.31.0.8" + "version": "==2.31.0.9" }, "types-s3transfer": { "hashes": [ diff --git a/src/requirements.txt b/src/requirements.txt index 2c76aca02..0e3d41d87 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,8 +1,8 @@ -i https://pypi.python.org/simple annotated-types==0.6.0; python_version >= '3.8' asgiref==3.7.2; python_version >= '3.7' -boto3==1.28.62; python_version >= '3.7' -botocore==1.31.62; python_version >= '3.7' +boto3==1.28.63; python_version >= '3.7' +botocore==1.31.63; python_version >= '3.7' cachetools==5.3.1; python_version >= '3.7' certifi==2023.7.22; python_version >= '3.6' cfenv==0.5.3 @@ -27,7 +27,7 @@ fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a furl==2.1.3 future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' gevent==23.9.1; python_version >= '3.8' -geventconnpool@ git+https://github.com/zandercymatics/geventconnpool.git@e4f349117875fa70b1034b89c3bc7caafac6a9b4 +geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4 greenlet==3.0.0; python_version < '3.11' and platform_python_implementation == 'CPython' gunicorn==21.2.0; python_version >= '3.5' idna==3.4; python_version >= '3.5' From a9e4d340994d1b66d6d96a027540210df86e61aa Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:08:28 -0600 Subject: [PATCH 17/65] Tests / pool restart on freeze --- src/epplibwrapper/client.py | 16 +++- src/epplibwrapper/tests/__init__.py | 0 src/epplibwrapper/tests/test_pool.py | 132 +++++++++++++++++++++++++++ src/registrar/config/settings.py | 4 + 4 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 src/epplibwrapper/tests/__init__.py create mode 100644 src/epplibwrapper/tests/test_pool.py diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index e38665e01..b0d79c300 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -3,6 +3,8 @@ import logging from time import sleep +from gevent import Timeout + from epplibwrapper.utility.pool_status import PoolStatus try: @@ -84,14 +86,22 @@ def __init__(self) -> None: def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ + # Start a timeout to check if the pool is hanging + timeout = Timeout(settings.POOL_TIMEOUT) + timeout.start() try: if not self.pool_status.connection_success: raise LoginError( "Couldn't connect to the registry after three attempts" ) - # TODO - add a timeout with self._pool.get() as connection: response = connection.send(command) + except Timeout as t: + if t is timeout: + # Flag that the pool is frozen, + # then restart the pool. + self.pool_status.pool_hanging = True + self.start_connection_pool() except (ValueError, ParsingError) as err: message = f"{cmd_type} failed to execute due to some syntax error." logger.warning(message, exc_info=True) @@ -115,6 +125,8 @@ def _send(self, command): raise RegistryError(response.msg, code=response.code) else: return response + finally: + timeout.close() def send(self, command, *, cleaned=False): """Login, send the command, then close the connection. Tries 3 times.""" @@ -155,7 +167,6 @@ def start_connection_pool(self, restart_pool_if_exists = True): If an instance of the pool already exists, then then that instance will be killed first. It is generally recommended to keep this enabled.""" - # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. if settings.DEBUG or self._test_registry_connection_success(): @@ -179,6 +190,7 @@ def start_connection_pool(self, restart_pool_if_exists = True): client=self._client, login=self._login, options=self.pool_options ) self.pool_status.pool_running = True + self.pool_status.pool_hanging = False logger.info("Connection pool started") diff --git a/src/epplibwrapper/tests/__init__.py b/src/epplibwrapper/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py new file mode 100644 index 000000000..8752c9eab --- /dev/null +++ b/src/epplibwrapper/tests/test_pool.py @@ -0,0 +1,132 @@ +from unittest import skip +from unittest.mock import patch +from django.conf import settings + +from django.test import Client +from epplibwrapper.client import EPPLibWrapper +from registrar.tests.common import MockEppLib + +import logging + +try: + from epplib.client import Client + from epplib import commands + from epplib.exceptions import TransportError + from epplib.transport import SocketTransport +except ImportError: + pass + +logger = logging.getLogger(__name__) + + +@patch("djangooidc.views.CLIENT", autospec=True) +class TestConnectionPool(MockEppLib): + """Tests for our connection pooling behaviour""" + def setUp(self): + """ + Background: + Given the registrant is logged in + And the registrant is the admin on a domain + """ + super().setUp() + self.pool_options = { + # Current pool size + "size": 1, + # Which errors the pool should look out for + "exc_classes": ( + TransportError, + ), + # Occasionally pings the registry to keep the connection alive. + # Value in seconds => (keepalive / size) + "keepalive": 60, + } + + def tearDown(self): + super().tearDown() + + def user_info(*args): + return { + "sub": "TEST", + "email": "test@example.com", + "first_name": "Testy", + "last_name": "Tester", + "phone": "814564000", + } + + def test_pool_created_successfully(self, mock_client): + # setup + session = self.client.session + session["state"] = "TEST" # nosec B105 + session.save() + # mock + mock_client.callback.side_effect = self.user_info + + client = EPPLibWrapper() + pool = client._pool + + # These are defined outside of the pool, + # so we can reimplement how this is being done + # in client.py. They should remain unchanged, + # and if they aren't, something went wrong. + expected_login = commands.Login( + cl_id='nothing', + password='nothing', + obj_uris=[ + 'urn:ietf:params:xml:ns:domain-1.0', + 'urn:ietf:params:xml:ns:contact-1.0' + ], + new_pw=None, + version='1.0', + lang='en', + ext_uris=[] + ) + + # Key/cert will generate a new file everytime. + # This should never be null, so we can check for that. + try: + expected_client = Client( + SocketTransport( + settings.SECRET_REGISTRY_HOSTNAME, + cert_file=pool._client.transport.cert_file, + key_file=pool._client.transport.key_file, + password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, + ) + ).__dict__ + except Exception as err: + self.fail(err) + + # We don't care about checking if the objects are both of + # the same reference, we only care about data parity, so + # we do a dict conversion. + actual_client = pool._client.__dict__ + actual_client["transport"] = actual_client["transport"].__dict__ + expected_client["transport"] = expected_client["transport"].__dict__ + + # Ensure that we're getting the credentials we expect + self.assertEqual(pool._login, expected_login) + self.assertEqual(actual_client, expected_client) + + # Check that options are set correctly + self.assertEqual(pool.size, self.pool_options["size"]) + self.assertEqual(pool.keepalive, self.pool_options["keepalive"]) + self.assertEqual(pool.exc_classes, self.pool_options["exc_classes"]) + + # Check that it is running + self.assertEqual(client.pool_status.connection_success, True) + self.assertEqual(client.pool_status.pool_running, True) + + @skip("not implemented yet") + def test_pool_timesout(self): + """The pool timesout and restarts""" + raise + + @skip("not implemented yet") + def test_multiple_users_send_data(self): + """Multiple users send data concurrently""" + raise + + @skip("not implemented yet") + def test_pool_sends_data(self): + """A .send is invoked on the pool""" + raise + diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index fe7ff57c4..9d03dbc89 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -548,6 +548,10 @@ # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE POOL_KEEP_ALIVE = 60 # Ping every 20 seconds +# Determines how long we try to keep a pool alive for, +# before restarting it. +POOL_TIMEOUT = 60 + # endregion # region: Security and Privacy----------------------------------------------### From 7bb6bb9e7d9a20cc1669007a215a92c3e9c9774b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:33:11 -0600 Subject: [PATCH 18/65] Update pipfile --- src/Pipfile | 1 + src/Pipfile.lock | 3 ++- src/epplibwrapper/client.py | 25 +++++++++----------- src/epplibwrapper/errors.py | 2 +- src/epplibwrapper/tests/test_pool.py | 30 +++++++++++------------- src/epplibwrapper/utility/pool.py | 15 +++++------- src/epplibwrapper/utility/pool_status.py | 1 + 7 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 377252e05..43b919c08 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -25,6 +25,7 @@ django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"} boto3 = "*" typing-extensions ='*' django-login-required-middleware = "*" +gevent = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"} diff --git a/src/Pipfile.lock b/src/Pipfile.lock index c8ca68d5c..a7314de46 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a3a996c98e72cee37bc89a3b95fab6ae4b396d5663eb4fe66a80684154bc90e0" + "sha256": "67b51a57b7d9d7d70d1eeca3515e169cd614d575a7213f31251f9dde43e1f748" }, "pipfile-spec": 6, "requires": {}, @@ -433,6 +433,7 @@ "sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543", "sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==23.9.1" }, diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index b0d79c300..50c3c3a3e 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -36,6 +36,7 @@ exc_info=True, ) + class EPPLibWrapper: """ A wrapper over epplib's client. @@ -69,9 +70,7 @@ def __init__(self) -> None: # Pool size "size": settings.EPP_CONNECTION_POOL_SIZE, # Which errors the pool should look out for - "exc_classes": ( - TransportError, - ), + "exc_classes": (TransportError,), # Occasionally pings the registry to keep the connection alive. # Value in seconds => (keepalive / size) "keepalive": settings.POOL_KEEP_ALIVE, @@ -133,7 +132,7 @@ def send(self, command, *, cleaned=False): # try to prevent use of this method without appropriate safeguards if not cleaned: raise ValueError("Please sanitize user input before sending it.") - + # Reopen the pool if its closed if not self.pool_status.pool_running: # We want to reopen the connection pool, @@ -160,12 +159,12 @@ def send(self, command, *, cleaned=False): else: # don't try again raise err - def start_connection_pool(self, restart_pool_if_exists = True): - """Starts a connection pool for the registry. + def start_connection_pool(self, restart_pool_if_exists=True): + """Starts a connection pool for the registry. - restart_pool_if_exists -> bool: + restart_pool_if_exists -> bool: If an instance of the pool already exists, - then then that instance will be killed first. + then then that instance will be killed first. It is generally recommended to keep this enabled.""" # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. @@ -179,13 +178,13 @@ def start_connection_pool(self, restart_pool_if_exists = True): return else: self.pool_status.connection_success = True - + # If this function is reinvoked, then ensure # that we don't have duplicate data sitting around. if self._pool is not None and restart_pool_if_exists: logger.info("Connection pool restarting...") self.kill_pool() - + self._pool = EPPConnectionPool( client=self._client, login=self._login, options=self.pool_options ) @@ -193,7 +192,7 @@ def start_connection_pool(self, restart_pool_if_exists = True): self.pool_status.pool_hanging = False logger.info("Connection pool started") - + def kill_pool(self): """Kills the existing pool. Use this instead of self._pool = None, as that doesn't clear @@ -203,9 +202,7 @@ def kill_pool(self): self._pool = None self.pool_status.pool_running = False return - logger.info( - "kill_pool() was invoked but there was no pool to delete" - ) + logger.info("kill_pool() was invoked but there was no pool to delete") def _test_registry_connection_success(self): """Check that determines if our login diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index 91c8721d8..884b453fa 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -79,7 +79,7 @@ def is_server_error(self): def is_client_error(self): return self.code is not None and (self.code >= 2000 and self.code <= 2308) - + def is_not_retryable(self): pass diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 8752c9eab..f77607d56 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -22,6 +22,7 @@ @patch("djangooidc.views.CLIENT", autospec=True) class TestConnectionPool(MockEppLib): """Tests for our connection pooling behaviour""" + def setUp(self): """ Background: @@ -33,9 +34,7 @@ def setUp(self): # Current pool size "size": 1, # Which errors the pool should look out for - "exc_classes": ( - TransportError, - ), + "exc_classes": (TransportError,), # Occasionally pings the registry to keep the connection alive. # Value in seconds => (keepalive / size) "keepalive": 60, @@ -43,7 +42,7 @@ def setUp(self): def tearDown(self): super().tearDown() - + def user_info(*args): return { "sub": "TEST", @@ -69,16 +68,16 @@ def test_pool_created_successfully(self, mock_client): # in client.py. They should remain unchanged, # and if they aren't, something went wrong. expected_login = commands.Login( - cl_id='nothing', - password='nothing', + cl_id="nothing", + password="nothing", obj_uris=[ - 'urn:ietf:params:xml:ns:domain-1.0', - 'urn:ietf:params:xml:ns:contact-1.0' + "urn:ietf:params:xml:ns:domain-1.0", + "urn:ietf:params:xml:ns:contact-1.0", ], new_pw=None, - version='1.0', - lang='en', - ext_uris=[] + version="1.0", + lang="en", + ext_uris=[], ) # Key/cert will generate a new file everytime. @@ -94,7 +93,7 @@ def test_pool_created_successfully(self, mock_client): ).__dict__ except Exception as err: self.fail(err) - + # We don't care about checking if the objects are both of # the same reference, we only care about data parity, so # we do a dict conversion. @@ -118,15 +117,14 @@ def test_pool_created_successfully(self, mock_client): @skip("not implemented yet") def test_pool_timesout(self): """The pool timesout and restarts""" - raise - + raise + @skip("not implemented yet") def test_multiple_users_send_data(self): """Multiple users send data concurrently""" raise - + @skip("not implemented yet") def test_pool_sends_data(self): """A .send is invoked on the pool""" raise - diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 3784f3fbc..00bf0ffd0 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -22,6 +22,7 @@ class EPPConnectionPool(ConnectionPool): options (dict): Options for the ConnectionPool base class """ + def __init__(self, client, login, options: dict): # For storing shared credentials self._client = client @@ -51,11 +52,11 @@ def _create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" socket = Socket(client, login) return socket - + def get_connections(self): """Returns the connection queue""" return self.conn - + def kill_all_connections(self): """Kills all active connections in the pool.""" try: @@ -66,11 +67,9 @@ def kill_all_connections(self): self.lock.release() # TODO - connection pool err except Exception as err: - logger.error( - "Could not kill all connections." - ) + logger.error("Could not kill all connections.") raise err - + def repopulate_all_connections(self): """Regenerates the connection pool. If any connections exist, kill them first. @@ -80,6 +79,4 @@ def repopulate_all_connections(self): for i in range(self.size): self.lock.acquire() for i in range(self.size): - gevent.spawn_later(self.SPAWN_FREQUENCY*i, self._addOne) - - + gevent.spawn_later(self.SPAWN_FREQUENCY * i, self._addOne) diff --git a/src/epplibwrapper/utility/pool_status.py b/src/epplibwrapper/utility/pool_status.py index 82c032941..214bf8ac1 100644 --- a/src/epplibwrapper/utility/pool_status.py +++ b/src/epplibwrapper/utility/pool_status.py @@ -1,5 +1,6 @@ class PoolStatus: """A list of Booleans to keep track of Pool Status""" + def __init__(self): self.pool_running = False self.connection_success = False From 8db3f66c42841562b72bb9970d05178639e9ea06 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:44:00 -0600 Subject: [PATCH 19/65] Test fix for scanner --- src/epplibwrapper/utility/pool.py | 1 - src/registrar/models/domain.py | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 00bf0ffd0..df4ff993d 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,4 +1,3 @@ -from collections import deque import logging import gevent from geventconnpool import ConnectionPool diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index d6dd5e287..898836fc4 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -8,15 +8,19 @@ from django.db import models from typing import Any -from epplibwrapper import ( - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, -) +try: + from epplibwrapper import ( + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, + ) +except ImportError: + pass + from registrar.utility.errors import ( ActionNotAllowed, From 0896401169db553a5da4b365116a3c7cbfbf92ff Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:51:13 -0600 Subject: [PATCH 20/65] Another import test --- src/epplibwrapper/__init__.py | 1 + src/registrar/models/domain.py | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index dd6664a3a..7defcca31 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -44,6 +44,7 @@ # Attn: these imports should NOT be at the top of the file try: + from .utility.pool import EPPConnectionPool from .client import CLIENT, commands from .errors import RegistryError, ErrorCode from epplib.models import common, info diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 898836fc4..06b9dce60 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -8,18 +8,17 @@ from django.db import models from typing import Any -try: - from epplibwrapper import ( - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, - ) -except ImportError: - pass + +from epplibwrapper import ( + + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, +) from registrar.utility.errors import ( From 3aac1e38df2643123f65a83f7eee4bbabd8012e8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:54:20 -0600 Subject: [PATCH 21/65] Log error --- src/epplibwrapper/__init__.py | 1 - src/registrar/models/domain.py | 29 +++++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index 7defcca31..dd6664a3a 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -44,7 +44,6 @@ # Attn: these imports should NOT be at the top of the file try: - from .utility.pool import EPPConnectionPool from .client import CLIENT, commands from .errors import RegistryError, ErrorCode from epplib.models import common, info diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 06b9dce60..319be9fae 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -9,17 +9,6 @@ from django.db import models from typing import Any -from epplibwrapper import ( - - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, -) - from registrar.utility.errors import ( ActionNotAllowed, @@ -35,8 +24,24 @@ from .utility.time_stamped_model import TimeStampedModel from .public_contact import PublicContact - logger = logging.getLogger(__name__) +try: + from epplibwrapper import ( + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, + ) +except ImportError as err: + logger.error("An import error occured....") + logger.error(err) + raise err + + + class Domain(TimeStampedModel, DomainHelper): From d7c7fb9d238181e56102276a0366b5e88aa6d5ac Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:21:18 -0600 Subject: [PATCH 22/65] Tests / code cleanup --- src/epplibwrapper/client.py | 66 +++++----- src/epplibwrapper/socket.py | 4 +- src/epplibwrapper/tests/test_pool.py | 158 +++++++++++++----------- src/epplibwrapper/utility/pool.py | 17 +-- src/epplibwrapper/utility/pool_error.py | 44 +++++++ src/registrar/models/domain.py | 29 ++--- 6 files changed, 188 insertions(+), 130 deletions(-) create mode 100644 src/epplibwrapper/utility/pool_error.py diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 50c3c3a3e..920dc284d 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -44,7 +44,7 @@ class EPPLibWrapper: ATTN: This should not be used directly. Use `Domain` from domain.py. """ - def __init__(self) -> None: + def __init__(self, start_connection_pool=True) -> None: """Initialize settings which will be used for all connections.""" # prepare (but do not send) a Login command self._login = commands.Login( @@ -80,7 +80,8 @@ def __init__(self) -> None: # Tracks the status of the pool self.pool_status = PoolStatus() - self.start_connection_pool() + if start_connection_pool: + self.start_connection_pool() def _send(self, command): """Helper function used by `send`.""" @@ -103,21 +104,21 @@ def _send(self, command): self.start_connection_pool() except (ValueError, ParsingError) as err: message = f"{cmd_type} failed to execute due to some syntax error." - logger.warning(message, exc_info=True) + logger.error(message, exc_info=True) raise RegistryError(message) from err except TransportError as err: message = f"{cmd_type} failed to execute due to a connection error." - logger.warning(message, exc_info=True) + logger.error(message, exc_info=True) raise RegistryError(message) from err except LoginError as err: # For linter text = "failed to execute due to a registry login error." message = f"{cmd_type} {text}" - logger.warning(message, exc_info=True) + logger.error(message, exc_info=True) raise RegistryError(message) from err except Exception as err: message = f"{cmd_type} failed to execute due to an unknown error." - logger.warning(message, exc_info=True) + logger.error(message, exc_info=True) raise RegistryError(message) from err else: if response.code >= 2000: @@ -134,15 +135,15 @@ def send(self, command, *, cleaned=False): raise ValueError("Please sanitize user input before sending it.") # Reopen the pool if its closed + # Only occurs when a login error is raised, after connection is successful if not self.pool_status.pool_running: # We want to reopen the connection pool, # but we don't want the end user to wait while it opens. # Raise syntax doesn't allow this, so we use a try/catch # block. try: - raise RegistryError( - "Can't contact the Registry. Please try again later" - ) + logger.error("Can't contact the Registry. Pool was not running.") + raise RegistryError("Can't contact the Registry. Pool was not running.") except RegistryError as err: raise err finally: @@ -159,39 +160,46 @@ def send(self, command, *, cleaned=False): else: # don't try again raise err - def start_connection_pool(self, restart_pool_if_exists=True): + def start_connection_pool( + self, restart_pool_if_exists=True, try_start_if_invalid=False + ): """Starts a connection pool for the registry. restart_pool_if_exists -> bool: If an instance of the pool already exists, then then that instance will be killed first. - It is generally recommended to keep this enabled.""" + It is generally recommended to keep this enabled. + + try_start_if_invalid -> bool: + Designed for use in test cases, if we can't connect + to the registry, ignore that and try to connect anyway + It is generally recommended to keep this disabled. + """ # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. - if settings.DEBUG or self._test_registry_connection_success(): + if ( + not try_start_if_invalid + and settings.DEBUG + or not self._test_registry_connection_success() + ): logger.warning("Cannot contact the Registry") self.pool_status.connection_success = False - # Q: Should err be raised instead? - # Q2: Since we try to connect 3 times, - # this indicates that the Registry isn't responsive. - # What should we do in this case? - return else: self.pool_status.connection_success = True - # If this function is reinvoked, then ensure - # that we don't have duplicate data sitting around. - if self._pool is not None and restart_pool_if_exists: - logger.info("Connection pool restarting...") - self.kill_pool() + # If this function is reinvoked, then ensure + # that we don't have duplicate data sitting around. + if self._pool is not None and restart_pool_if_exists: + logger.info("Connection pool restarting...") + self.kill_pool() - self._pool = EPPConnectionPool( - client=self._client, login=self._login, options=self.pool_options - ) - self.pool_status.pool_running = True - self.pool_status.pool_hanging = False + self._pool = EPPConnectionPool( + client=self._client, login=self._login, options=self.pool_options + ) + self.pool_status.pool_running = True + self.pool_status.pool_hanging = False - logger.info("Connection pool started") + logger.info("Connection pool started") def kill_pool(self): """Kills the existing pool. Use this instead @@ -220,7 +228,7 @@ def _test_registry_connection_success(self): try: # Initialize epplib CLIENT = EPPLibWrapper() - logger.debug("registry client initialized") + logger.info("registry client initialized") except Exception: CLIENT = None # type: ignore logger.warning( diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index c6ffe20bf..19ad2bd0b 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -16,7 +16,7 @@ class Socket: """Context manager which establishes a TCP connection with registry.""" - def __init__(self, client: commands.Login, login: Client) -> None: + def __init__(self, client: Client, login: commands.Login) -> None: """Save the epplib client and login details.""" self.client = client self.login = login @@ -29,7 +29,7 @@ def __exit__(self, *args, **kwargs): """Runs disconnect(), which closes a connection with EPPLib.""" self.disconnect() - def connect(self, pass_response_only=False): + def connect(self): """Use epplib to connect.""" self.client.connect() response = self.client.send(self.login) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index f77607d56..fedd9f7a4 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -1,10 +1,14 @@ from unittest import skip -from unittest.mock import patch +from unittest.mock import MagicMock, patch from django.conf import settings from django.test import Client +from django.test import TestCase from epplibwrapper.client import EPPLibWrapper +from epplibwrapper.utility.pool import EPPConnectionPool +from registrar.models.domain import Domain from registrar.tests.common import MockEppLib +from registrar.models.domain import registry import logging @@ -12,24 +16,17 @@ from epplib.client import Client from epplib import commands from epplib.exceptions import TransportError - from epplib.transport import SocketTransport + from epplib.responses import base except ImportError: pass logger = logging.getLogger(__name__) -@patch("djangooidc.views.CLIENT", autospec=True) -class TestConnectionPool(MockEppLib): +class TestConnectionPool(TestCase): """Tests for our connection pooling behaviour""" def setUp(self): - """ - Background: - Given the registrant is logged in - And the registrant is the admin on a domain - """ - super().setUp() self.pool_options = { # Current pool size "size": 1, @@ -40,10 +37,45 @@ def setUp(self): "keepalive": 60, } - def tearDown(self): - super().tearDown() + # Mock a successful connection + self.mock_connect_patch = patch("epplib.client.Client.connect") + self.mocked_connect_function = self.mock_connect_patch.start() + self.mocked_connect_function.side_effect = self.mock_connect + + # Mock the send behaviour + self.mock_send_patch = patch("epplib.client.Client.send") + self.mocked_send_function = self.mock_send_patch.start() + self.mocked_send_function.side_effect = self.mock_send + + # Mock the pool object + self.mockSendPatch = patch("registrar.models.domain.registry._pool") + self.mockedSendFunction = self.mockSendPatch.start() + self.mockedSendFunction.side_effect = self.fake_pool - def user_info(*args): + def tearDown(self): + self.mock_send_patch.stop() + self.mock_connect_patch.stop() + self.mockSendPatch.stop() + + def mock_connect(self, _request): + return None + + def mock_send(self, _request): + if isinstance(_request, commands.Login): + response = MagicMock( + code=1000, + msg="Command completed successfully", + res_data=None, + cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", + sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", + extensions=[], + msg_q=None, + ) + + return response + return None + + def user_info(self, *args): return { "sub": "TEST", "email": "test@example.com", @@ -52,67 +84,19 @@ def user_info(*args): "phone": "814564000", } - def test_pool_created_successfully(self, mock_client): - # setup - session = self.client.session - session["state"] = "TEST" # nosec B105 - session.save() - # mock + @patch("djangooidc.views.CLIENT", autospec=True) + def fake_pool(self, mock_client): + # mock client mock_client.callback.side_effect = self.user_info + # Create a mock transport object + mock_login = MagicMock() + mock_login.cert_file = "path/to/cert_file" + mock_login.key_file = "path/to/key_file" - client = EPPLibWrapper() - pool = client._pool - - # These are defined outside of the pool, - # so we can reimplement how this is being done - # in client.py. They should remain unchanged, - # and if they aren't, something went wrong. - expected_login = commands.Login( - cl_id="nothing", - password="nothing", - obj_uris=[ - "urn:ietf:params:xml:ns:domain-1.0", - "urn:ietf:params:xml:ns:contact-1.0", - ], - new_pw=None, - version="1.0", - lang="en", - ext_uris=[], + pool = EPPConnectionPool( + client=mock_client, login=mock_login, options=self.pool_options ) - - # Key/cert will generate a new file everytime. - # This should never be null, so we can check for that. - try: - expected_client = Client( - SocketTransport( - settings.SECRET_REGISTRY_HOSTNAME, - cert_file=pool._client.transport.cert_file, - key_file=pool._client.transport.key_file, - password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, - ) - ).__dict__ - except Exception as err: - self.fail(err) - - # We don't care about checking if the objects are both of - # the same reference, we only care about data parity, so - # we do a dict conversion. - actual_client = pool._client.__dict__ - actual_client["transport"] = actual_client["transport"].__dict__ - expected_client["transport"] = expected_client["transport"].__dict__ - - # Ensure that we're getting the credentials we expect - self.assertEqual(pool._login, expected_login) - self.assertEqual(actual_client, expected_client) - - # Check that options are set correctly - self.assertEqual(pool.size, self.pool_options["size"]) - self.assertEqual(pool.keepalive, self.pool_options["keepalive"]) - self.assertEqual(pool.exc_classes, self.pool_options["exc_classes"]) - - # Check that it is running - self.assertEqual(client.pool_status.connection_success, True) - self.assertEqual(client.pool_status.pool_running, True) + return pool @skip("not implemented yet") def test_pool_timesout(self): @@ -124,7 +108,33 @@ def test_multiple_users_send_data(self): """Multiple users send data concurrently""" raise - @skip("not implemented yet") + def test_pool_tries_create_invalid(self): + """A .send is invoked on the pool, but the pool + shouldn't be running.""" + # Fake data for the _pool object + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + + # Trigger the getter - should fail + expected_contact = domain.security_contact + self.assertEqual(registry.pool_status.pool_running, False) + self.assertEqual(registry.pool_status.connection_success, False) + self.assertEqual(len(registry._pool.conn), 0) + def test_pool_sends_data(self): - """A .send is invoked on the pool""" - raise + """A .send is invoked on the pool successfully""" + # Fake data for the _pool object + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + + # The connection pool will fail to start, start it manually + # so that our mocks can take over + registry.start_connection_pool(try_start_if_invalid=True) + + # Pretend that we've connected + registry.pool_status.pool_running = True + registry.pool_status.connection_success = True + + # Trigger the getter - should succeed + expected_contact = domain.security_contact + self.assertEqual(registry.pool_status.pool_running, True) + self.assertEqual(registry.pool_status.connection_success, True) + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index df4ff993d..6d51e539f 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,8 +1,8 @@ import logging import gevent from geventconnpool import ConnectionPool -from epplibwrapper.errors import RegistryError, LoginError from epplibwrapper.socket import Socket +from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes try: from epplib.commands import Hello @@ -33,10 +33,13 @@ def _new_connection(self): try: connection = socket.connect() return connection - except LoginError as err: - message = "_new_connection failed to execute due to a registry login error." + except Exception as err: + message = f"Failed to execute due to a registry login error: {err}" logger.error(message, exc_info=True) - raise RegistryError(message) from err + # We want to raise a pool error rather than a LoginError here + # because if this occurs internally, we should handle this + # differently than we otherwise would for LoginError. + raise PoolError(code=PoolErrorCodes.NEW_CONNECTION_FAILED) from err def _keepalive(self, c): """Sends a command to the server to keep the connection alive.""" @@ -44,8 +47,9 @@ def _keepalive(self, c): # Sends a ping to EPPLib c.send(Hello()) except Exception as err: - logger.error("Failed to keep the connection alive.", exc_info=True) - raise RegistryError("Failed to keep the connection alive.") from err + message = "Failed to keep the connection alive." + logger.error(message, exc_info=True) + raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err def _create_socket(self, client, login) -> Socket: """Creates and returns a socket instance""" @@ -64,7 +68,6 @@ def kill_all_connections(self): # Clear the semaphore for i in range(self.lock.counter): self.lock.release() - # TODO - connection pool err except Exception as err: logger.error("Could not kill all connections.") raise err diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py new file mode 100644 index 000000000..0bcd36a53 --- /dev/null +++ b/src/epplibwrapper/utility/pool_error.py @@ -0,0 +1,44 @@ +from enum import IntEnum + + +class PoolErrorCodes(IntEnum): + """Used in the PoolError class for + error mapping. + + Overview of contact error codes: + - 2000 KILL_ALL_FAILED + - 2001 NEW_CONNECTION_FAILED + - 2002 KEEP_ALIVE_FAILED + """ + + KILL_ALL_FAILED = 2000 + NEW_CONNECTION_FAILED = 2001 + KEEP_ALIVE_FAILED = 2002 + + +class PoolError(Exception): + """ + Overview of contact error codes: + - 2000 KILL_ALL_FAILED + - 2001 NEW_CONNECTION_FAILED + - 2002 KEEP_ALIVE_FAILED + """ + + # For linter + kill_failed = "Could not kill all connections." + conn_failed = "Failed to execute due to a registry login error." + alive_failed = "Failed to keep the connection alive." + _error_mapping = { + PoolErrorCodes.KILL_ALL_FAILED: kill_failed, + PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed, + PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed, + } + + def __init__(self, *args, code=None, **kwargs): + super().__init__(*args, **kwargs) + self.code = code + if self.code in self._error_mapping: + self.message = self._error_mapping.get(self.code) + + def __str__(self): + return f"{self.message}" diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 319be9fae..07d92f757 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -16,32 +16,25 @@ NameserverErrorCodes as nsErrorCodes, ) -from registrar.models.utility.contact_error import ContactError, ContactErrorCodes +from epplibwrapper import ( + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, +) +from registrar.models.utility.contact_error import ContactError, ContactErrorCodes from .utility.domain_field import DomainField from .utility.domain_helper import DomainHelper from .utility.time_stamped_model import TimeStampedModel from .public_contact import PublicContact -logger = logging.getLogger(__name__) -try: - from epplibwrapper import ( - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, - ) -except ImportError as err: - logger.error("An import error occured....") - logger.error(err) - raise err - - +logger = logging.getLogger(__name__) class Domain(TimeStampedModel, DomainHelper): From b6921b3f4ce3156b03b471918165d634a4960d66 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:26:47 -0600 Subject: [PATCH 23/65] Linter --- src/epplibwrapper/tests/test_pool.py | 5 ----- src/registrar/config/settings.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index fedd9f7a4..6573a64f5 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -2,21 +2,16 @@ from unittest.mock import MagicMock, patch from django.conf import settings -from django.test import Client from django.test import TestCase -from epplibwrapper.client import EPPLibWrapper from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import Domain -from registrar.tests.common import MockEppLib from registrar.models.domain import registry import logging try: - from epplib.client import Client from epplib import commands from epplib.exceptions import TransportError - from epplib.responses import base except ImportError: pass diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 9d03dbc89..fd3893617 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -546,7 +546,7 @@ # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -POOL_KEEP_ALIVE = 60 # Ping every 20 seconds +POOL_KEEP_ALIVE = 60 # Determines how long we try to keep a pool alive for, # before restarting it. From 0dde277c864d308075042dd74a740b86884c191c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:40:15 -0600 Subject: [PATCH 24/65] Test changes --- src/epplibwrapper/client.py | 18 +++++++++++++++--- src/epplibwrapper/tests/test_pool.py | 17 +++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 920dc284d..680a6661c 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -121,6 +121,7 @@ def _send(self, command): logger.error(message, exc_info=True) raise RegistryError(message) from err else: + print(f"test thing {response}") if response.code >= 2000: raise RegistryError(response.msg, code=response.code) else: @@ -160,6 +161,16 @@ def send(self, command, *, cleaned=False): else: # don't try again raise err + def get_pool(self): + """Get the current pool instance""" + return self._pool + + def _create_pool(self, client, login, options): + """Creates and returns new pool instance""" + return EPPConnectionPool( + client, login, options + ) + def start_connection_pool( self, restart_pool_if_exists=True, try_start_if_invalid=False ): @@ -193,9 +204,10 @@ def start_connection_pool( logger.info("Connection pool restarting...") self.kill_pool() - self._pool = EPPConnectionPool( - client=self._client, login=self._login, options=self.pool_options - ) + self._pool = self._create_pool( + self._client, self._login, self.pool_options + ) + self.pool_status.pool_running = True self.pool_status.pool_hanging = False diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 6573a64f5..e62135221 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -3,9 +3,11 @@ from django.conf import settings from django.test import TestCase +from epplibwrapper.client import EPPLibWrapper from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import Domain from registrar.models.domain import registry +from contextlib import ExitStack import logging @@ -120,16 +122,23 @@ def test_pool_sends_data(self): # Fake data for the _pool object domain, _ = Domain.objects.get_or_create(name="freeman.gov") + def start_fake_connection(self): + registry.pool_status.pool_running = True + registry.pool_status.connection_success = True + registry._pool = registry.get_pool() + # The connection pool will fail to start, start it manually # so that our mocks can take over - registry.start_connection_pool(try_start_if_invalid=True) - + with ExitStack() as stack: + stack.enter_context(patch.object(EPPLibWrapper, "get_pool", self.fake_pool)) + stack.enter_context(patch.object(EPPLibWrapper, "start_connection_pool", start_fake_connection)) + expected_contact = domain.security_contact + # Pretend that we've connected registry.pool_status.pool_running = True registry.pool_status.connection_success = True # Trigger the getter - should succeed - expected_contact = domain.security_contact self.assertEqual(registry.pool_status.pool_running, True) self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + self.assertEqual(len(registry.get_pool().conn), self.pool_options["size"]) From faa35613e23ff6f95cf06a355140b685fe18d7ca Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:58:03 -0600 Subject: [PATCH 25/65] test --- src/epplibwrapper/__init__.py | 5 +++++ src/epplibwrapper/client.py | 5 +++-- src/epplibwrapper/utility/pool.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index dd6664a3a..b8fb12bcb 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -44,6 +44,8 @@ # Attn: these imports should NOT be at the top of the file try: + from epplibwrapper.socket import Socket + from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes from .client import CLIENT, commands from .errors import RegistryError, ErrorCode from epplib.models import common, info @@ -61,4 +63,7 @@ "info", "ErrorCode", "RegistryError", + "Socket", + "PoolError", + "PoolErrorCodes" ] diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 680a6661c..4f393aa0c 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -81,7 +81,8 @@ def __init__(self, start_connection_pool=True) -> None: self.pool_status = PoolStatus() if start_connection_pool: - self.start_connection_pool() + pass + #self.start_connection_pool() def _send(self, command): """Helper function used by `send`.""" @@ -207,7 +208,7 @@ def start_connection_pool( self._pool = self._create_pool( self._client, self._login, self.pool_options ) - + self.pool_status.pool_running = True self.pool_status.pool_hanging = False diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 6d51e539f..7a58f7efe 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -3,12 +3,12 @@ from geventconnpool import ConnectionPool from epplibwrapper.socket import Socket from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes - try: from epplib.commands import Hello except ImportError: pass + logger = logging.getLogger(__name__) From fc5436039d89972daea989b6a661cee4217a898d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:06:05 -0600 Subject: [PATCH 26/65] Stuff --- src/epplibwrapper/client.py | 3 +-- src/registrar/config/settings.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 4f393aa0c..812760330 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -81,8 +81,7 @@ def __init__(self, start_connection_pool=True) -> None: self.pool_status = PoolStatus() if start_connection_pool: - pass - #self.start_connection_pool() + self.start_connection_pool() def _send(self, command): """Helper function used by `send`.""" diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index fd3893617..6c7f32de6 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -78,6 +78,7 @@ # Installing them here makes them available for execution. # Do not access INSTALLED_APPS directly. Use `django.apps.apps` instead. INSTALLED_APPS = [ + "epplibwrapper", # let's be sure to install our own application! # it needs to be listed before django.contrib.admin # otherwise Django would find the default template From d38c33e4d95ca5cb50ecfa8873c1254f0c3dd4db Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:20:11 -0600 Subject: [PATCH 27/65] Test script change --- .github/actions/django-security-check/entrypoint.sh | 2 +- src/registrar/config/settings.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/django-security-check/entrypoint.sh b/.github/actions/django-security-check/entrypoint.sh index 69f8246d7..8e2ca26ab 100755 --- a/.github/actions/django-security-check/entrypoint.sh +++ b/.github/actions/django-security-check/entrypoint.sh @@ -14,7 +14,7 @@ if [[ "$ENV_TYPE" == "pipenv" ]]; then cd $REQS pip3 install pipenv PIPENV_IGNORE_VIRTUALENVS=1 pipenv install - cd $MANAGE_PATH && PIPENV_IGNORE_VIRTUALENVS=1 pipenv run python3 manage.py check --deploy --fail-level ${FAIL} ${ARGS} &> output.txt + cd $MANAGE_PATH && PIPENV_IGNORE_VIRTUALENVS=1 pipenv run python manage.py check --deploy --fail-level ${FAIL} ${ARGS} &> output.txt EXIT_CODE=$? fi if [[ "$ENV_TYPE" == "venv" ]]; then diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 6c7f32de6..fd3893617 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -78,7 +78,6 @@ # Installing them here makes them available for execution. # Do not access INSTALLED_APPS directly. Use `django.apps.apps` instead. INSTALLED_APPS = [ - "epplibwrapper", # let's be sure to install our own application! # it needs to be listed before django.contrib.admin # otherwise Django would find the default template From 939ff4b06396f830a62cbaa061f0f9466a5cbe75 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:34:24 -0600 Subject: [PATCH 28/65] Import fix test --- .github/actions/django-security-check/entrypoint.sh | 2 +- src/registrar/models/__init__.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/actions/django-security-check/entrypoint.sh b/.github/actions/django-security-check/entrypoint.sh index 8e2ca26ab..69f8246d7 100755 --- a/.github/actions/django-security-check/entrypoint.sh +++ b/.github/actions/django-security-check/entrypoint.sh @@ -14,7 +14,7 @@ if [[ "$ENV_TYPE" == "pipenv" ]]; then cd $REQS pip3 install pipenv PIPENV_IGNORE_VIRTUALENVS=1 pipenv install - cd $MANAGE_PATH && PIPENV_IGNORE_VIRTUALENVS=1 pipenv run python manage.py check --deploy --fail-level ${FAIL} ${ARGS} &> output.txt + cd $MANAGE_PATH && PIPENV_IGNORE_VIRTUALENVS=1 pipenv run python3 manage.py check --deploy --fail-level ${FAIL} ${ARGS} &> output.txt EXIT_CODE=$? fi if [[ "$ENV_TYPE" == "venv" ]]; then diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index f287c401c..0091955b0 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -1,7 +1,11 @@ from auditlog.registry import auditlog # type: ignore from .contact import Contact -from .domain_application import DomainApplication +try: + from .domain_application import DomainApplication +except ImportError as err: + print(err.with_traceback()) + pass from .domain_information import DomainInformation from .domain import Domain from .draft_domain import DraftDomain From ae01ffd9105d4033f8e22bc683e185d0e1e98c2a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:36:03 -0600 Subject: [PATCH 29/65] Update __init__.py --- src/registrar/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 0091955b0..6285b9f09 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -4,7 +4,7 @@ try: from .domain_application import DomainApplication except ImportError as err: - print(err.with_traceback()) + print(err) pass from .domain_information import DomainInformation from .domain import Domain From f1589c8480b0a54f50eeb394c9702449fdeaad91 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:41:55 -0600 Subject: [PATCH 30/65] Update __init__.py --- src/registrar/models/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 6285b9f09..5ff1f38e2 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -1,10 +1,11 @@ from auditlog.registry import auditlog # type: ignore - +import traceback from .contact import Contact try: from .domain_application import DomainApplication except ImportError as err: - print(err) + print("Error traceback is...") + print(traceback.format_exc()) pass from .domain_information import DomainInformation from .domain import Domain From d50de8516ea08f6cd5a84bde48cc06e897c323ee Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:44:05 -0600 Subject: [PATCH 31/65] Change import order --- src/registrar/models/__init__.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 5ff1f38e2..63f0bbb54 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -1,14 +1,8 @@ from auditlog.registry import auditlog # type: ignore -import traceback from .contact import Contact -try: - from .domain_application import DomainApplication -except ImportError as err: - print("Error traceback is...") - print(traceback.format_exc()) - pass -from .domain_information import DomainInformation from .domain import Domain +from .domain_application import DomainApplication +from .domain_information import DomainInformation from .draft_domain import DraftDomain from .host_ip import HostIP from .host import Host @@ -23,9 +17,9 @@ __all__ = [ "Contact", + "Domain", "DomainApplication", "DomainInformation", - "Domain", "DraftDomain", "DomainInvitation", "HostIP", From d943b69b3d353a8a54eb8abc2f4b032f88a9067f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:49:42 -0600 Subject: [PATCH 32/65] Mocked sockets --- src/epplibwrapper/client.py | 6 ++-- src/epplibwrapper/tests/test_pool.py | 52 ++++++++++++++++++---------- src/registrar/models/__init__.py | 4 +-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 812760330..a465593a0 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -168,7 +168,7 @@ def get_pool(self): def _create_pool(self, client, login, options): """Creates and returns new pool instance""" return EPPConnectionPool( - client, login, options + client, login, options ) def start_connection_pool( @@ -190,8 +190,8 @@ def start_connection_pool( # one socket, and if successful, then we know we can connect. if ( not try_start_if_invalid - and settings.DEBUG - or not self._test_registry_connection_success() + and (settings.DEBUG + or not self._test_registry_connection_success()) ): logger.warning("Cannot contact the Registry") self.pool_status.connection_success = False diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index e62135221..d088697ec 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -4,6 +4,7 @@ from django.test import TestCase from epplibwrapper.client import EPPLibWrapper +from epplibwrapper.socket import Socket from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import Domain from registrar.models.domain import registry @@ -121,24 +122,37 @@ def test_pool_sends_data(self): """A .send is invoked on the pool successfully""" # Fake data for the _pool object domain, _ = Domain.objects.get_or_create(name="freeman.gov") - - def start_fake_connection(self): - registry.pool_status.pool_running = True - registry.pool_status.connection_success = True - registry._pool = registry.get_pool() - # The connection pool will fail to start, start it manually - # so that our mocks can take over + def fake_send(self): + return MagicMock( + code=1000, + msg="Command completed successfully", + res_data=None, + cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", + sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", + extensions=[], + msg_q=None, + ) + with ExitStack() as stack: - stack.enter_context(patch.object(EPPLibWrapper, "get_pool", self.fake_pool)) - stack.enter_context(patch.object(EPPLibWrapper, "start_connection_pool", start_fake_connection)) - expected_contact = domain.security_contact - - # Pretend that we've connected - registry.pool_status.pool_running = True - registry.pool_status.connection_success = True - - # Trigger the getter - should succeed - self.assertEqual(registry.pool_status.pool_running, True) - self.assertEqual(registry.pool_status.connection_success, True) - self.assertEqual(len(registry.get_pool().conn), self.pool_options["size"]) + stack.enter_context(patch.object(Socket, "connect", None)) + stack.enter_context(patch.object(Socket, "send", fake_send)) + stack.enter_context(patch.object(Socket, "_create_socket", Socket())) + #stack.enter_context(patch.object(EPPLibWrapper, "get_pool", self.fake_pool)) + pool = EPPLibWrapper(False) + # The connection pool will fail to start, start it manually + # so that our mocks can take over + pool.start_connection_pool(try_start_if_invalid=True) + print(f"this is pool {pool._pool.__dict__}") + # Pool should be running, and be the right size + self.assertEqual(pool.pool_status.pool_running, True) + self.assertEqual(pool.pool_status.connection_success, True) + pool.send(commands.InfoDomain(name="test.gov"), cleaned=True) + self.assertEqual(len(pool._pool.conn), self.pool_options["size"]) + + #pool.send() + + # Trigger the getter - should succeed + #expected_contact = domain.security_contact + + diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 63f0bbb54..1d28c9e89 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -1,8 +1,8 @@ from auditlog.registry import auditlog # type: ignore from .contact import Contact -from .domain import Domain from .domain_application import DomainApplication from .domain_information import DomainInformation +from .domain import Domain from .draft_domain import DraftDomain from .host_ip import HostIP from .host import Host @@ -17,9 +17,9 @@ __all__ = [ "Contact", - "Domain", "DomainApplication", "DomainInformation", + "Domain", "DraftDomain", "DomainInvitation", "HostIP", From 2f4425d9144266f642b3f5ee13edd20e54f322dc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:53:57 -0600 Subject: [PATCH 33/65] Fix bad logic --- src/epplibwrapper/socket.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 19ad2bd0b..716fef18a 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -61,7 +61,13 @@ def test_connection_success(self): return False else: self.disconnect() - return not self.is_login_error(response.code) + + # If we encounter a login error, fail + if self.is_login_error(response.code): + return False + + # otherwise, just return true + return True def disconnect(self): """Close the connection.""" From 1502e7a693a4f4c51428082a084dd0f58796dc21 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:00:21 -0600 Subject: [PATCH 34/65] Update tests --- src/epplibwrapper/client.py | 2 +- src/epplibwrapper/socket.py | 6 +++++- src/epplibwrapper/tests/test_pool.py | 15 +++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index a465593a0..bdb00b856 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -189,7 +189,7 @@ def start_connection_pool( # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. if ( - not try_start_if_invalid + try_start_if_invalid and (settings.DEBUG or not self._test_registry_connection_success()) ): diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 716fef18a..186b98322 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -46,6 +46,7 @@ def test_connection_success(self): Tries 3 times""" # Something went wrong if this doesn't exist if not hasattr(self.client, "connect"): + logger.warning("self.client does not have a connect method") return False counter = 0 # we'll try 3 times @@ -58,12 +59,15 @@ def test_connection_success(self): counter += 1 sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms else: # don't try again + logger.warning("LoginError raised and should not retry or has been retried 3 times already") + logger.warning(f"should retry? {err.should_retry()}") return False else: self.disconnect() - + # If we encounter a login error, fail if self.is_login_error(response.code): + logger.warning("was login error") return False # otherwise, just return true diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index d088697ec..3005edb29 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -137,18 +137,13 @@ def fake_send(self): with ExitStack() as stack: stack.enter_context(patch.object(Socket, "connect", None)) stack.enter_context(patch.object(Socket, "send", fake_send)) - stack.enter_context(patch.object(Socket, "_create_socket", Socket())) + stack.enter_context(patch.object(EPPLibWrapper, "_create_pool", self.fake_pool)) #stack.enter_context(patch.object(EPPLibWrapper, "get_pool", self.fake_pool)) - pool = EPPLibWrapper(False) - # The connection pool will fail to start, start it manually - # so that our mocks can take over - pool.start_connection_pool(try_start_if_invalid=True) - print(f"this is pool {pool._pool.__dict__}") # Pool should be running, and be the right size - self.assertEqual(pool.pool_status.pool_running, True) - self.assertEqual(pool.pool_status.connection_success, True) - pool.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(len(pool._pool.conn), self.pool_options["size"]) + self.assertEqual(registry.pool_status.pool_running, True) + self.assertEqual(registry.pool_status.connection_success, True) + registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) #pool.send() From 1ca7c51fbb5a5a4aa069384fafc74bbe74c99883 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:56:28 -0600 Subject: [PATCH 35/65] Hotfix --- src/epplibwrapper/client.py | 7 ++--- src/epplibwrapper/tests/test_pool.py | 39 ++++++++++++++++++------- src/epplibwrapper/utility/pool.py | 2 +- src/epplibwrapper/utility/pool_error.py | 2 +- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index bdb00b856..3cecb4995 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -172,7 +172,7 @@ def _create_pool(self, client, login, options): ) def start_connection_pool( - self, restart_pool_if_exists=True, try_start_if_invalid=False + self, restart_pool_if_exists=True ): """Starts a connection pool for the registry. @@ -189,9 +189,8 @@ def start_connection_pool( # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. if ( - try_start_if_invalid - and (settings.DEBUG - or not self._test_registry_connection_success()) + settings.DEBUG + or not self._test_registry_connection_success() ): logger.warning("Cannot contact the Registry") self.pool_status.connection_success = False diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 3005edb29..895eaa7e8 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -14,6 +14,7 @@ try: from epplib import commands + from epplib.client import Client from epplib.exceptions import TransportError except ImportError: pass @@ -35,6 +36,15 @@ def setUp(self): "keepalive": 60, } + #self.start_mocks() + + def tearDown(self): + #self.mock_send_patch.stop() + #self.mock_connect_patch.stop() + #self.mockSendPatch.stop() + pass + + def start_mocks(self): # Mock a successful connection self.mock_connect_patch = patch("epplib.client.Client.connect") self.mocked_connect_function = self.mock_connect_patch.start() @@ -50,11 +60,6 @@ def setUp(self): self.mockedSendFunction = self.mockSendPatch.start() self.mockedSendFunction.side_effect = self.fake_pool - def tearDown(self): - self.mock_send_patch.stop() - self.mock_connect_patch.stop() - self.mockSendPatch.stop() - def mock_connect(self, _request): return None @@ -95,6 +100,22 @@ def fake_pool(self, mock_client): client=mock_client, login=mock_login, options=self.pool_options ) return pool + + @patch("djangooidc.views.CLIENT", autospec=True) + def fake_socket(self, mock_client): + # mock client + mock_client.callback.side_effect = self.user_info + # Create a mock transport object + mock_login = MagicMock() + mock_login.transport.cert_file = "path/to/cert_file" + mock_login.transport.key_file = "path/to/key_file" + return Socket(mock_client, mock_login) + + def test(self, client, login): + mock = MagicMock() + mock.response.code = 1000 + mock().return_value = 1000 + return MagicMock() @skip("not implemented yet") def test_pool_timesout(self): @@ -124,7 +145,7 @@ def test_pool_sends_data(self): domain, _ = Domain.objects.get_or_create(name="freeman.gov") def fake_send(self): - return MagicMock( + mock = MagicMock( code=1000, msg="Command completed successfully", res_data=None, @@ -133,12 +154,10 @@ def fake_send(self): extensions=[], msg_q=None, ) + return mock with ExitStack() as stack: - stack.enter_context(patch.object(Socket, "connect", None)) - stack.enter_context(patch.object(Socket, "send", fake_send)) - stack.enter_context(patch.object(EPPLibWrapper, "_create_pool", self.fake_pool)) - #stack.enter_context(patch.object(EPPLibWrapper, "get_pool", self.fake_pool)) + stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) # Pool should be running, and be the right size self.assertEqual(registry.pool_status.pool_running, True) self.assertEqual(registry.pool_status.connection_success, True) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 7a58f7efe..3b8eb240c 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -34,7 +34,7 @@ def _new_connection(self): connection = socket.connect() return connection except Exception as err: - message = f"Failed to execute due to a registry login error: {err}" + message = f"Failed to execute due to a registry error: {err}" logger.error(message, exc_info=True) # We want to raise a pool error rather than a LoginError here # because if this occurs internally, we should handle this diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py index 0bcd36a53..70312f32e 100644 --- a/src/epplibwrapper/utility/pool_error.py +++ b/src/epplibwrapper/utility/pool_error.py @@ -26,7 +26,7 @@ class PoolError(Exception): # For linter kill_failed = "Could not kill all connections." - conn_failed = "Failed to execute due to a registry login error." + conn_failed = "Failed to execute due to a registry error." alive_failed = "Failed to keep the connection alive." _error_mapping = { PoolErrorCodes.KILL_ALL_FAILED: kill_failed, From 9c6a6ef12a5d44516c129c464e5e4e594f4b445a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:02:14 -0600 Subject: [PATCH 36/65] Fix test cases --- .../decisions/0023-use-geventconnpool.md | 2 +- src/epplibwrapper/client.py | 6 +- src/epplibwrapper/tests/test_pool.py | 224 +++++++++--------- .../tests/utility/infoDomain.xml | 33 +++ 4 files changed, 149 insertions(+), 116 deletions(-) create mode 100644 src/epplibwrapper/tests/utility/infoDomain.xml diff --git a/docs/architecture/decisions/0023-use-geventconnpool.md b/docs/architecture/decisions/0023-use-geventconnpool.md index c24318b4f..9288f3e86 100644 --- a/docs/architecture/decisions/0023-use-geventconnpool.md +++ b/docs/architecture/decisions/0023-use-geventconnpool.md @@ -4,7 +4,7 @@ Date: 2023-13-10 ## Status -In Review +Accepted ## Context diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 3cecb4995..5381f7ce1 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -121,7 +121,6 @@ def _send(self, command): logger.error(message, exc_info=True) raise RegistryError(message) from err else: - print(f"test thing {response}") if response.code >= 2000: raise RegistryError(response.msg, code=response.code) else: @@ -188,10 +187,7 @@ def start_connection_pool( """ # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. - if ( - settings.DEBUG - or not self._test_registry_connection_success() - ): + if not self._test_registry_connection_success(): logger.warning("Cannot contact the Registry") self.pool_status.connection_success = False else: diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 895eaa7e8..74a2b687d 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -1,5 +1,8 @@ +import datetime +from pathlib import Path from unittest import skip from unittest.mock import MagicMock, patch +from dateutil.tz import tzlocal from django.conf import settings from django.test import TestCase @@ -16,6 +19,8 @@ from epplib import commands from epplib.client import Client from epplib.exceptions import TransportError + from epplib.transport import SocketTransport + from epplib.models import common, info except ImportError: pass @@ -35,37 +40,25 @@ def setUp(self): # Value in seconds => (keepalive / size) "keepalive": 60, } + + def fake_socket(self, login, client): + # Create a fake client object + fake_client = Client( + SocketTransport( + "none", + cert_file="path/to/cert_file", + key_file="path/to/key_file", + password="none", + ) + ) - #self.start_mocks() + return Socket(fake_client, MagicMock()) - def tearDown(self): - #self.mock_send_patch.stop() - #self.mock_connect_patch.stop() - #self.mockSendPatch.stop() - pass + def patch_success(self): + return True - def start_mocks(self): - # Mock a successful connection - self.mock_connect_patch = patch("epplib.client.Client.connect") - self.mocked_connect_function = self.mock_connect_patch.start() - self.mocked_connect_function.side_effect = self.mock_connect - - # Mock the send behaviour - self.mock_send_patch = patch("epplib.client.Client.send") - self.mocked_send_function = self.mock_send_patch.start() - self.mocked_send_function.side_effect = self.mock_send - - # Mock the pool object - self.mockSendPatch = patch("registrar.models.domain.registry._pool") - self.mockedSendFunction = self.mockSendPatch.start() - self.mockedSendFunction.side_effect = self.fake_pool - - def mock_connect(self, _request): - return None - - def mock_send(self, _request): - if isinstance(_request, commands.Login): - response = MagicMock( + def fake_send(self, command, cleaned=None): + mock = MagicMock( code=1000, msg="Command completed successfully", res_data=None, @@ -74,99 +67,110 @@ def mock_send(self, _request): extensions=[], msg_q=None, ) + return mock - return response - return None - - def user_info(self, *args): - return { - "sub": "TEST", - "email": "test@example.com", - "first_name": "Testy", - "last_name": "Tester", - "phone": "814564000", + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + def test_pool_sends_data(self): + """A .send is invoked on the pool successfully""" + self.maxDiff = None + expected_result = { + 'cl_tr_id': None, + 'code': 1000, + 'extensions': [], + 'msg': 'Command completed successfully', + 'msg_q': None, + 'res_data': [info.InfoDomainResultData( + roid='DF1340360-GOV', + statuses=[ + common.Status( + state='serverTransferProhibited', + description=None, + lang='en' + ), + common.Status(state='inactive', + description=None, + lang='en')], + cl_id='gov2023-ote', + cr_id='gov2023-ote', + cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), + up_id='gov2023-ote', + up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + tr_date=None, + name='test3.gov', + registrant='TuaWnx9hnm84GCSU', + admins=[], + nsset=None, + keyset=None, + ex_date=datetime.date(2024, 8, 15), + auth_info=info.DomainAuthInfo(pw='2fooBAR123fooBaz') + ) + ], + 'sv_tr_id': 'wRRNVhKhQW2m6wsUHbo/lA==-29a' } - @patch("djangooidc.views.CLIENT", autospec=True) - def fake_pool(self, mock_client): - # mock client - mock_client.callback.side_effect = self.user_info - # Create a mock transport object - mock_login = MagicMock() - mock_login.cert_file = "path/to/cert_file" - mock_login.key_file = "path/to/key_file" - - pool = EPPConnectionPool( - client=mock_client, login=mock_login, options=self.pool_options - ) - return pool - - @patch("djangooidc.views.CLIENT", autospec=True) - def fake_socket(self, mock_client): - # mock client - mock_client.callback.side_effect = self.user_info - # Create a mock transport object - mock_login = MagicMock() - mock_login.transport.cert_file = "path/to/cert_file" - mock_login.transport.key_file = "path/to/key_file" - return Socket(mock_client, mock_login) - - def test(self, client, login): - mock = MagicMock() - mock.response.code = 1000 - mock().return_value = 1000 - return MagicMock() - - @skip("not implemented yet") - def test_pool_timesout(self): - """The pool timesout and restarts""" - raise - - @skip("not implemented yet") - def test_multiple_users_send_data(self): - """Multiple users send data concurrently""" - raise - - def test_pool_tries_create_invalid(self): - """A .send is invoked on the pool, but the pool - shouldn't be running.""" - # Fake data for the _pool object - domain, _ = Domain.objects.get_or_create(name="freeman.gov") + def fake_client(mock_client): + client = Client( + SocketTransport( + "none", + cert_file="path/to/cert_file", + key_file="path/to/key_file", + password="none", + ) + ) + return client + + # Mock a response from EPP + def fake_receive(command, cleaned=None): + location= Path(__file__).parent / "utility" / "infoDomain.xml" + xml = (location).read_bytes() + return xml + # Mock what happens inside the "with" + with ExitStack() as stack: + stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + stack.enter_context(patch.object(Socket, "connect", fake_client)) + stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) + stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) + # Restart the connection pool, since it starts on app startup + registry.start_connection_pool() + # Pool should be running, and be the right size + self.assertEqual(registry.pool_status.connection_success, True) + self.assertEqual(registry.pool_status.pool_running, True) - # Trigger the getter - should fail - expected_contact = domain.security_contact - self.assertEqual(registry.pool_status.pool_running, False) - self.assertEqual(registry.pool_status.connection_success, False) - self.assertEqual(len(registry._pool.conn), 0) + # Send a command + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - def test_pool_sends_data(self): - """A .send is invoked on the pool successfully""" + self.assertEqual(result.__dict__, expected_result) + # The number of open pools should match the number of requested ones. + # If it is 0, then they failed to open + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + def test_raises_transport_error(self): + """A .send is invoked on the pool, but registry connection is lost + right as we send a command.""" # Fake data for the _pool object - domain, _ = Domain.objects.get_or_create(name="freeman.gov") - - def fake_send(self): - mock = MagicMock( - code=1000, - msg="Command completed successfully", - res_data=None, - cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", - sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", - extensions=[], - msg_q=None, + def fake_client(self): + client = Client( + SocketTransport( + "none", + cert_file="path/to/cert_file", + key_file="path/to/key_file", + password="none", + ) ) - return mock + return client with ExitStack() as stack: stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) - # Pool should be running, and be the right size - self.assertEqual(registry.pool_status.pool_running, True) + stack.enter_context(patch.object(Socket, "connect", fake_client)) + # Restart the connection pool, since it starts on app startup + registry.start_connection_pool() + # Pool should be running self.assertEqual(registry.pool_status.connection_success, True) - registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + self.assertEqual(registry.pool_status.pool_running, True) - #pool.send() - - # Trigger the getter - should succeed - #expected_contact = domain.security_contact + # Try to send a command out - should fail + with self.assertRaises(TransportError): + registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) diff --git a/src/epplibwrapper/tests/utility/infoDomain.xml b/src/epplibwrapper/tests/utility/infoDomain.xml new file mode 100644 index 000000000..541812577 --- /dev/null +++ b/src/epplibwrapper/tests/utility/infoDomain.xml @@ -0,0 +1,33 @@ + + + + + + Command completed successfully + + + + test3.gov + DF1340360-GOV + + + TuaWnx9hnm84GCSU + CONT2 + CONT3 + gov2023-ote + gov2023-ote + 2023-08-15T23:56:36Z + gov2023-ote + 2023-08-17T02:03:19Z + 2024-08-15T23:56:36Z + + 2fooBAR123fooBaz + + + + + wRRNVhKhQW2m6wsUHbo/lA==-29a + + + + From ffcfb9818b4b450b0426dae47d601103a0845dce Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:15:40 -0600 Subject: [PATCH 37/65] Test fix for scanner --- src/epplibwrapper/tests/test_pool.py | 8 ++++++-- src/registrar/models/domain.py | 21 ++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 74a2b687d..b1912b9cf 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -68,11 +68,10 @@ def fake_send(self, command, cleaned=None): msg_q=None, ) return mock - + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_pool_sends_data(self): """A .send is invoked on the pool successfully""" - self.maxDiff = None expected_result = { 'cl_tr_id': None, 'code': 1000, @@ -124,6 +123,7 @@ def fake_receive(command, cleaned=None): location= Path(__file__).parent / "utility" / "infoDomain.xml" xml = (location).read_bytes() return xml + # Mock what happens inside the "with" with ExitStack() as stack: stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) @@ -139,7 +139,11 @@ def fake_receive(command, cleaned=None): # Send a command result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # Should this ever fail, it either means that the schema has changed, + # or the pool is broken. + # If the schema has changed: Update the associated infoDomain.xml file self.assertEqual(result.__dict__, expected_result) + # The number of open pools should match the number of requested ones. # If it is 0, then they failed to open self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index f66221338..68f0600ec 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -17,15 +17,18 @@ NameserverErrorCodes as nsErrorCodes, ) -from epplibwrapper import ( - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, -) +try: + from epplibwrapper import ( + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, + ) +except Exception: + pass from registrar.models.utility.contact_error import ContactError, ContactErrorCodes From f672d15b002f6fa6d603fe16b08e791f967e55b3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:34:18 -0600 Subject: [PATCH 38/65] Update domain.py --- src/registrar/models/domain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 8da56eb1b..b03b98ad1 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -27,7 +27,8 @@ RegistryError, ErrorCode, ) -except Exception: +except Exception as err: + print(f"err is {err}") pass from registrar.models.utility.contact_error import ContactError, ContactErrorCodes From a10ddccd0e903eaa52d7d0304ffbb8dcc2bff6cc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:54:06 -0600 Subject: [PATCH 39/65] Test --- src/epplibwrapper/client.py | 3 ++- src/registrar/models/domain.py | 24 ++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 5381f7ce1..b83f481b1 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -234,7 +234,8 @@ def _test_registry_connection_success(self): try: # Initialize epplib - CLIENT = EPPLibWrapper() + #CLIENT = EPPLibWrapper() + CLIENT = None logger.info("registry client initialized") except Exception: CLIENT = None # type: ignore diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index b03b98ad1..94bdae0a1 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -16,20 +16,16 @@ NameserverError, NameserverErrorCodes as nsErrorCodes, ) - -try: - from epplibwrapper import ( - CLIENT as registry, - commands, - common as epp, - extensions, - info as eppInfo, - RegistryError, - ErrorCode, - ) -except Exception as err: - print(f"err is {err}") - pass + +from epplibwrapper import ( + CLIENT as registry, + commands, + common as epp, + extensions, + info as eppInfo, + RegistryError, + ErrorCode, +) from registrar.models.utility.contact_error import ContactError, ContactErrorCodes From 599b22662d86f9ce90bb7935a4a0ff8dc9917646 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:59:15 -0600 Subject: [PATCH 40/65] Defix the fix --- src/epplibwrapper/__init__.py | 2 -- src/epplibwrapper/client.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/epplibwrapper/__init__.py b/src/epplibwrapper/__init__.py index 81add8e79..d0138d73c 100644 --- a/src/epplibwrapper/__init__.py +++ b/src/epplibwrapper/__init__.py @@ -44,8 +44,6 @@ # Attn: these imports should NOT be at the top of the file try: - from epplibwrapper.socket import Socket - from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes from .client import CLIENT, commands from .errors import RegistryError, ErrorCode, CANNOT_CONTACT_REGISTRY, GENERIC_ERROR from epplib.models import common, info diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index b83f481b1..5381f7ce1 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -234,8 +234,7 @@ def _test_registry_connection_success(self): try: # Initialize epplib - #CLIENT = EPPLibWrapper() - CLIENT = None + CLIENT = EPPLibWrapper() logger.info("registry client initialized") except Exception: CLIENT = None # type: ignore From 730319744f611a28a413ff067e1d472f14348c60 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:26:11 -0600 Subject: [PATCH 41/65] Fix pipfile --- src/Pipfile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Pipfile b/src/Pipfile index 43b919c08..33f2c0954 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -25,6 +25,7 @@ django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"} boto3 = "*" typing-extensions ='*' django-login-required-middleware = "*" +greenlet = "*" gevent = "*" fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"} geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"} From c2cc19ee1a21e1d2a8f4af5f90a09756fa5e1eef Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:28:51 -0600 Subject: [PATCH 42/65] Test --- src/Pipfile.lock | 195 +++++++++++++++--------------- src/epplibwrapper/utility/pool.py | 1 + src/requirements.txt | 14 +-- 3 files changed, 104 insertions(+), 106 deletions(-) diff --git a/src/Pipfile.lock b/src/Pipfile.lock index a7314de46..a773db219 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "67b51a57b7d9d7d70d1eeca3515e169cd614d575a7213f31251f9dde43e1f748" + "sha256": "423c746438717fb7d281dfac02d3950e6e5033c6190f4adf0c63ccdf9433fae5" }, "pipfile-spec": 6, "requires": {}, @@ -32,20 +32,20 @@ }, "boto3": { "hashes": [ - "sha256:65d052ec13197460586ee385aa2d6bba0e7378d2d2c7f3e93c044c43ae1ca782", - "sha256:94218aba2feb5b404b665b8d76c172dc654f79b4c5fa0e9e92459c098da87bf4" + "sha256:38658585791f47cca3fc6aad03838de0136778b533e8c71c6a9590aedc60fbde", + "sha256:a8228522c7db33694c0746dec8b48c05473671626359dd62ab6829eb7871eddc" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.63" + "version": "==1.28.66" }, "botocore": { "hashes": [ - "sha256:6e582c811ea74f25bdb490ac372b2645de4a60286b42ddd8c69f3b6df82b6b12", - "sha256:cb9db5db5af865b1fc2e1405b967db5d78dd0f4d84e5dc1974e082733c1034b7" + "sha256:70e94a5f9bd46b26b63a41fb441ad35f2ae8862ad9d90765b6fa31ccc02c0a19", + "sha256:8d161a97a25eb381721b4b7251d5126ef4ec57e452114250b3e51ba5e4ff45a4" ], "markers": "python_version >= '3.7'", - "version": "==1.31.63" + "version": "==1.31.66" }, "cachetools": { "hashes": [ @@ -366,11 +366,11 @@ }, "faker": { "hashes": [ - "sha256:63da90512d0cb3acdb71bd833bb3071cb8a196020d08b8567a01d232954f1820", - "sha256:f321e657ed61616fbfe14dbb9ccc6b2e8282652bbcfcb503c1bd0231ff834df6" + "sha256:a62a3fd3bfa3122d4f57dfa26a1cc37d76751a76c8ddd63cf9d24078c57913a4", + "sha256:e28090068293c5a83e7f4d636417d45fae1031ca8a8136cc2415549ebc2111e2" ], "markers": "python_version >= '3.8'", - "version": "==19.10.0" + "version": "==19.11.0" }, "fred-epplib": { "git": "https://github.com/cisagov/epplib.git", @@ -439,7 +439,7 @@ }, "geventconnpool": { "git": "https://github.com/rasky/geventconnpool.git", - "ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4" + "ref": null }, "greenlet": { "hashes": [ @@ -506,7 +506,8 @@ "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f", "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a" ], - "markers": "python_version < '3.11' and platform_python_implementation == 'CPython'", + "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==3.0.0" }, "gunicorn": { @@ -740,10 +741,10 @@ }, "phonenumberslite": { "hashes": [ - "sha256:8d1e5f2adfee2a634ccdb54b251dec32c5308fbca3d7f6ae6058f4adee4594a3", - "sha256:98684f21804c6df2e7d224e72d60defee20eddf9e144d57f24cbd9db0df450e0" + "sha256:7c719e35ef551a895459382e9faf592f52647312dd90b543b06460aa0e1c49c4", + "sha256:cf6cf56c889c6787ec6b30b5791693f6dd678f633358f4aeea1fddf98d4cadcb" ], - "version": "==8.13.22" + "version": "==8.13.23" }, "psycopg2-binary": { "hashes": [ @@ -1070,11 +1071,11 @@ }, "urllib3": { "hashes": [ - "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", - "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" + "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", + "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" ], "markers": "python_version >= '3.7'", - "version": "==2.0.6" + "version": "==2.0.7" }, "whitenoise": { "hashes": [ @@ -1164,32 +1165,28 @@ }, "black": { "hashes": [ - "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f", - "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7", - "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100", - "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573", - "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d", - "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f", - "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9", - "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300", - "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948", - "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325", - "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9", - "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71", - "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186", - "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f", - "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe", - "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855", - "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80", - "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393", - "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c", - "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204", - "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377", - "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301" + "sha256:0e232f24a337fed7a82c1185ae46c56c4a6167fb0fe37411b43e876892c76699", + "sha256:30b78ac9b54cf87bcb9910ee3d499d2bc893afd52495066c49d9ee6b21eee06e", + "sha256:31946ec6f9c54ed7ba431c38bc81d758970dd734b96b8e8c2b17a367d7908171", + "sha256:31b9f87b277a68d0e99d2905edae08807c007973eaa609da5f0c62def6b7c0bd", + "sha256:47c4510f70ec2e8f9135ba490811c071419c115e46f143e4dce2ac45afdcf4c9", + "sha256:481167c60cd3e6b1cb8ef2aac0f76165843a374346aeeaa9d86765fe0dd0318b", + "sha256:6901631b937acbee93c75537e74f69463adaf34379a04eef32425b88aca88a23", + "sha256:76baba9281e5e5b230c9b7f83a96daf67a95e919c2dfc240d9e6295eab7b9204", + "sha256:7fb5fc36bb65160df21498d5a3dd330af8b6401be3f25af60c6ebfe23753f747", + "sha256:960c21555be135c4b37b7018d63d6248bdae8514e5c55b71e994ad37407f45b8", + "sha256:a3c2ddb35f71976a4cfeca558848c2f2f89abc86b06e8dd89b5a65c1e6c0f22a", + "sha256:c870bee76ad5f7a5ea7bd01dc646028d05568d33b0b09b7ecfc8ec0da3f3f39c", + "sha256:d3d9129ce05b0829730323bdcb00f928a448a124af5acf90aa94d9aba6969604", + "sha256:db451a3363b1e765c172c3fd86213a4ce63fb8524c938ebd82919bf2a6e28c6a", + "sha256:e223b731a0e025f8ef427dd79d8cd69c167da807f5710add30cdf131f13dd62e", + "sha256:f20ff03f3fdd2fd4460b4f631663813e57dc277e37fb216463f3b907aa5a9bdd", + "sha256:f74892b4b836e5162aa0452393112a574dac85e13902c57dfbaaf388e4eda37c", + "sha256:f8dc7d50d94063cdfd13c82368afd8588bac4ce360e4224ac399e769d6704e98" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==23.9.1" + "version": "==23.10.0" }, "blinker": { "hashes": [ @@ -1201,12 +1198,12 @@ }, "boto3": { "hashes": [ - "sha256:65d052ec13197460586ee385aa2d6bba0e7378d2d2c7f3e93c044c43ae1ca782", - "sha256:94218aba2feb5b404b665b8d76c172dc654f79b4c5fa0e9e92459c098da87bf4" + "sha256:38658585791f47cca3fc6aad03838de0136778b533e8c71c6a9590aedc60fbde", + "sha256:a8228522c7db33694c0746dec8b48c05473671626359dd62ab6829eb7871eddc" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.63" + "version": "==1.28.66" }, "boto3-mocking": { "hashes": [ @@ -1219,28 +1216,28 @@ }, "boto3-stubs": { "hashes": [ - "sha256:bd1becb0f8781d0a3022261a41d33f757a121117bf84ea6476b4761cb9e3cfd5", - "sha256:ecf4fb2b5b71be52cfc970ee059fe17439ed1904d0395508f5545c380d4d951d" + "sha256:06e696ce8529f899a2ba388d6604ca8ed8ba367dd53898e27b4ce49e8b3fd2aa", + "sha256:b85689c50a6768bb0fcb85e06394d7898b330b82f34cec26c36d912e6a41280d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==1.28.63" + "version": "==1.28.66" }, "botocore": { "hashes": [ - "sha256:6e582c811ea74f25bdb490ac372b2645de4a60286b42ddd8c69f3b6df82b6b12", - "sha256:cb9db5db5af865b1fc2e1405b967db5d78dd0f4d84e5dc1974e082733c1034b7" + "sha256:70e94a5f9bd46b26b63a41fb441ad35f2ae8862ad9d90765b6fa31ccc02c0a19", + "sha256:8d161a97a25eb381721b4b7251d5126ef4ec57e452114250b3e51ba5e4ff45a4" ], "markers": "python_version >= '3.7'", - "version": "==1.31.63" + "version": "==1.31.66" }, "botocore-stubs": { "hashes": [ - "sha256:873715a5c21d0f4593628393c78e47cf94e53a43e40863a9ef5f165fcdcf900f", - "sha256:e92b5bd8d2667e557ea25025b396613c9bcb33d18b1971f98ebc24fa54caf495" + "sha256:5ea5f4af18ee654cf510d69b3bc7c1ed3236b50fcd4e3eb98c11d28033ff05c3", + "sha256:b65fa3ff36e8a70518a143b5025559918a68d7a20b85c88f8a1f067f6620a205" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.31.63" + "version": "==1.31.66" }, "click": { "hashes": [ @@ -1277,20 +1274,20 @@ }, "django-stubs": { "hashes": [ - "sha256:7d4a132c381519815e865c27a89eca41bcbd06056832507224816a43d75c601c", - "sha256:834b60fd81510cce6b56c1c6c28bec3c504a418bc90ff7d0063fabe8ab9a7868" + "sha256:5a23cf622f1426a0b0c48bd6e2ef709a66275d72073baf6fdf5ac36fc4cce736", + "sha256:706b2456bd0e56c468dfd8f27b0e7dde001c5c7cd3010d67fcbda9d95467e050" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.2.4" + "version": "==4.2.5" }, "django-stubs-ext": { "hashes": [ - "sha256:c69d1cc46f1c4c3b7894b685a5022c29b2a36c7cfb52e23762eaf357ebfc2c98", - "sha256:fdacc65a14d2d4b97334b58ff178a5853ec8c8c76cec406e417916ad67536ce4" + "sha256:8c4d1fb5f68419b3b2474c659681a189803e27d6a5e5abf5aa0da57601b58633", + "sha256:921cd7ae4614e74c234bc0fe86ee75537d163addfe1fc6f134bf03e29d86c01e" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.2.5" }, "django-webtest": { "hashes": [ @@ -1319,11 +1316,11 @@ }, "gitpython": { "hashes": [ - "sha256:5f4c4187de49616d710a77e98ddf17b4782060a1788df441846bddefbb89ab33", - "sha256:f9b9ddc0761c125d5780eab2d64be4873fc6817c2899cbcb34b02344bdc7bc54" + "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4", + "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a" ], "markers": "python_version >= '3.7'", - "version": "==3.1.37" + "version": "==3.1.40" }, "jmespath": { "hashes": [ @@ -1359,37 +1356,37 @@ }, "mypy": { "hashes": [ - "sha256:091f53ff88cb093dcc33c29eee522c087a438df65eb92acd371161c1f4380ff0", - "sha256:1a69db3018b87b3e6e9dd28970f983ea6c933800c9edf8c503c3135b3274d5ad", - "sha256:24f3de8b9e7021cd794ad9dfbf2e9fe3f069ff5e28cb57af6f873ffec1cb0425", - "sha256:31eba8a7a71f0071f55227a8057468b8d2eb5bf578c8502c7f01abaec8141b2f", - "sha256:3c8835a07b8442da900db47ccfda76c92c69c3a575872a5b764332c4bacb5a0a", - "sha256:3df87094028e52766b0a59a3e46481bb98b27986ed6ded6a6cc35ecc75bb9182", - "sha256:49499cf1e464f533fc45be54d20a6351a312f96ae7892d8e9f1708140e27ce41", - "sha256:4c192445899c69f07874dabda7e931b0cc811ea055bf82c1ababf358b9b2a72c", - "sha256:4f3d27537abde1be6d5f2c96c29a454da333a2a271ae7d5bc7110e6d4b7beb3f", - "sha256:7469545380dddce5719e3656b80bdfbb217cfe8dbb1438532d6abc754b828fed", - "sha256:7807a2a61e636af9ca247ba8494031fb060a0a744b9fee7de3a54bed8a753323", - "sha256:856bad61ebc7d21dbc019b719e98303dc6256cec6dcc9ebb0b214b81d6901bd8", - "sha256:89513ddfda06b5c8ebd64f026d20a61ef264e89125dc82633f3c34eeb50e7d60", - "sha256:8e0db37ac4ebb2fee7702767dfc1b773c7365731c22787cb99f507285014fcaf", - "sha256:971104bcb180e4fed0d7bd85504c9036346ab44b7416c75dd93b5c8c6bb7e28f", - "sha256:9e1589ca150a51d9d00bb839bfeca2f7a04f32cd62fad87a847bc0818e15d7dc", - "sha256:9f8464ed410ada641c29f5de3e6716cbdd4f460b31cf755b2af52f2d5ea79ead", - "sha256:ab98b8f6fdf669711f3abe83a745f67f50e3cbaea3998b90e8608d2b459fd566", - "sha256:b19006055dde8a5425baa5f3b57a19fa79df621606540493e5e893500148c72f", - "sha256:c69051274762cccd13498b568ed2430f8d22baa4b179911ad0c1577d336ed849", - "sha256:d2dad072e01764823d4b2f06bc7365bb1d4b6c2f38c4d42fade3c8d45b0b4b67", - "sha256:dccd850a2e3863891871c9e16c54c742dba5470f5120ffed8152956e9e0a5e13", - "sha256:e28d7b221898c401494f3b77db3bac78a03ad0a0fff29a950317d87885c655d2", - "sha256:e4b7a99275a61aa22256bab5839c35fe8a6887781862471df82afb4b445daae6", - "sha256:eb7ff4007865833c470a601498ba30462b7374342580e2346bf7884557e40531", - "sha256:f8598307150b5722854f035d2e70a1ad9cc3c72d392c34fffd8c66d888c90f17", - "sha256:fea451a3125bf0bfe716e5d7ad4b92033c471e4b5b3e154c67525539d14dc15a" + "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7", + "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e", + "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c", + "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169", + "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208", + "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0", + "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1", + "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1", + "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7", + "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45", + "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143", + "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5", + "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f", + "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd", + "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245", + "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f", + "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332", + "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30", + "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183", + "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f", + "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85", + "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46", + "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71", + "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660", + "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb", + "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c", + "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.6.0" + "version": "==1.6.1" }, "mypy-extensions": { "hashes": [ @@ -1593,11 +1590,11 @@ }, "types-awscrt": { "hashes": [ - "sha256:477a14565909312fe1de70d0b301548e83c038f436b8a1d7c83729e87cdd0b85", - "sha256:d8c379420ba75b1e43687d12b0b772a5bb17f352859a2bef6aa8f0abde123f55" + "sha256:7b55f5a12ccd4407bc8f1e35c69bb40c931f8513ce1ad81a4527fce3989003fd", + "sha256:9a21caac4287c113dd52665707785c45bb1d3242b7a2b8aeb57c49e9e749a330" ], "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==0.19.2" + "version": "==0.19.3" }, "types-cachetools": { "hashes": [ @@ -1623,12 +1620,12 @@ }, "types-requests": { "hashes": [ - "sha256:140e323da742a0cd0ff0a5a83669da9ffcebfaeb855d367186b2ec3985ba2742", - "sha256:3bb11188795cc3aa39f9635032044ee771009370fb31c3a06ae952b267b6fcd7" + "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc", + "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.31.0.9" + "version": "==2.31.0.10" }, "types-s3transfer": { "hashes": [ @@ -1649,11 +1646,11 @@ }, "urllib3": { "hashes": [ - "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", - "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" + "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", + "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" ], "markers": "python_version >= '3.7'", - "version": "==2.0.6" + "version": "==2.0.7" }, "waitress": { "hashes": [ diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 3b8eb240c..2fff8e170 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,4 +1,5 @@ import logging +import greenlet import gevent from geventconnpool import ConnectionPool from epplibwrapper.socket import Socket diff --git a/src/requirements.txt b/src/requirements.txt index 0e3d41d87..109262b07 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,8 +1,8 @@ -i https://pypi.python.org/simple annotated-types==0.6.0; python_version >= '3.8' asgiref==3.7.2; python_version >= '3.7' -boto3==1.28.63; python_version >= '3.7' -botocore==1.31.63; python_version >= '3.7' +boto3==1.28.66; python_version >= '3.7' +botocore==1.31.66; python_version >= '3.7' cachetools==5.3.1; python_version >= '3.7' certifi==2023.7.22; python_version >= '3.6' cfenv==0.5.3 @@ -22,13 +22,13 @@ django-login-required-middleware==0.9.0 django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8' django-widget-tweaks==1.5.0; python_version >= '3.8' environs[django]==9.5.0; python_version >= '3.6' -faker==19.10.0; python_version >= '3.8' +faker==19.11.0; python_version >= '3.8' fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c furl==2.1.3 future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' gevent==23.9.1; python_version >= '3.8' -geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4 -greenlet==3.0.0; python_version < '3.11' and platform_python_implementation == 'CPython' +geventconnpool@ git+https://github.com/rasky/geventconnpool.git +greenlet==3.0.0; python_version >= '3.7' gunicorn==21.2.0; python_version >= '3.5' idna==3.4; python_version >= '3.5' jmespath==1.0.1; python_version >= '3.7' @@ -39,7 +39,7 @@ marshmallow==3.20.1; python_version >= '3.8' oic==1.6.1; python_version ~= '3.7' orderedmultidict==1.0.1 packaging==23.2; python_version >= '3.7' -phonenumberslite==8.13.22 +phonenumberslite==8.13.23 psycopg2-binary==2.9.9; python_version >= '3.7' pycparser==2.21 pycryptodomex==3.19.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' @@ -55,7 +55,7 @@ setuptools==68.2.2; python_version >= '3.8' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sqlparse==0.4.4; python_version >= '3.5' typing-extensions==4.8.0; python_version >= '3.8' -urllib3==2.0.6; python_version >= '3.7' +urllib3==2.0.7; python_version >= '3.7' whitenoise==6.6.0; python_version >= '3.8' zope.event==5.0; python_version >= '3.7' zope.interface==6.1; python_version >= '3.7' From 9a12e29c5a4e0913d679ee8402e5fa009149df29 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:41:38 -0600 Subject: [PATCH 43/65] Linter --- src/epplibwrapper/client.py | 14 +--- src/epplibwrapper/socket.py | 4 +- src/epplibwrapper/tests/test_pool.py | 115 ++++++++++++++------------- src/epplibwrapper/utility/pool.py | 2 +- src/registrar/config/settings.py | 2 +- src/registrar/models/domain.py | 2 +- 6 files changed, 66 insertions(+), 73 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 5381f7ce1..01998b955 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -163,16 +163,12 @@ def send(self, command, *, cleaned=False): def get_pool(self): """Get the current pool instance""" return self._pool - + def _create_pool(self, client, login, options): """Creates and returns new pool instance""" - return EPPConnectionPool( - client, login, options - ) + return EPPConnectionPool(client, login, options) - def start_connection_pool( - self, restart_pool_if_exists=True - ): + def start_connection_pool(self, restart_pool_if_exists=True): """Starts a connection pool for the registry. restart_pool_if_exists -> bool: @@ -199,9 +195,7 @@ def start_connection_pool( logger.info("Connection pool restarting...") self.kill_pool() - self._pool = self._create_pool( - self._client, self._login, self.pool_options - ) + self._pool = self._create_pool(self._client, self._login, self.pool_options) self.pool_status.pool_running = True self.pool_status.pool_hanging = False diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 186b98322..00cad80af 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -59,8 +59,6 @@ def test_connection_success(self): counter += 1 sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms else: # don't try again - logger.warning("LoginError raised and should not retry or has been retried 3 times already") - logger.warning(f"should retry? {err.should_retry()}") return False else: self.disconnect() @@ -69,7 +67,7 @@ def test_connection_success(self): if self.is_login_error(response.code): logger.warning("was login error") return False - + # otherwise, just return true return True diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index b1912b9cf..077b059ea 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -1,15 +1,11 @@ import datetime from pathlib import Path -from unittest import skip from unittest.mock import MagicMock, patch from dateutil.tz import tzlocal -from django.conf import settings - from django.test import TestCase from epplibwrapper.client import EPPLibWrapper from epplibwrapper.socket import Socket from epplibwrapper.utility.pool import EPPConnectionPool -from registrar.models.domain import Domain from registrar.models.domain import registry from contextlib import ExitStack @@ -40,7 +36,7 @@ def setUp(self): # Value in seconds => (keepalive / size) "keepalive": 60, } - + def fake_socket(self, login, client): # Create a fake client object fake_client = Client( @@ -56,55 +52,57 @@ def fake_socket(self, login, client): def patch_success(self): return True - + def fake_send(self, command, cleaned=None): - mock = MagicMock( - code=1000, - msg="Command completed successfully", - res_data=None, - cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", - sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", - extensions=[], - msg_q=None, - ) - return mock - + mock = MagicMock( + code=1000, + msg="Command completed successfully", + res_data=None, + cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376", + sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a", + extensions=[], + msg_q=None, + ) + return mock + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_pool_sends_data(self): """A .send is invoked on the pool successfully""" expected_result = { - 'cl_tr_id': None, - 'code': 1000, - 'extensions': [], - 'msg': 'Command completed successfully', - 'msg_q': None, - 'res_data': [info.InfoDomainResultData( - roid='DF1340360-GOV', - statuses=[ - common.Status( - state='serverTransferProhibited', - description=None, - lang='en' - ), - common.Status(state='inactive', - description=None, - lang='en')], - cl_id='gov2023-ote', - cr_id='gov2023-ote', - cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()), - up_id='gov2023-ote', - up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), - tr_date=None, - name='test3.gov', - registrant='TuaWnx9hnm84GCSU', - admins=[], - nsset=None, - keyset=None, - ex_date=datetime.date(2024, 8, 15), - auth_info=info.DomainAuthInfo(pw='2fooBAR123fooBaz') - ) - ], - 'sv_tr_id': 'wRRNVhKhQW2m6wsUHbo/lA==-29a' + "cl_tr_id": None, + "code": 1000, + "extensions": [], + "msg": "Command completed successfully", + "msg_q": None, + "res_data": [ + info.InfoDomainResultData( + roid="DF1340360-GOV", + statuses=[ + common.Status( + state="serverTransferProhibited", + description=None, + lang="en", + ), + common.Status(state="inactive", description=None, lang="en"), + ], + cl_id="gov2023-ote", + cr_id="gov2023-ote", + cr_date=datetime.datetime( + 2023, 8, 15, 23, 56, 36, tzinfo=tzlocal() + ), + up_id="gov2023-ote", + up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + tr_date=None, + name="test3.gov", + registrant="TuaWnx9hnm84GCSU", + admins=[], + nsset=None, + keyset=None, + ex_date=datetime.date(2024, 8, 15), + auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), + ) + ], + "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", } def fake_client(mock_client): @@ -117,16 +115,18 @@ def fake_client(mock_client): ) ) return client - + # Mock a response from EPP def fake_receive(command, cleaned=None): - location= Path(__file__).parent / "utility" / "infoDomain.xml" + location = Path(__file__).parent / "utility" / "infoDomain.xml" xml = (location).read_bytes() return xml # Mock what happens inside the "with" with ExitStack() as stack: - stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + stack.enter_context( + patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) + ) stack.enter_context(patch.object(Socket, "connect", fake_client)) stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) @@ -141,17 +141,18 @@ def fake_receive(command, cleaned=None): # Should this ever fail, it either means that the schema has changed, # or the pool is broken. - # If the schema has changed: Update the associated infoDomain.xml file + # If the schema has changed: Update the associated infoDomain.xml file self.assertEqual(result.__dict__, expected_result) # The number of open pools should match the number of requested ones. # If it is 0, then they failed to open self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) - + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_raises_transport_error(self): """A .send is invoked on the pool, but registry connection is lost right as we send a command.""" + # Fake data for the _pool object def fake_client(self): client = Client( @@ -165,7 +166,9 @@ def fake_client(self): return client with ExitStack() as stack: - stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + stack.enter_context( + patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) + ) stack.enter_context(patch.object(Socket, "connect", fake_client)) # Restart the connection pool, since it starts on app startup registry.start_connection_pool() @@ -176,5 +179,3 @@ def fake_client(self): # Try to send a command out - should fail with self.assertRaises(TransportError): registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - - diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 2fff8e170..ba7edec91 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,9 +1,9 @@ import logging -import greenlet import gevent from geventconnpool import ConnectionPool from epplibwrapper.socket import Socket from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes + try: from epplib.commands import Hello except ImportError: diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index d4b0af408..2e88154ba 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -546,7 +546,7 @@ # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -POOL_KEEP_ALIVE = 60 +POOL_KEEP_ALIVE = 60 # Determines how long we try to keep a pool alive for, # before restarting it. diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 94bdae0a1..2bfdd58c5 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -16,7 +16,7 @@ NameserverError, NameserverErrorCodes as nsErrorCodes, ) - + from epplibwrapper import ( CLIENT as registry, commands, From ceb2e5ec66c8d8a32f224d170fb2238d40af82d2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 18 Oct 2023 16:03:03 -0600 Subject: [PATCH 44/65] Remove unused code --- src/epplibwrapper/client.py | 3 +-- src/epplibwrapper/errors.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 01998b955..6fafb2fd6 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -1,10 +1,9 @@ """Provide a wrapper around epplib to handle authentication and errors.""" import logging -from time import sleep +from time import sleep from gevent import Timeout - from epplibwrapper.utility.pool_status import PoolStatus try: diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index 0223ec0ae..dba5f328c 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -83,9 +83,6 @@ def is_server_error(self): def is_client_error(self): return self.code is not None and (self.code >= 2000 and self.code <= 2308) - def is_not_retryable(self): - pass - class LoginError(RegistryError): pass From 825d07ba7c6b755750072f73a70a8127f7382551 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 07:59:09 -0600 Subject: [PATCH 45/65] Debug client bug --- src/epplibwrapper/client.py | 58 +++++++++++++++++++------ src/epplibwrapper/errors.py | 2 + src/epplibwrapper/socket.py | 9 ++-- src/epplibwrapper/utility/pool_error.py | 5 +++ 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 6fafb2fd6..f068bfad4 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -4,14 +4,18 @@ from time import sleep from gevent import Timeout +from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes from epplibwrapper.utility.pool_status import PoolStatus +logger = logging.getLogger(__name__) + try: from epplib.client import Client from epplib import commands from epplib.exceptions import TransportError, ParsingError from epplib.transport import SocketTransport except ImportError: + logger.warning("There was an import error {}") pass from django.conf import settings @@ -21,7 +25,7 @@ from .socket import Socket from .utility.pool import EPPConnectionPool -logger = logging.getLogger(__name__) + try: # Write cert and key to disk @@ -55,15 +59,11 @@ def __init__(self, start_connection_pool=True) -> None: ], ) + # TODO - if client is none, send signal up and set it + # back to this # establish a client object with a TCP socket transport - self._client = Client( - SocketTransport( - settings.SECRET_REGISTRY_HOSTNAME, - cert_file=CERT.filename, - key_file=KEY.filename, - password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, - ) - ) + self._client = self._get_default_client() + logger.warning(f"client is this {self._client}") self.pool_options = { # Pool size @@ -82,6 +82,16 @@ def __init__(self, start_connection_pool=True) -> None: if start_connection_pool: self.start_connection_pool() + def _get_default_client(self): + return Client( + SocketTransport( + settings.SECRET_REGISTRY_HOSTNAME, + cert_file=CERT.filename, + key_file=KEY.filename, + password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, + ) + ) + def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ @@ -217,12 +227,34 @@ def _test_registry_connection_success(self): credentials are valid, and/or if the Registrar can be contacted """ - socket = Socket(self._login, self._client) - can_login = False + socket = self._create_default_socket() + can_login = True # Something went wrong if this doesn't exist - if hasattr(socket, "test_connection_success"): + if not hasattr(socket, "test_connection_success"): + return can_login + + try: can_login = socket.test_connection_success() - return can_login + except PoolError as err: + logger.error(err) + # If the client isn't the right type, + # recreate it. + if err.code == PoolErrorCodes.INVALID_CLIENT_TYPE: + # Try to recreate the socket + self._client = self._get_default_client() + socket = self._create_default_socket() + + # Test it again + can_login = socket.test_connection_success() + return can_login + else: + return can_login + + def _create_default_socket(self): + """Creates a default socket. + Uses self._login and self._client + """ + return Socket(self._login, self._client) try: diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index dba5f328c..96188750c 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -86,3 +86,5 @@ def is_client_error(self): class LoginError(RegistryError): pass + + diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 00cad80af..63fab9743 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -1,13 +1,15 @@ import logging from time import sleep +from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes + try: from epplib import commands from epplib.client import Client except ImportError: pass -from .errors import LoginError +from .errors import LoginError, SocketError logger = logging.getLogger(__name__) @@ -46,8 +48,9 @@ def test_connection_success(self): Tries 3 times""" # Something went wrong if this doesn't exist if not hasattr(self.client, "connect"): - logger.warning("self.client does not have a connect method") - return False + message = "self.client does not have a connect method" + logger.warning(message) + raise PoolError(code=PoolErrorCodes.INVALID_CLIENT_TYPE) counter = 0 # we'll try 3 times while True: diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py index 70312f32e..2febcaaa0 100644 --- a/src/epplibwrapper/utility/pool_error.py +++ b/src/epplibwrapper/utility/pool_error.py @@ -9,11 +9,13 @@ class PoolErrorCodes(IntEnum): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED + - 2003 INVALID_CLIENT_TYPE """ KILL_ALL_FAILED = 2000 NEW_CONNECTION_FAILED = 2001 KEEP_ALIVE_FAILED = 2002 + INVALID_CLIENT_TYPE = 2003 class PoolError(Exception): @@ -22,16 +24,19 @@ class PoolError(Exception): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED + - 2003 INVALID_CLIENT_TYPE """ # For linter kill_failed = "Could not kill all connections." conn_failed = "Failed to execute due to a registry error." alive_failed = "Failed to keep the connection alive." + invalid_client = "Invalid client type." _error_mapping = { PoolErrorCodes.KILL_ALL_FAILED: kill_failed, PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed, PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed, + PoolErrorCodes.INVALID_CLIENT_TYPE: invalid_client } def __init__(self, *args, code=None, **kwargs): From d4633aeef2816376e99ad1845ea166c6101be164 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:05:45 -0600 Subject: [PATCH 46/65] Update test_pool.py --- src/epplibwrapper/tests/test_pool.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 077b059ea..d7b4d4aad 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -170,8 +170,7 @@ def fake_client(self): patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) ) stack.enter_context(patch.object(Socket, "connect", fake_client)) - # Restart the connection pool, since it starts on app startup - registry.start_connection_pool() + # Pool should be running self.assertEqual(registry.pool_status.connection_success, True) self.assertEqual(registry.pool_status.pool_running, True) From 47afb0339faa72d0f4fafb86be1d85c6f4a104e4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:50:53 -0600 Subject: [PATCH 47/65] Update broken piplock --- src/Pipfile.lock | 2 +- src/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Pipfile.lock b/src/Pipfile.lock index a773db219..9d7daf597 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -439,7 +439,7 @@ }, "geventconnpool": { "git": "https://github.com/rasky/geventconnpool.git", - "ref": null + "ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4" }, "greenlet": { "hashes": [ diff --git a/src/requirements.txt b/src/requirements.txt index 109262b07..ff289ea63 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -27,7 +27,7 @@ fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a furl==2.1.3 future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3' gevent==23.9.1; python_version >= '3.8' -geventconnpool@ git+https://github.com/rasky/geventconnpool.git +geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4 greenlet==3.0.0; python_version >= '3.7' gunicorn==21.2.0; python_version >= '3.5' idna==3.4; python_version >= '3.5' From 6262a4cf39dcae754cd74a0d45d17bb4d58384d0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:00:56 -0600 Subject: [PATCH 48/65] Revert "Debug client bug" This reverts commit 825d07ba7c6b755750072f73a70a8127f7382551. --- src/epplibwrapper/client.py | 58 ++++++------------------- src/epplibwrapper/errors.py | 2 - src/epplibwrapper/socket.py | 9 ++-- src/epplibwrapper/utility/pool_error.py | 5 --- 4 files changed, 16 insertions(+), 58 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index f068bfad4..6fafb2fd6 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -4,18 +4,14 @@ from time import sleep from gevent import Timeout -from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes from epplibwrapper.utility.pool_status import PoolStatus -logger = logging.getLogger(__name__) - try: from epplib.client import Client from epplib import commands from epplib.exceptions import TransportError, ParsingError from epplib.transport import SocketTransport except ImportError: - logger.warning("There was an import error {}") pass from django.conf import settings @@ -25,7 +21,7 @@ from .socket import Socket from .utility.pool import EPPConnectionPool - +logger = logging.getLogger(__name__) try: # Write cert and key to disk @@ -59,11 +55,15 @@ def __init__(self, start_connection_pool=True) -> None: ], ) - # TODO - if client is none, send signal up and set it - # back to this # establish a client object with a TCP socket transport - self._client = self._get_default_client() - logger.warning(f"client is this {self._client}") + self._client = Client( + SocketTransport( + settings.SECRET_REGISTRY_HOSTNAME, + cert_file=CERT.filename, + key_file=KEY.filename, + password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, + ) + ) self.pool_options = { # Pool size @@ -82,16 +82,6 @@ def __init__(self, start_connection_pool=True) -> None: if start_connection_pool: self.start_connection_pool() - def _get_default_client(self): - return Client( - SocketTransport( - settings.SECRET_REGISTRY_HOSTNAME, - cert_file=CERT.filename, - key_file=KEY.filename, - password=settings.SECRET_REGISTRY_KEY_PASSPHRASE, - ) - ) - def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ @@ -227,34 +217,12 @@ def _test_registry_connection_success(self): credentials are valid, and/or if the Registrar can be contacted """ - socket = self._create_default_socket() - can_login = True + socket = Socket(self._login, self._client) + can_login = False # Something went wrong if this doesn't exist - if not hasattr(socket, "test_connection_success"): - return can_login - - try: + if hasattr(socket, "test_connection_success"): can_login = socket.test_connection_success() - except PoolError as err: - logger.error(err) - # If the client isn't the right type, - # recreate it. - if err.code == PoolErrorCodes.INVALID_CLIENT_TYPE: - # Try to recreate the socket - self._client = self._get_default_client() - socket = self._create_default_socket() - - # Test it again - can_login = socket.test_connection_success() - return can_login - else: - return can_login - - def _create_default_socket(self): - """Creates a default socket. - Uses self._login and self._client - """ - return Socket(self._login, self._client) + return can_login try: diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index 96188750c..dba5f328c 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -86,5 +86,3 @@ def is_client_error(self): class LoginError(RegistryError): pass - - diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 63fab9743..00cad80af 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -1,15 +1,13 @@ import logging from time import sleep -from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes - try: from epplib import commands from epplib.client import Client except ImportError: pass -from .errors import LoginError, SocketError +from .errors import LoginError logger = logging.getLogger(__name__) @@ -48,9 +46,8 @@ def test_connection_success(self): Tries 3 times""" # Something went wrong if this doesn't exist if not hasattr(self.client, "connect"): - message = "self.client does not have a connect method" - logger.warning(message) - raise PoolError(code=PoolErrorCodes.INVALID_CLIENT_TYPE) + logger.warning("self.client does not have a connect method") + return False counter = 0 # we'll try 3 times while True: diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py index 2febcaaa0..70312f32e 100644 --- a/src/epplibwrapper/utility/pool_error.py +++ b/src/epplibwrapper/utility/pool_error.py @@ -9,13 +9,11 @@ class PoolErrorCodes(IntEnum): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED - - 2003 INVALID_CLIENT_TYPE """ KILL_ALL_FAILED = 2000 NEW_CONNECTION_FAILED = 2001 KEEP_ALIVE_FAILED = 2002 - INVALID_CLIENT_TYPE = 2003 class PoolError(Exception): @@ -24,19 +22,16 @@ class PoolError(Exception): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED - - 2003 INVALID_CLIENT_TYPE """ # For linter kill_failed = "Could not kill all connections." conn_failed = "Failed to execute due to a registry error." alive_failed = "Failed to keep the connection alive." - invalid_client = "Invalid client type." _error_mapping = { PoolErrorCodes.KILL_ALL_FAILED: kill_failed, PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed, PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed, - PoolErrorCodes.INVALID_CLIENT_TYPE: invalid_client } def __init__(self, *args, code=None, **kwargs): From 352a895d8d3d488dd855659ae4fcbf046e65c10d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:07:35 -0600 Subject: [PATCH 49/65] Fix socket bug --- src/epplibwrapper/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 6fafb2fd6..4a65e63ea 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -217,7 +217,7 @@ def _test_registry_connection_success(self): credentials are valid, and/or if the Registrar can be contacted """ - socket = Socket(self._login, self._client) + socket = Socket(self._client, self._login) can_login = False # Something went wrong if this doesn't exist if hasattr(socket, "test_connection_success"): From b663ac7b713d9d78f6c31cdee64fb5acee78fc88 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:28:11 -0600 Subject: [PATCH 50/65] Fix edge case on localhost --- src/epplibwrapper/client.py | 3 ++- src/epplibwrapper/socket.py | 4 ++++ src/epplibwrapper/tests/test_pool.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 4a65e63ea..77e152d0e 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -229,8 +229,9 @@ def _test_registry_connection_success(self): # Initialize epplib CLIENT = EPPLibWrapper() logger.info("registry client initialized") -except Exception: +except Exception as err: CLIENT = None # type: ignore logger.warning( "Unable to configure epplib. Registrar cannot contact registry.", exc_info=True ) + logger.warning(err) diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 00cad80af..8329e36cf 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -60,6 +60,10 @@ def test_connection_success(self): sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms else: # don't try again return False + # Occurs when an invalid creds are passed in - such as on localhost + except OSError as err: + logger.error(err) + return False else: self.disconnect() diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index d7b4d4aad..f82d5ee6a 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -170,7 +170,7 @@ def fake_client(self): patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) ) stack.enter_context(patch.object(Socket, "connect", fake_client)) - + # Pool should be running self.assertEqual(registry.pool_status.connection_success, True) self.assertEqual(registry.pool_status.pool_running, True) From e91ee079d097c7c371a0a8a37ded97a2914fe4b3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:40:40 -0600 Subject: [PATCH 51/65] Linter + fix test --- src/epplibwrapper/tests/test_pool.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index f82d5ee6a..edcd48981 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -4,6 +4,7 @@ from dateutil.tz import tzlocal from django.test import TestCase from epplibwrapper.client import EPPLibWrapper +from epplibwrapper.errors import RegistryError from epplibwrapper.socket import Socket from epplibwrapper.utility.pool import EPPConnectionPool from registrar.models.domain import registry @@ -38,13 +39,15 @@ def setUp(self): } def fake_socket(self, login, client): + # Linter reasons + pw = "none" # Create a fake client object fake_client = Client( SocketTransport( "none", cert_file="path/to/cert_file", key_file="path/to/key_file", - password="none", + password=pw, ) ) @@ -149,7 +152,7 @@ def fake_receive(command, cleaned=None): self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) - def test_raises_transport_error(self): + def test_raises_connection_error(self): """A .send is invoked on the pool, but registry connection is lost right as we send a command.""" @@ -176,5 +179,7 @@ def fake_client(self): self.assertEqual(registry.pool_status.pool_running, True) # Try to send a command out - should fail - with self.assertRaises(TransportError): - registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + with self.assertRaises(RegistryError): + expected_message = "InfoDomain failed to execute due to a connection error." + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + self.assertEqual(result, expected_message) From 29dc3111aefe4e4e64bda8100fa34eced38b2cf3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:59:31 -0600 Subject: [PATCH 52/65] Linter pt. 2 --- src/epplibwrapper/tests/test_pool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index edcd48981..a3ac66e54 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -109,12 +109,13 @@ def test_pool_sends_data(self): } def fake_client(mock_client): + pw = "none" client = Client( SocketTransport( "none", cert_file="path/to/cert_file", key_file="path/to/key_file", - password="none", + password=pw, ) ) return client From aabe4df12a4b56e8b75493c440c287876ea5b651 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:11:23 -0600 Subject: [PATCH 53/65] Linter pt. 3 --- src/epplibwrapper/tests/test_pool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index a3ac66e54..9fb546bd1 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -159,12 +159,13 @@ def test_raises_connection_error(self): # Fake data for the _pool object def fake_client(self): + pw = "none" client = Client( SocketTransport( "none", cert_file="path/to/cert_file", key_file="path/to/key_file", - password="none", + password=pw, ) ) return client From a744b9407fa21e9a014614877bd387185efe6b7f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:20:20 -0600 Subject: [PATCH 54/65] Lint pt. many --- src/epplibwrapper/tests/test_pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 9fb546bd1..24b8b7b31 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -1,7 +1,7 @@ import datetime from pathlib import Path from unittest.mock import MagicMock, patch -from dateutil.tz import tzlocal +from dateutil.tz import tzlocal # type: ignore from django.test import TestCase from epplibwrapper.client import EPPLibWrapper from epplibwrapper.errors import RegistryError From b8dfe0b8f053520ea2f0d1fefdb2d34c3231a8eb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:25:51 -0600 Subject: [PATCH 55/65] Update test_pool.py --- src/epplibwrapper/tests/test_pool.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 24b8b7b31..35e5d9378 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -1,7 +1,7 @@ import datetime from pathlib import Path from unittest.mock import MagicMock, patch -from dateutil.tz import tzlocal # type: ignore +from dateutil.tz import tzlocal # type: ignore from django.test import TestCase from epplibwrapper.client import EPPLibWrapper from epplibwrapper.errors import RegistryError @@ -182,6 +182,8 @@ def fake_client(self): # Try to send a command out - should fail with self.assertRaises(RegistryError): - expected_message = "InfoDomain failed to execute due to a connection error." - result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) - self.assertEqual(result, expected_message) + expected = "InfoDomain failed to execute due to a connection error." + result = registry.send( + commands.InfoDomain(name="test.gov"), cleaned=True + ) + self.assertEqual(result, expected) From e7d73d2254224fd293705ed092d798ff006dd630 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:52:32 -0600 Subject: [PATCH 56/65] Update test_models_domain.py --- src/registrar/tests/test_models_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index ef3084f9c..f0522b36d 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -873,7 +873,7 @@ def test_contact_getter_registrant(self): contact_id="regContact", contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ) - + # test commit - will remove self.assertEqual( self.domain_contact.registrant_contact.email, expected_contact.email ) From 055942fe3aed798d56b35248bf776ed4cc1b3081 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:59:28 -0600 Subject: [PATCH 57/65] Fix kill_pool() Kill pool was not killing instances correctly. This fixes that, and adds an additional test case --- src/epplibwrapper/client.py | 23 +++-- src/epplibwrapper/socket.py | 43 ++++++---- src/epplibwrapper/tests/test_pool.py | 124 +++++++++++++++++++++------ src/epplibwrapper/utility/pool.py | 64 ++++++++++++-- 4 files changed, 189 insertions(+), 65 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index 77e152d0e..b6359d494 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -68,7 +68,9 @@ def __init__(self, start_connection_pool=True) -> None: self.pool_options = { # Pool size "size": settings.EPP_CONNECTION_POOL_SIZE, - # Which errors the pool should look out for + # Which errors the pool should look out for. + # Avoid changing this unless necessary, + # it can and will break things. "exc_classes": (TransportError,), # Occasionally pings the registry to keep the connection alive. # Value in seconds => (keepalive / size) @@ -76,6 +78,7 @@ def __init__(self, start_connection_pool=True) -> None: } self._pool = None + # Tracks the status of the pool self.pool_status = PoolStatus() @@ -85,9 +88,11 @@ def __init__(self, start_connection_pool=True) -> None: def _send(self, command): """Helper function used by `send`.""" cmd_type = command.__class__.__name__ + # Start a timeout to check if the pool is hanging timeout = Timeout(settings.POOL_TIMEOUT) timeout.start() + try: if not self.pool_status.connection_success: raise LoginError( @@ -96,6 +101,9 @@ def _send(self, command): with self._pool.get() as connection: response = connection.send(command) except Timeout as t: + # If more than one pool exists, + # multiple timeouts can be floating around. + # We need to be specific as to which we are targeting. if t is timeout: # Flag that the pool is frozen, # then restart the pool. @@ -125,6 +133,7 @@ def _send(self, command): else: return response finally: + # Close the timeout no matter what happens timeout.close() def send(self, command, *, cleaned=False): @@ -174,11 +183,6 @@ def start_connection_pool(self, restart_pool_if_exists=True): If an instance of the pool already exists, then then that instance will be killed first. It is generally recommended to keep this enabled. - - try_start_if_invalid -> bool: - Designed for use in test cases, if we can't connect - to the registry, ignore that and try to connect anyway - It is generally recommended to keep this disabled. """ # Since we reuse the same creds for each pool, we can test on # one socket, and if successful, then we know we can connect. @@ -209,7 +213,7 @@ def kill_pool(self): self._pool.kill_all_connections() self._pool = None self.pool_status.pool_running = False - return + return None logger.info("kill_pool() was invoked but there was no pool to delete") def _test_registry_connection_success(self): @@ -219,9 +223,11 @@ def _test_registry_connection_success(self): """ socket = Socket(self._client, self._login) can_login = False + # Something went wrong if this doesn't exist if hasattr(socket, "test_connection_success"): can_login = socket.test_connection_success() + return can_login @@ -229,9 +235,8 @@ def _test_registry_connection_success(self): # Initialize epplib CLIENT = EPPLibWrapper() logger.info("registry client initialized") -except Exception as err: +except Exception: CLIENT = None # type: ignore logger.warning( "Unable to configure epplib. Registrar cannot contact registry.", exc_info=True ) - logger.warning(err) diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index 8329e36cf..c44d07910 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -38,15 +38,36 @@ def connect(self): raise LoginError(response.msg) return self.client + def disconnect(self): + """Close the connection.""" + try: + self.client.send(commands.Logout()) + self.client.close() + except Exception: + logger.warning("Connection to registry was not cleanly closed.") + + def send(self, command): + """Sends a command to the registry. + If the response code is >= 2000, + then this function raises a LoginError. + The calling function should handle this.""" + response = self.client.send(command) + if self.is_login_error(response.code): + self.client.close() + raise LoginError(response.msg) + + return response + def is_login_error(self, code): + """Returns the result of code >= 2000""" return code >= 2000 def test_connection_success(self): """Tests if a successful connection can be made with the registry. - Tries 3 times""" + Tries 3 times.""" # Something went wrong if this doesn't exist if not hasattr(self.client, "connect"): - logger.warning("self.client does not have a connect method") + logger.warning("self.client does not have a connect attribute") return False counter = 0 # we'll try 3 times @@ -72,21 +93,5 @@ def test_connection_success(self): logger.warning("was login error") return False - # otherwise, just return true + # Otherwise, just return true return True - - def disconnect(self): - """Close the connection.""" - try: - self.client.send(commands.Logout()) - self.client.close() - except Exception: - logger.warning("Connection to registry was not cleanly closed.") - - def send(self, command): - response = self.client.send(command) - if response.code >= 2000: - self.client.close() - raise LoginError(response.msg) - - return response diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 35e5d9378..3a431ef1e 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -68,6 +68,18 @@ def fake_send(self, command, cleaned=None): ) return mock + def fake_client(mock_client): + pw = "none" + client = Client( + SocketTransport( + "none", + cert_file="path/to/cert_file", + key_file="path/to/key_file", + password=pw, + ) + ) + return client + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_pool_sends_data(self): """A .send is invoked on the pool successfully""" @@ -108,18 +120,6 @@ def test_pool_sends_data(self): "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", } - def fake_client(mock_client): - pw = "none" - client = Client( - SocketTransport( - "none", - cert_file="path/to/cert_file", - key_file="path/to/key_file", - password=pw, - ) - ) - return client - # Mock a response from EPP def fake_receive(command, cleaned=None): location = Path(__file__).parent / "utility" / "infoDomain.xml" @@ -131,10 +131,10 @@ def fake_receive(command, cleaned=None): stack.enter_context( patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) ) - stack.enter_context(patch.object(Socket, "connect", fake_client)) + stack.enter_context(patch.object(Socket, "connect", self.fake_client)) stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) - # Restart the connection pool, since it starts on app startup + # Restart the connection pool registry.start_connection_pool() # Pool should be running, and be the right size self.assertEqual(registry.pool_status.connection_success, True) @@ -152,29 +152,97 @@ def fake_receive(command, cleaned=None): # If it is 0, then they failed to open self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + def test_pool_restarts_on_send(self): + """A .send is invoked, but the pool isn't running. + The pool should restart.""" + expected_result = { + "cl_tr_id": None, + "code": 1000, + "extensions": [], + "msg": "Command completed successfully", + "msg_q": None, + "res_data": [ + info.InfoDomainResultData( + roid="DF1340360-GOV", + statuses=[ + common.Status( + state="serverTransferProhibited", + description=None, + lang="en", + ), + common.Status(state="inactive", description=None, lang="en"), + ], + cl_id="gov2023-ote", + cr_id="gov2023-ote", + cr_date=datetime.datetime( + 2023, 8, 15, 23, 56, 36, tzinfo=tzlocal() + ), + up_id="gov2023-ote", + up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()), + tr_date=None, + name="test3.gov", + registrant="TuaWnx9hnm84GCSU", + admins=[], + nsset=None, + keyset=None, + ex_date=datetime.date(2024, 8, 15), + auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"), + ) + ], + "sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a", + } + + # Mock a response from EPP + def fake_receive(command, cleaned=None): + location = Path(__file__).parent / "utility" / "infoDomain.xml" + xml = (location).read_bytes() + return xml + + # Mock what happens inside the "with" + with ExitStack() as stack: + stack.enter_context( + patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) + ) + stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + stack.enter_context(patch.object(SocketTransport, "send", self.fake_send)) + stack.enter_context(patch.object(SocketTransport, "receive", fake_receive)) + # Kill the connection pool + registry.kill_pool() + + self.assertEqual(registry.pool_status.connection_success, False) + self.assertEqual(registry.pool_status.pool_running, False) + + # An exception should be raised as end user will be informed + # that they cannot connect to EPP + with self.assertRaises(RegistryError): + expected = "InfoDomain failed to execute due to a connection error." + result = registry.send( + commands.InfoDomain(name="test.gov"), cleaned=True + ) + self.assertEqual(result, expected) + + # A subsequent command should be successful, as the pool restarts + result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) + # Should this ever fail, it either means that the schema has changed, + # or the pool is broken. + # If the schema has changed: Update the associated infoDomain.xml file + self.assertEqual(result.__dict__, expected_result) + + # The number of open pools should match the number of requested ones. + # If it is 0, then they failed to open + self.assertEqual(len(registry._pool.conn), self.pool_options["size"]) + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) def test_raises_connection_error(self): """A .send is invoked on the pool, but registry connection is lost right as we send a command.""" - # Fake data for the _pool object - def fake_client(self): - pw = "none" - client = Client( - SocketTransport( - "none", - cert_file="path/to/cert_file", - key_file="path/to/key_file", - password=pw, - ) - ) - return client - with ExitStack() as stack: stack.enter_context( patch.object(EPPConnectionPool, "_create_socket", self.fake_socket) ) - stack.enter_context(patch.object(Socket, "connect", fake_client)) + stack.enter_context(patch.object(Socket, "connect", self.fake_client)) # Pool should be running self.assertEqual(registry.pool_status.connection_success, True) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index ba7edec91..322779285 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -6,9 +6,12 @@ try: from epplib.commands import Hello + from epplib.exceptions import TransportError except ImportError: pass +from gevent.lock import BoundedSemaphore +from collections import deque logger = logging.getLogger(__name__) @@ -27,7 +30,34 @@ def __init__(self, client, login, options: dict): # For storing shared credentials self._client = client self._login = login - super().__init__(**options) + # Keep track of each greenlet + self.greenlets = [] + + # Define optional pool settings. + # Kept in a dict so that the parent class, + # client.py, can maintain seperation/expanadability + self.size = 1 + if "size" in options: + self.size = options["size"] + + self.exc_classes = tuple((TransportError,)) + if "exc_classes" in options: + self.exc_classes = options["exc_classes"] + + self.keepalive = None + if "keepalive" in options: + self.keepalive = options["keepalive"] + + # Determines the period in which new + # gevent threads are spun up + self.spawn_frequency = 0.1 + if "spawn_frequency" in options: + self.spawn_frequency = options["spawn_frequency"] + + self.conn = deque() + self.lock = BoundedSemaphore(self.size) + + self.populate_all_connections() def _new_connection(self): socket = self._create_socket(self._client, self._login) @@ -64,22 +94,38 @@ def get_connections(self): def kill_all_connections(self): """Kills all active connections in the pool.""" try: - gevent.killall(self.conn) - self.conn.clear() - # Clear the semaphore - for i in range(self.lock.counter): - self.lock.release() + if len(self.conn) > 0: + gevent.killall(self.greenlets) + + self.greenlets.clear() + self.conn.clear() + + # Clear the semaphore + self.lock = BoundedSemaphore(self.size) + else: + logger.info("No connections to kill.") except Exception as err: logger.error("Could not kill all connections.") raise err - def repopulate_all_connections(self): - """Regenerates the connection pool. + def populate_all_connections(self): + """Generates the connection pool. If any connections exist, kill them first. + Based off of the __init__ definition for geventconnpool. """ if len(self.conn) > 0: self.kill_all_connections() + + # Setup the lock for i in range(self.size): self.lock.acquire() + + # Open multiple connections for i in range(self.size): - gevent.spawn_later(self.SPAWN_FREQUENCY * i, self._addOne) + self.greenlets.append( + gevent.spawn_later(self.spawn_frequency * i, self._addOne) + ) + + # Open a "keepalive" thread if we want to ping open connections + if self.keepalive: + self.greenlets.append(gevent.spawn(self._keepalive_periodic)) From 02baff3f79197103b526385a9ca6e5e583829b4d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:00:31 -0600 Subject: [PATCH 58/65] Add spacing --- src/epplibwrapper/utility/pool.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 322779285..01edb25e8 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -30,12 +30,13 @@ def __init__(self, client, login, options: dict): # For storing shared credentials self._client = client self._login = login + # Keep track of each greenlet self.greenlets = [] # Define optional pool settings. # Kept in a dict so that the parent class, - # client.py, can maintain seperation/expanadability + # client.py, can maintain seperation/expandability self.size = 1 if "size" in options: self.size = options["size"] From 37d6bc9de9b18ccd18e8755c2bfe6ca4b8ffbd48 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 20 Oct 2023 08:48:07 -0600 Subject: [PATCH 59/65] Linting --- src/epplibwrapper/utility/pool.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 01edb25e8..99d5326ab 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -1,4 +1,5 @@ import logging +from typing import List import gevent from geventconnpool import ConnectionPool from epplibwrapper.socket import Socket @@ -32,7 +33,7 @@ def __init__(self, client, login, options: dict): self._login = login # Keep track of each greenlet - self.greenlets = [] + self.greenlets: List[gevent.Greenlet] = [] # Define optional pool settings. # Kept in a dict so that the parent class, @@ -55,7 +56,7 @@ def __init__(self, client, login, options: dict): if "spawn_frequency" in options: self.spawn_frequency = options["spawn_frequency"] - self.conn = deque() + self.conn: deque = deque() self.lock = BoundedSemaphore(self.size) self.populate_all_connections() From 0827ea77eaf8bae5301d92c7a14b4a6519ce6fb9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 23 Oct 2023 07:35:36 -0600 Subject: [PATCH 60/65] Remove test commit --- src/registrar/tests/test_models_domain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index f0522b36d..3024aeaba 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -873,7 +873,6 @@ def test_contact_getter_registrant(self): contact_id="regContact", contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ) - # test commit - will remove self.assertEqual( self.domain_contact.registrant_contact.email, expected_contact.email ) From 2cd02240085300a861ef2e117a0c69b4587719c1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 23 Oct 2023 07:38:37 -0600 Subject: [PATCH 61/65] Remove old comment --- src/registrar/config/settings.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 2e88154ba..5506bbcaf 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -534,12 +534,6 @@ SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase SECRET_REGISTRY_HOSTNAME = secret_registry_hostname -# Question for reviewers: For one client, the performance difference -# between a pool of size 1 vs a pool of size 10 isn't noticeable. -# The main performance increase comes from an open connection. -# We would need to do load testing to determine the ideal number, -# my recommendation now would be 3 as it is a good balance between -# overhead vs capacity. # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! EPP_CONNECTION_POOL_SIZE = 1 From 736fe46ec3afc6a0dab176cfebf5fee7776f0e2c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:12:36 -0600 Subject: [PATCH 62/65] Update domain.py --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 2bfdd58c5..457f3305c 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -59,7 +59,7 @@ class Domain(TimeStampedModel, DomainHelper): G) Activation is controlled by the registry. It will happen automatically when the domain meets the required checks. """ - + # test comment for pushing to sandbox - will remove def __init__(self, *args, **kwargs): self._cache = {} super(Domain, self).__init__(*args, **kwargs) From a87519e61524d64533bdf34a2ea6bd9e204cc0f3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 25 Oct 2023 08:29:39 -0600 Subject: [PATCH 63/65] Cleanup, add comment --- src/registrar/config/settings.py | 1 + src/registrar/models/domain.py | 2 +- src/registrar/tests/test_models_domain.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 5506bbcaf..e4c4ae1f8 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -540,6 +540,7 @@ # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE +# WARNING: Setting this value too high could cause frequent app crashes! POOL_KEEP_ALIVE = 60 # Determines how long we try to keep a pool alive for, diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 457f3305c..2bfdd58c5 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -59,7 +59,7 @@ class Domain(TimeStampedModel, DomainHelper): G) Activation is controlled by the registry. It will happen automatically when the domain meets the required checks. """ - # test comment for pushing to sandbox - will remove + def __init__(self, *args, **kwargs): self._cache = {} super(Domain, self).__init__(*args, **kwargs) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 3024aeaba..ef3084f9c 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -873,6 +873,7 @@ def test_contact_getter_registrant(self): contact_id="regContact", contact_type=PublicContact.ContactTypeChoices.REGISTRANT, ) + self.assertEqual( self.domain_contact.registrant_contact.email, expected_contact.email ) From 91c68f91f1768c60e1cfc3bf021cfff56d11f5b5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:35:41 -0600 Subject: [PATCH 64/65] Add PR suggestions --- src/epplibwrapper/client.py | 12 +++++++----- src/epplibwrapper/socket.py | 8 +++++--- src/epplibwrapper/tests/test_pool.py | 1 + src/epplibwrapper/utility/pool.py | 7 ++++--- src/epplibwrapper/utility/pool_error.py | 16 ++++++++++++---- src/epplibwrapper/utility/pool_status.py | 7 ++++++- src/registrar/config/settings.py | 3 ++- 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index b6359d494..e4b7a5d53 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -111,21 +111,21 @@ def _send(self, command): self.start_connection_pool() except (ValueError, ParsingError) as err: message = f"{cmd_type} failed to execute due to some syntax error." - logger.error(message, exc_info=True) + logger.error(f"{message} Error: {err}", exc_info=True) raise RegistryError(message) from err except TransportError as err: message = f"{cmd_type} failed to execute due to a connection error." - logger.error(message, exc_info=True) + logger.error(f"{message} Error: {err}", exc_info=True) raise RegistryError(message) from err except LoginError as err: - # For linter + # For linter due to it not liking this line length text = "failed to execute due to a registry login error." message = f"{cmd_type} {text}" - logger.error(message, exc_info=True) + logger.error(f"{message} Error: {err}", exc_info=True) raise RegistryError(message) from err except Exception as err: message = f"{cmd_type} failed to execute due to an unknown error." - logger.error(message, exc_info=True) + logger.error(f"{message} Error: {err}", exc_info=True) raise RegistryError(message) from err else: if response.code >= 2000: @@ -155,6 +155,8 @@ def send(self, command, *, cleaned=False): except RegistryError as err: raise err finally: + # Code execution will halt after here. + # The end user will need to recall .send. self.start_connection_pool() counter = 0 # we'll try 3 times diff --git a/src/epplibwrapper/socket.py b/src/epplibwrapper/socket.py index c44d07910..6040f6682 100644 --- a/src/epplibwrapper/socket.py +++ b/src/epplibwrapper/socket.py @@ -48,7 +48,7 @@ def disconnect(self): def send(self, command): """Sends a command to the registry. - If the response code is >= 2000, + If the RegistryError code is >= 2000, then this function raises a LoginError. The calling function should handle this.""" response = self.client.send(command) @@ -59,7 +59,9 @@ def send(self, command): return response def is_login_error(self, code): - """Returns the result of code >= 2000""" + """Returns the result of code >= 2000 for RegistryError. + This indicates that something weird happened on the Registry, + and that we should return a LoginError.""" return code >= 2000 def test_connection_success(self): @@ -90,7 +92,7 @@ def test_connection_success(self): # If we encounter a login error, fail if self.is_login_error(response.code): - logger.warning("was login error") + logger.warning("A login error was found in test_connection_success") return False # Otherwise, just return true diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 3a431ef1e..4e919ba76 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -28,6 +28,7 @@ class TestConnectionPool(TestCase): """Tests for our connection pooling behaviour""" def setUp(self): + # Mimic the settings added to settings.py self.pool_options = { # Current pool size "size": 1, diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 99d5326ab..8979c9744 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -51,7 +51,8 @@ def __init__(self, client, login, options: dict): self.keepalive = options["keepalive"] # Determines the period in which new - # gevent threads are spun up + # gevent threads are spun up. + # This time period is in seconds. So for instance, .1 would be .1 seconds. self.spawn_frequency = 0.1 if "spawn_frequency" in options: self.spawn_frequency = options["spawn_frequency"] @@ -77,7 +78,7 @@ def _new_connection(self): def _keepalive(self, c): """Sends a command to the server to keep the connection alive.""" try: - # Sends a ping to EPPLib + # Sends a ping to the registry via EPPLib c.send(Hello()) except Exception as err: message = "Failed to keep the connection alive." @@ -108,7 +109,7 @@ def kill_all_connections(self): logger.info("No connections to kill.") except Exception as err: logger.error("Could not kill all connections.") - raise err + raise PoolError(code=PoolErrorCodes.KILL_ALL_FAILED) from err def populate_all_connections(self): """Generates the connection pool. diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py index 70312f32e..16aa0e08d 100644 --- a/src/epplibwrapper/utility/pool_error.py +++ b/src/epplibwrapper/utility/pool_error.py @@ -22,12 +22,20 @@ class PoolError(Exception): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED + + Note: These are separate from the error codes returned from EppLib """ - # For linter - kill_failed = "Could not kill all connections." - conn_failed = "Failed to execute due to a registry error." - alive_failed = "Failed to keep the connection alive." + # Used variables due to linter requirements + kill_failed = "Could not kill all connections. Are multiple pools running?" + conn_failed = ( + "Failed to execute due to a registry error." + " See previous logs to determine the cause of the error." + ) + alive_failed = ( + "Failed to keep the connection alive. " + "It is likely that the registry returned a LoginError." + ) _error_mapping = { PoolErrorCodes.KILL_ALL_FAILED: kill_failed, PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed, diff --git a/src/epplibwrapper/utility/pool_status.py b/src/epplibwrapper/utility/pool_status.py index 214bf8ac1..64ebbe5eb 100644 --- a/src/epplibwrapper/utility/pool_status.py +++ b/src/epplibwrapper/utility/pool_status.py @@ -1,5 +1,10 @@ class PoolStatus: - """A list of Booleans to keep track of Pool Status""" + """A list of Booleans to keep track of Pool Status. + + pool_running -> bool: Tracks if the pool itself is active or not. + connection_success -> bool: Tracks if connection is possible with the registry. + pool_hanging -> pool: Tracks if the pool has exceeded its timeout period. + """ def __init__(self): self.pool_running = False diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index e4c4ae1f8..385f2a1e3 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -536,11 +536,12 @@ # Use this variable to set the size of our connection pool in client.py # WARNING: Setting this value too high could cause frequent app crashes! +# Having too many connections open could cause the sandbox to timeout, +# as the spinup time could exceed the timeout time. EPP_CONNECTION_POOL_SIZE = 1 # Determines the interval in which we ping open connections in seconds # Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE -# WARNING: Setting this value too high could cause frequent app crashes! POOL_KEEP_ALIVE = 60 # Determines how long we try to keep a pool alive for, From 745f2bbdf9efaf0777a425ae2ff148cdf5f1c5f6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 26 Oct 2023 07:51:31 -0600 Subject: [PATCH 65/65] Linter --- src/epplibwrapper/utility/pool.py | 2 +- src/epplibwrapper/utility/pool_error.py | 8 ++++---- src/epplibwrapper/utility/pool_status.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/epplibwrapper/utility/pool.py b/src/epplibwrapper/utility/pool.py index 8979c9744..36771252b 100644 --- a/src/epplibwrapper/utility/pool.py +++ b/src/epplibwrapper/utility/pool.py @@ -51,7 +51,7 @@ def __init__(self, client, login, options: dict): self.keepalive = options["keepalive"] # Determines the period in which new - # gevent threads are spun up. + # gevent threads are spun up. # This time period is in seconds. So for instance, .1 would be .1 seconds. self.spawn_frequency = 0.1 if "spawn_frequency" in options: diff --git a/src/epplibwrapper/utility/pool_error.py b/src/epplibwrapper/utility/pool_error.py index 16aa0e08d..821962774 100644 --- a/src/epplibwrapper/utility/pool_error.py +++ b/src/epplibwrapper/utility/pool_error.py @@ -22,19 +22,19 @@ class PoolError(Exception): - 2000 KILL_ALL_FAILED - 2001 NEW_CONNECTION_FAILED - 2002 KEEP_ALIVE_FAILED - + Note: These are separate from the error codes returned from EppLib """ # Used variables due to linter requirements kill_failed = "Could not kill all connections. Are multiple pools running?" conn_failed = ( - "Failed to execute due to a registry error." + "Failed to execute due to a registry error." " See previous logs to determine the cause of the error." ) alive_failed = ( - "Failed to keep the connection alive. " - "It is likely that the registry returned a LoginError." + "Failed to keep the connection alive. " + "It is likely that the registry returned a LoginError." ) _error_mapping = { PoolErrorCodes.KILL_ALL_FAILED: kill_failed, diff --git a/src/epplibwrapper/utility/pool_status.py b/src/epplibwrapper/utility/pool_status.py index 64ebbe5eb..3a0ae750f 100644 --- a/src/epplibwrapper/utility/pool_status.py +++ b/src/epplibwrapper/utility/pool_status.py @@ -1,6 +1,6 @@ class PoolStatus: """A list of Booleans to keep track of Pool Status. - + pool_running -> bool: Tracks if the pool itself is active or not. connection_success -> bool: Tracks if connection is possible with the registry. pool_hanging -> pool: Tracks if the pool has exceeded its timeout period.