diff --git a/README.md b/README.md index f51be5b7..3462085d 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,6 @@ $ npm install # This installs any node packages that are within package.json (CD $ make install # This calls `pipenv install --dev` on the repo root and any of the directories that contain a Makefile with `install` ``` -_**Note** you might have an issue installing `psycopg2` - I found [this](https://github.com/pypa/pipenv/issues/3991#issuecomment-564645309) helpful_ - A file named `.env` is expected in the root of the repository, the expected values are: ```bash diff --git a/alembic_migration/Pipfile b/alembic_migration/Pipfile index d099c64a..b6595c2f 100644 --- a/alembic_migration/Pipfile +++ b/alembic_migration/Pipfile @@ -14,7 +14,7 @@ sqlalchemy = "==1.4.0" db = {editable = true, path = "./../layers/db"} pytest = "==7.4.3" moto = "==5.0.17" -psycopg2 = "==2.9.10" +psycopg2-binary = "==2.9.10" pytest-docker = "==2.0.1" assertpy = "==1.1" pytest-cov = "==4.1.0" diff --git a/alembic_migration/Pipfile.lock b/alembic_migration/Pipfile.lock index d078246b..27a4d95f 100644 --- a/alembic_migration/Pipfile.lock +++ b/alembic_migration/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3a0c02a88a70b1762cd50bc669a5323d2588c0ce3dce3fa0f5d212144e6efd0d" + "sha256": "3d0853932c6ea3664fd7c1a200b159d587be8d92b22ed4d3350c5410c62d2583" }, "pipfile-spec": 6, "requires": { @@ -36,11 +36,11 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "cfnresponse": { "hashes": [ @@ -147,11 +147,11 @@ }, "mako": { "hashes": [ - "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", - "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a" + "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627", + "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8" ], "markers": "python_version >= '3.8'", - "version": "==1.3.6" + "version": "==1.3.8" }, "markupsafe": { "hashes": [ @@ -246,19 +246,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -328,11 +328,11 @@ }, "attrs": { "hashes": [ - "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", - "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" ], - "markers": "python_version >= '3.7'", - "version": "==24.2.0" + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "boto3": { "hashes": [ @@ -345,19 +345,19 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "cffi": { "hashes": [ @@ -548,104 +548,104 @@ "toml" ], "hashes": [ - "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", - "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", - "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", - "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", - "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", - "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", - "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", - "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", - "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", - "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", - "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", - "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", - "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", - "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", - "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", - "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", - "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", - "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", - "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", - "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", - "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", - "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", - "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", - "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", - "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", - "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", - "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", - "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", - "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", - "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", - "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", - "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", - "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", - "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", - "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", - "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", - "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", - "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", - "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", - "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", - "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", - "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", - "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", - "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", - "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", - "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", - "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", - "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", - "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", - "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", - "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", - "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", - "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", - "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", - "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", - "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", - "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", - "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", - "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", - "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", - "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", - "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "markers": "python_version >= '3.9'", - "version": "==7.6.4" + "version": "==7.6.9" }, "cryptography": { "hashes": [ - "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", - "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", - "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", - "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", - "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", - "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", - "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", - "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", - "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", - "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", - "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", - "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", - "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", - "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", - "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", - "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", - "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", - "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", - "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", - "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", - "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", - "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", - "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", - "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", - "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", - "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", - "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", + "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731", + "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", + "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", + "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", + "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", + "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", + "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", + "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", + "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", + "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", + "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", + "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", + "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", + "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", + "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", + "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa", + "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", + "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", + "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", + "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", + "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", + "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", + "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", + "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", + "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756", + "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4" ], - "markers": "python_version >= '3.7'", - "version": "==43.0.3" + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.0" }, "db": { "editable": true, @@ -840,11 +840,11 @@ }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pluggy": { "hashes": [ @@ -854,17 +854,75 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, - "psycopg2": { + "psycopg2-binary": { "hashes": [ - "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", - "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", - "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", - "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", - "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716", - "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", - "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442", - "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b", - "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", "markers": "python_version >= '3.8'", @@ -1015,19 +1073,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -1080,11 +1138,11 @@ }, "werkzeug": { "hashes": [ - "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", - "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.8'", - "version": "==3.0.4" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "xmltodict": { "hashes": [ diff --git a/cdk/downloader_stack.py b/cdk/downloader_stack.py index 3d2b9abf..b2d699fa 100644 --- a/cdk/downloader_stack.py +++ b/cdk/downloader_stack.py @@ -6,16 +6,17 @@ Duration, RemovalPolicy, Stack, + aws_apigatewayv2, + aws_apigatewayv2_integrations, aws_cloudwatch, aws_ec2, aws_events, aws_events_targets, aws_iam, aws_lambda, - aws_s3, ) from aws_cdk import aws_lambda_python_alpha as aws_lambda_python -from aws_cdk import aws_logs, aws_rds, aws_secretsmanager, aws_sqs, aws_ssm +from aws_cdk import aws_logs, aws_rds, aws_s3, aws_secretsmanager, aws_sqs, aws_ssm from aws_cdk import aws_stepfunctions as sfn from aws_cdk import aws_stepfunctions_tasks as tasks from constructs import Construct @@ -255,7 +256,7 @@ def __init__( self, id=f"{identifier}-link-fetcher", entry="lambdas/link_fetcher", - index="handler.py", + index="app/search_handler.py", handler="handler", layers=[ db_layer, @@ -286,6 +287,58 @@ def __init__( threshold=1, ) + link_subscription = aws_lambda_python.PythonFunction( + self, + id=f"{identifier}-link-subscription", + entry="lambdas/link_fetcher", + index="app/subscription_handler.py", + handler="handler", + layers=[ + db_layer, + ], + memory_size=200, + timeout=Duration.minutes(15), + runtime=aws_lambda.Runtime.PYTHON_3_11, + environment=link_fetcher_environment_vars, + ) + + aws_logs.LogGroup( + self, + id=f"{identifier}-link-subscription-log-group", + log_group_name=f"/aws/lambda/{link_subscription.function_name}", + removal_policy=RemovalPolicy.DESTROY + if removal_policy_destroy + else RemovalPolicy.RETAIN, + retention=aws_logs.RetentionDays.ONE_DAY + if removal_policy_destroy + else aws_logs.RetentionDays.TWO_WEEKS, + ) + + aws_cloudwatch.Alarm( + self, + id=f"{identifier}-link-subscription-errors-alarm", + metric=link_fetcher.metric_errors(), + evaluation_periods=3, + threshold=1, + ) + + forwarder_api = aws_apigatewayv2.HttpApi( + self, + "EsaPushSubscriptionHandlerApi", + api_name="EsaPushSubscriptionHandlerApi", + default_integration=aws_apigatewayv2_integrations.HttpLambdaIntegration( + "EsaPushSubscriptionHandlerApi-Integration", + handler=link_subscription, + ), + ) + + aws_ssm.StringParameter( + self, + id=f"{identifier}-link-subscription-endpoint-url", + string_value=forwarder_api.url, + parameter_name=f"/hls-s2-downloader-serverless/{identifier}/link_subscription_endpoint_url", + ) + downloader_environment_vars = { "STAGE": identifier, "DB_CONNECTION_SECRET_ARN": downloader_rds_secret.secret_arn, @@ -338,7 +391,7 @@ def __init__( self, id=f"{identifier}-downloader-role-arn", string_value=self.downloader.role.role_arn, - parameter_name=(f"/integration_tests/{identifier}/downloader_role_arn"), + parameter_name=f"/integration_tests/{identifier}/downloader_role_arn", ) self.downloader.role.add_managed_policy(lambda_insights_policy) @@ -351,15 +404,9 @@ def __init__( downloader_bucket.grant_write(self.downloader) downloader_rds_secret.grant_read(link_fetcher) + downloader_rds_secret.grant_read(link_subscription) downloader_rds_secret.grant_read(self.downloader) - scihub_credentials = aws_secretsmanager.Secret.from_secret_name_v2( - self, - id=f"{identifier}-scihub-credentials", - secret_name=f"hls-s2-downloader-serverless/{identifier}/scihub-credentials", - ) - scihub_credentials.grant_read(self.downloader) - copernicus_credentials = aws_secretsmanager.Secret.from_secret_name_v2( self, id=f"{identifier}-copernicus-credentials", @@ -368,9 +415,17 @@ def __init__( copernicus_credentials.grant_read(self.downloader) copernicus_credentials.grant_read(self.token_rotator) + esa_subscription_credentials = aws_secretsmanager.Secret.from_secret_name_v2( + self, + id=f"{identifier}-esa-subscription-credentials", + secret_name=f"hls-s2-downloader-serverless/{identifier}/esa-subscription-credentials", + ) + esa_subscription_credentials.grant_read(link_subscription) + token_parameter.grant_read(self.downloader) to_download_queue.grant_send_messages(link_fetcher) + to_download_queue.grant_send_messages(link_subscription) to_download_queue.grant_consume_messages(self.downloader) # We must resort to using CfnEventSourceMapping to set the maximum concurrency diff --git a/cdk/integration_stack.py b/cdk/integration_stack.py index b918e883..6eba0af8 100644 --- a/cdk/integration_stack.py +++ b/cdk/integration_stack.py @@ -1,9 +1,9 @@ import json from typing import Optional -from aws_cdk import Duration, RemovalPolicy, Stack, aws_apigateway, aws_lambda +from aws_cdk import Duration, RemovalPolicy, Stack, aws_apigateway, aws_iam, aws_lambda from aws_cdk import aws_lambda_python_alpha as aws_lambda_python -from aws_cdk import aws_iam, aws_logs, aws_s3, aws_secretsmanager, aws_ssm +from aws_cdk import aws_logs, aws_s3, aws_secretsmanager, aws_ssm from constructs import Construct @@ -27,16 +27,16 @@ def __init__( ) ) - # TODO remove this, along with other references to it, but leaving for - # now, just in case removing it would break the downloader lambda aws_secretsmanager.Secret( self, - id=f"{identifier}-integration-scihub-credentials", - secret_name=f"hls-s2-downloader-serverless/{identifier}/scihub-credentials", - description="Dummy values for the Mock SciHub API credentials", + id=f"{identifier}-integration-esa-subscription-credentials", + secret_name=f"hls-s2-downloader-serverless/{identifier}/esa-subscription-credentials", + description="Dummy values for the ESA 'push' subscription authentication", generate_secret_string=aws_secretsmanager.SecretStringGenerator( - secret_string_template=json.dumps({"username": "test-user"}), - generate_string_key="password", + secret_string_template=json.dumps( + {"notification_username": "test-user"} + ), + generate_string_key="notification_password", ), ) diff --git a/images/hls-s2-downloader-link-subscription.png b/images/hls-s2-downloader-link-subscription.png new file mode 100644 index 00000000..e46b709e Binary files /dev/null and b/images/hls-s2-downloader-link-subscription.png differ diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 75079580..885b28d5 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -77,6 +77,15 @@ def db_session(monkeypatch, ssm_parameter: Callable[[str], str]) -> Iterable[Ses session.commit() +@pytest.fixture +def link_subscription_endpoint_url(ssm_client: SSMClient, identifier: str): + qname = f"/hls-s2-downloader-serverless/{identifier}/link_subscription_endpoint_url" + result = ssm_client.get_parameter(Name=qname) + value = result["Parameter"].get("Value") + assert value is not None, f"No such SSM parameter: {qname}" + return value + + @pytest.fixture def step_function_arn(ssm_parameter: Callable[[str], str]): return ssm_parameter("link_fetcher_step_function_arn") diff --git a/integration_tests/test_link_push_subscription.py b/integration_tests/test_link_push_subscription.py new file mode 100644 index 00000000..b9adbf1e --- /dev/null +++ b/integration_tests/test_link_push_subscription.py @@ -0,0 +1,149 @@ +import datetime as dt +import json +from pathlib import Path +from typing import Callable +from uuid import uuid4 + +import boto3 +import polling2 +import pytest +import requests +from db.models.granule import Granule +from mypy_boto3_sqs import SQSClient +from sqlalchemy.orm import Session + + +def check_sqs_message_count(sqs_client, queue_url, count): + queue_attributes = sqs_client.get_queue_attributes( + QueueUrl=queue_url, AttributeNames=["ApproximateNumberOfMessages"] + ) + return int(queue_attributes["Attributes"]["ApproximateNumberOfMessages"]) == count + + +def _format_dt(datetime: dt.datetime) -> str: + """Format datetime into string used by ESA's payload""" + return datetime.isoformat().replace("+00:00", "Z") + + +@pytest.fixture +def recent_event_s2_created() -> dict: + """Create a recent Sentinel-2 "Created" event from ESA's push subscription + + This message contains two types of fields, + * Message metadata (event type, subscription ID, ack ID, notification date, etc) + * Message "body" - `(.value)` + """ + # Reusing example from ESA as a template + data = ( + Path(__file__).parents[1] + / "lambdas" + / "link_fetcher" + / "tests" + / "data" + / "push-granule-created-s2-n1.json" + ) + payload = json.loads(data.read_text()) + + # Update relevant parts of message payload to be "recent" + # where recent is <30 days from today as we're not currently + # reprocessing historical scenes that ESA has reprocessed + now = dt.datetime.now(tz=dt.timezone.utc) + + payload["NotificationDate"] = _format_dt(now) + payload["value"]["OriginDate"] = _format_dt(now - dt.timedelta(seconds=7)) + payload["value"]["PublicationDate"] = _format_dt(now - dt.timedelta(seconds=37)) + payload["value"]["ModificationDate"] = _format_dt(now - dt.timedelta(seconds=1)) + payload["value"]["ContentDate"] = { + "Start": _format_dt(now - dt.timedelta(hours=3, seconds=3)), + "End": _format_dt(now - dt.timedelta(hours=3)), + } + # We're not using fields in `payload["value"]["Attributes"]` but there's duplicate + # datetime information in there following OData conventions + + # Randomize ID of message to ensure each fixture's return is unique according + # to our DB (which uses granule ID as primary key) + payload["value"]["Id"] = str(uuid4()) + + return payload + + +@pytest.fixture +def link_subscription_credentials( + identifier: str, ssm_parameter: Callable[[str], str] +) -> tuple[str, str]: + """Return user/pass credentials for subscription endpoint""" + secrets_manager_client = boto3.client("secretsmanager") + secret = json.loads( + secrets_manager_client.get_secret_value( + SecretId=( + f"hls-s2-downloader-serverless/{identifier}/esa-subscription-credentials" + ) + )["SecretString"] + ) + + return ( + secret["notification_username"], + secret["notification_password"], + ) + + +@pytest.mark.parametrize("notification_count", [1, 2]) +def test_link_push_subscription_handles_event( + recent_event_s2_created: dict, + link_subscription_endpoint_url: str, + link_subscription_credentials: tuple[str, str], + db_session: Session, + sqs_client: SQSClient, + queue_url: str, + notification_count: int, +): + """Test that we handle a new granule created notification + + We have occasionally observed duplicate granule IDs being + sent to our API endpoint and we want to only process one, + so this test includes a parametrized "notification_count" + to replicate this reality. + """ + for _ in range(notification_count): + resp = requests.post( + f"{link_subscription_endpoint_url}events", + auth=link_subscription_credentials, + json=recent_event_s2_created, + ) + + # ensure correct response (204) + assert resp.status_code == 204 + + # ensure we have SQS message + polling2.poll( + check_sqs_message_count, + args=(sqs_client, queue_url, 1), + step=5, + timeout=120, + ) + + # ensure we have 1 granule for this ID + granules = ( + db_session.query(Granule).filter( + Granule.id == recent_event_s2_created["value"]["Id"] + ) + ).all() + assert len(granules) == 1 + + +def test_link_push_subscription_user_auth_rejects_incorrect( + link_subscription_endpoint_url: str, +): + """Test that we reject incorrect authentication""" + url = f"{link_subscription_endpoint_url}events" + resp = requests.post( + url, + auth=( + "foo", + "bar", + ), + json={}, + ) + + # ensure correct response (401 Unauthorized) + assert resp.status_code == 401 diff --git a/lambdas/downloader/Pipfile b/lambdas/downloader/Pipfile index 6c5c864c..1c1696d3 100644 --- a/lambdas/downloader/Pipfile +++ b/lambdas/downloader/Pipfile @@ -15,7 +15,7 @@ db = {editable = true, path = "./../../layers/db"} pytest-docker = "==2.0.1" alembic = "==1.12.1" moto = "==5.0.17" -psycopg2 = "==2.9.10" +psycopg2-binary = "==2.9.10" assertpy = "==1.1" responses = "==0.23.1" freezegun = "==1.0.0" diff --git a/lambdas/downloader/Pipfile.lock b/lambdas/downloader/Pipfile.lock index 51d5a653..89c789e7 100644 --- a/lambdas/downloader/Pipfile.lock +++ b/lambdas/downloader/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e58efc825094313dd6dacac89c3e5f270f54cf3f9d550c871d75b84cd7f9c3ab" + "sha256": "efe1a983b300a24acee015f2cca32ffdc8d6f5f614ca5809476c3633077e7102" }, "pipfile-spec": 6, "requires": { @@ -38,19 +38,19 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "charset-normalizer": { "hashes": [ @@ -284,19 +284,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -367,11 +367,11 @@ }, "attrs": { "hashes": [ - "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", - "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" ], - "markers": "python_version >= '3.7'", - "version": "==24.2.0" + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "boto3": { "hashes": [ @@ -384,19 +384,19 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "cffi": { "hashes": [ @@ -587,104 +587,104 @@ "toml" ], "hashes": [ - "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", - "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", - "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", - "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", - "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", - "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", - "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", - "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", - "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", - "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", - "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", - "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", - "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", - "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", - "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", - "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", - "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", - "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", - "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", - "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", - "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", - "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", - "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", - "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", - "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", - "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", - "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", - "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", - "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", - "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", - "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", - "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", - "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", - "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", - "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", - "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", - "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", - "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", - "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", - "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", - "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", - "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", - "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", - "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", - "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", - "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", - "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", - "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", - "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", - "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", - "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", - "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", - "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", - "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", - "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", - "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", - "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", - "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", - "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", - "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", - "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", - "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "markers": "python_version >= '3.9'", - "version": "==7.6.4" + "version": "==7.6.9" }, "cryptography": { "hashes": [ - "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", - "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", - "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", - "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", - "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", - "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", - "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", - "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", - "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", - "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", - "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", - "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", - "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", - "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", - "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", - "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", - "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", - "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", - "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", - "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", - "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", - "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", - "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", - "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", - "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", - "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", - "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", + "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731", + "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", + "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", + "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", + "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", + "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", + "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", + "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", + "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", + "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", + "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", + "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", + "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", + "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", + "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", + "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa", + "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", + "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", + "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", + "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", + "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", + "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", + "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", + "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", + "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756", + "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4" ], - "markers": "python_version >= '3.7'", - "version": "==43.0.3" + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.0" }, "db": { "editable": true, @@ -812,11 +812,11 @@ }, "mako": { "hashes": [ - "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", - "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a" + "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627", + "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8" ], "markers": "python_version >= '3.8'", - "version": "==1.3.6" + "version": "==1.3.8" }, "markupsafe": { "hashes": [ @@ -896,11 +896,11 @@ }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pluggy": { "hashes": [ @@ -910,17 +910,75 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, - "psycopg2": { + "psycopg2-binary": { "hashes": [ - "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", - "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", - "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", - "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", - "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716", - "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", - "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442", - "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b", - "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", "markers": "python_version >= '3.8'", @@ -1073,19 +1131,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -1154,11 +1212,11 @@ }, "werkzeug": { "hashes": [ - "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", - "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.8'", - "version": "==3.0.4" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "xmltodict": { "hashes": [ diff --git a/lambdas/link_fetcher/Pipfile b/lambdas/link_fetcher/Pipfile index 9bb9bd47..9067a593 100644 --- a/lambdas/link_fetcher/Pipfile +++ b/lambdas/link_fetcher/Pipfile @@ -9,9 +9,12 @@ humanfriendly = "==9.1" requests = "==2.31.0" iso8601 = "==2.0.0" sqlalchemy = "==1.4.0" +fastapi = "*" +starlette = "*" +mangum = "*" [dev-packages] -boto3-stubs = {version = "==1.17.10.0", extras = ["sqs"]} +boto3-stubs = {version = "==1.17.10.0", extras = ["sqs", "ssm"]} freezegun = "==1.0.0" assertpy = "==1.1" pytest = "==7.4.3" @@ -19,13 +22,17 @@ responses = "==0.23.1" alembic = "==1.12.1" moto = "==5.0.17" db = {editable = true, path = "./../../layers/db"} -psycopg2 = "==2.9.10" +psycopg2-binary = "==2.9.10" pytest-docker = "==2.0.1" pytest-cov = "==4.1.0" mypy = "==1.6.0" types-requests = "==2.31.0" types-humanfriendly = "*" ruff = "==0.7.1" +uvicorn = "*" +httpx = "*" +click = "*" +pytest-mock = "*" [requires] python_version = "3.11" diff --git a/lambdas/link_fetcher/Pipfile.lock b/lambdas/link_fetcher/Pipfile.lock index cbd0adf1..b439e0da 100644 --- a/lambdas/link_fetcher/Pipfile.lock +++ b/lambdas/link_fetcher/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "72bdce1436bedb770bbee36ae3ac0b7906c80706726f6a408592f7b945935dfc" + "sha256": "c21f2b40d849814997f95bad4cebdf047b5d1c0972408e08d3bd446f64b6c208" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,22 @@ ] }, "default": { + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, + "anyio": { + "hashes": [ + "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", + "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352" + ], + "markers": "python_version >= '3.9'", + "version": "==4.7.0" + }, "boto3": { "hashes": [ "sha256:18416d07b41e6094101a44f8b881047dcec6b846dad0b9f83b9bbf2f0cd93d07", @@ -27,19 +43,19 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:78dd7bf8f49616d00073698d7bbaf5a115208fe730b7b7afae4456adddb3552e", + "sha256:e43b97d8cbf19d35ce3a177f144bd97cc370f0a67d0984c7d7cf105ac198748f" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.82" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "charset-normalizer": { "hashes": [ @@ -152,6 +168,15 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.4.0" }, + "fastapi": { + "hashes": [ + "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", + "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.115.6" + }, "greenlet": { "hashes": [ "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", @@ -265,6 +290,129 @@ "markers": "python_version >= '3.7'", "version": "==1.0.1" }, + "mangum": { + "hashes": [ + "sha256:e388e7c491b7b67970f8234e46fd4a7b21ff87785848f418de08148f71cf0bd6", + "sha256:e500b35f495d5e68ac98bc97334896d6101523f2ee2c57ba6a61893b65266e59" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.19.0" + }, + "pydantic": { + "hashes": [ + "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d", + "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.10.3" + }, + "pydantic-core": { + "hashes": [ + "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9", + "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b", + "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", + "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", + "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", + "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854", + "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", + "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", + "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a", + "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", + "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", + "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", + "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", + "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", + "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", + "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97", + "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", + "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", + "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", + "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4", + "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", + "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131", + "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", + "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd", + "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", + "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", + "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", + "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60", + "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", + "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", + "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", + "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", + "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2", + "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", + "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", + "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", + "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62", + "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", + "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be", + "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067", + "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", + "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f", + "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", + "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840", + "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5", + "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", + "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", + "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", + "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864", + "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e", + "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", + "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", + "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", + "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a", + "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3", + "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", + "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", + "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31", + "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", + "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", + "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", + "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36", + "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", + "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154", + "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", + "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", + "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd", + "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3", + "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", + "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78", + "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", + "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618", + "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", + "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", + "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", + "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c", + "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", + "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", + "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792", + "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", + "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9", + "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", + "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01", + "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", + "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", + "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f", + "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd", + "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", + "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab", + "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", + "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", + "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", + "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", + "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", + "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967", + "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", + "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", + "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", + "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", + "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.27.1" + }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", @@ -284,19 +432,27 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" + }, + "sniffio": { + "hashes": [ + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" }, "sqlalchemy": { "hashes": [ @@ -339,6 +495,23 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==1.4.0" }, + "starlette": { + "hashes": [ + "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", + "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.41.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.12.2" + }, "urllib3": { "hashes": [ "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", @@ -358,6 +531,14 @@ "markers": "python_version >= '3.7'", "version": "==1.12.1" }, + "anyio": { + "hashes": [ + "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", + "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352" + ], + "markers": "python_version >= '3.9'", + "version": "==4.7.0" + }, "assertpy": { "hashes": [ "sha256:acc64329934ad71a3221de185517a43af33e373bb44dc05b5a9b174394ef4833" @@ -367,11 +548,11 @@ }, "attrs": { "hashes": [ - "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", - "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" ], - "markers": "python_version >= '3.7'", - "version": "==24.2.0" + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "boto3": { "hashes": [ @@ -384,7 +565,8 @@ }, "boto3-stubs": { "extras": [ - "sqs" + "sqs", + "ssm" ], "hashes": [ "sha256:9d686cbc2147cd323c3b1815d60f7a0b6e98f6f578a69256f252a81347394573", @@ -395,19 +577,19 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:78dd7bf8f49616d00073698d7bbaf5a115208fe730b7b7afae4456adddb3552e", + "sha256:e43b97d8cbf19d35ce3a177f144bd97cc370f0a67d0984c7d7cf105ac198748f" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.82" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "cffi": { "hashes": [ @@ -593,109 +775,118 @@ "markers": "python_full_version >= '3.7.0'", "version": "==3.4.0" }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, "coverage": { "extras": [ "toml" ], "hashes": [ - "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", - "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", - "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", - "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", - "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", - "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", - "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", - "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", - "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", - "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", - "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", - "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", - "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", - "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", - "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", - "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", - "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", - "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", - "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", - "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", - "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", - "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", - "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", - "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", - "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", - "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", - "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", - "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", - "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", - "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", - "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", - "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", - "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", - "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", - "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", - "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", - "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", - "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", - "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", - "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", - "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", - "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", - "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", - "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", - "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", - "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", - "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", - "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", - "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", - "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", - "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", - "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", - "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", - "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", - "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", - "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", - "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", - "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", - "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", - "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", - "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", - "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "markers": "python_version >= '3.9'", - "version": "==7.6.4" + "version": "==7.6.9" }, "cryptography": { "hashes": [ - "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", - "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", - "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", - "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", - "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", - "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", - "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", - "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", - "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", - "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", - "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", - "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", - "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", - "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", - "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", - "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", - "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", - "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", - "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", - "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", - "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", - "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", - "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", - "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", - "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", - "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", - "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", + "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731", + "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", + "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", + "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", + "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", + "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", + "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", + "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", + "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", + "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", + "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", + "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", + "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", + "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", + "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", + "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa", + "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", + "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", + "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", + "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", + "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", + "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", + "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", + "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", + "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756", + "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4" ], - "markers": "python_version >= '3.7'", - "version": "==43.0.3" + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.0" }, "db": { "editable": true, @@ -789,6 +980,31 @@ "markers": "python_version >= '3'", "version": "==3.1.1" }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", + "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.7" + }, + "httpx": { + "hashes": [ + "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", + "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.28.1" + }, "idna": { "hashes": [ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", @@ -823,11 +1039,11 @@ }, "mako": { "hashes": [ - "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", - "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a" + "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627", + "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8" ], "markers": "python_version >= '3.8'", - "version": "==1.3.6" + "version": "==1.3.8" }, "markupsafe": { "hashes": [ @@ -946,6 +1162,13 @@ ], "version": "==1.17.10.0" }, + "mypy-boto3-ssm": { + "hashes": [ + "sha256:2f160ba434f25eb7be696f6a342f829f8e6fe8b6dbcb93b844b913196af1e215", + "sha256:7a4338334cd68b0cb2c213ed2eaba586d8cbddc4c7eaf81cf9d496396f3d5fb6" + ], + "version": "==1.17.10.0" + }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", @@ -956,11 +1179,11 @@ }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pluggy": { "hashes": [ @@ -970,17 +1193,75 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, - "psycopg2": { + "psycopg2-binary": { "hashes": [ - "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", - "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", - "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", - "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", - "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716", - "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", - "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442", - "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b", - "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", "markers": "python_version >= '3.8'", @@ -1021,6 +1302,15 @@ "markers": "python_version >= '3.6'", "version": "==2.0.1" }, + "pytest-mock": { + "hashes": [ + "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", + "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.14.0" + }, "python-dateutil": { "hashes": [ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", @@ -1133,19 +1423,27 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" + }, + "sniffio": { + "hashes": [ + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" }, "sqlalchemy": { "hashes": [ @@ -1190,12 +1488,12 @@ }, "types-humanfriendly": { "hashes": [ - "sha256:c0ca654b16ff36ed4c059152085a76c9909272f5ea6ac03cdb7e59b5dcd392c7", - "sha256:c87b2eb8ec2ab1ae51cb3cc2c0efee31622a6711b2a7627d8109e22b4cfd4366" + "sha256:9fd55a80481f3def1a1fa1f057e765f578bc3dde2d9c7034ccf4c6ca9177e8c1", + "sha256:c2cab19b77c7a81e4005a3512c7f352310406d6929f300487c423e4df5a3ec3d" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==10.0.1.11" + "markers": "python_version >= '3.8'", + "version": "==10.0.1.20241105" }, "types-pyyaml": { "hashes": [ @@ -1236,13 +1534,22 @@ "markers": "python_version >= '3.8'", "version": "==2.2.3" }, + "uvicorn": { + "hashes": [ + "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", + "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.34.0" + }, "werkzeug": { "hashes": [ - "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", - "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.8'", - "version": "==3.0.4" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "xmltodict": { "hashes": [ diff --git a/lambdas/link_fetcher/README.md b/lambdas/link_fetcher/README.md index 5ec4a546..2994a969 100644 --- a/lambdas/link_fetcher/README.md +++ b/lambdas/link_fetcher/README.md @@ -1,14 +1,23 @@ # Link Fetcher 🔗 -## High level overview +As of April, 2024 the Copernicus Data Space Ecosystem supports two forms of gathering +links to downloadable Sentinel products, + +* Search API ("polling based") +* Subscriptions API ("event based") + +We wish to migrate from a polling to an event driven method of link +fetching. During the transition period, this subdirectory handles both +of these methods and deploys them as two separate Lambda functions. This +README describes both forms of link fetching. + +## High level overview (Polling) ![Link fetcher in S2 Downloader diagram](../../images/hls-s2-downloader-link-fetcher.png) The Link Fetchers purpose is to query Copernicus Data Space Ecosystem for new imagery links to download. It is invoked within the `Link Fetching` Step Function; every invocation is performed on one day in the form `YYYY-MM-DD`. Images to download are stored as records in the `granule` table, the `granule_count` table is also updated with available and fetched link counts. The `To Download` queue is also populated with the images IDs and download URLs. ---- - -## Handler breakdown +## Handler breakdown (Polling) Provided below is some pseudo-code to explain the process happening each time the lambda is invoked: @@ -36,9 +45,53 @@ while there_is_still_imagery_to_process: --- +## High level overview (Event Based) + +![Link Subscription Handler](../../images/hls-s2-downloader-link-subscription.png) + +The link subscription handler's purpose is to handle "push" events from Copernicus Data Space Ecosystem's +Subscriptions API for new imagery links to download. It uses API Gateway to provide a publicly accessible endpoint +that triggers the Lambda function. Images to download are stored as records in the `granule` table. +The `To Download` queue is also populated with the images IDs and download URLs. + +ESA has provided documentation and an example application for handling the "push" subscriptions, + +* https://documentation.dataspace.copernicus.eu/APIs/Subscriptions.html#push-subscriptions +* https://gitlab.cloudferro.com/cat_public/push_subscription_endpoint_example + +## Handler breakdown (Event Based) + +The "push" subscription endpoint handles "granule created" events that ESA sends to our endpoint. +Each subscription "push" event includes a payload describing one (1) new granule that ESA has +published and made available online to download. The following pseudo-code describes what +the subscription handler Lambda function does each time it is invoked, + + +```python +if not user_password_correct(): + raise Unauthorized() + +new_granule = parse_event_payload() + +# bail if newly published granule was acquired too long ago to consider +# (this helps us avoid newly reprocessed images acquired years ago) +if not granule_is_recently_acquired(new_granule): + return + +# bail if newly published granule is not for a tile ID we want to process +if not granule_is_for_desired_mgrs_tile(new_granule): + return + +# record in DB and send to download queue if we've not seen this +# granule ID before (i.e., exactly once processing) +add_results_to_db_and_sqs_queue(filtered_results) +``` + +--- + ## Development -This Lambda makes use of `pipenv` for managing depedencies and for building the function when deploying it. +This Lambda makes use of `pipenv` for managing dependencies and for building the function when deploying it. To get setup for developing this project, run: @@ -76,11 +129,11 @@ A `Makefile` is provided to abstract commonly used commands away: **`make lint`** -> This will perform a dry run of `flake8`, `isort`, and `black` and let you know what issues were found +> This will perform a dry run of `ruff` and let you know what issues were found **`make format`** -> This will peform a run of `isort` and `black`, this **will** modify files if issues were found +> This will perform a run of `ruff`, this **will** modify files if issues were found **`make test`** diff --git a/lambdas/link_fetcher/app/__init__.py b/lambdas/link_fetcher/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lambdas/link_fetcher/allowed_tiles.txt b/lambdas/link_fetcher/app/allowed_tiles.txt similarity index 100% rename from lambdas/link_fetcher/allowed_tiles.txt rename to lambdas/link_fetcher/app/allowed_tiles.txt diff --git a/lambdas/link_fetcher/app/common.py b/lambdas/link_fetcher/app/common.py new file mode 100644 index 00000000..f48dab64 --- /dev/null +++ b/lambdas/link_fetcher/app/common.py @@ -0,0 +1,140 @@ +import json +import os +import re +from dataclasses import dataclass +from datetime import datetime +from typing import ( + TYPE_CHECKING, + Callable, + Final, + Sequence, + Set, +) + +import boto3 +from db.models.granule import Granule +from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm import Session +from typing_extensions import TypeAlias + +if TYPE_CHECKING: + from mypy_boto3_sqs.client import SQSClient + +SessionMaker: TypeAlias = Callable[[], Session] + +ACCEPTED_TILE_IDS_FILENAME: Final = "allowed_tiles.txt" + + +@dataclass(frozen=True) +class SearchResult: + image_id: str + filename: str + tileid: str + size: int + beginposition: datetime + endposition: datetime + ingestiondate: datetime + download_url: str + + +def parse_tile_id_from_title(title: str) -> str: + # The tile ID is encoded into the filename (title). It is embedded as + # `_TXXXXX_`, where `XXXXX` is the 5-character alphanumeric tile ID. + # https://sentinels.copernicus.eu/ca/web/sentinel/user-guides/sentinel-2-msi/naming-convention + match = re.search("_T(?P[0-9A-Z]{5})_", title) + tile_id = match["tile_id"] if match else "" + return tile_id + + +def get_accepted_tile_ids() -> Set[str]: + """ + Return MGRS square IDs acceptable for processing within the downloader. + + :returns: set of all acceptable MGRS square IDs + """ + accepted_tile_ids_filepath = os.path.join( + os.path.dirname(os.path.abspath(__file__)), ACCEPTED_TILE_IDS_FILENAME + ) + + with open(accepted_tile_ids_filepath) as tile_ids_in: + return {line.strip() for line in tile_ids_in} + + +def filter_search_results( + search_results: Sequence[SearchResult], + accepted_tile_ids: Set[str], +) -> Sequence[SearchResult]: + """ + Filters the given search results list and returns a list of results that tile ids + are within our accepted list of ids. + + :param search_results: List[SearchResult] representing the results of a query to + search + :param accepted_tile_ids: Set[str] representing acceptable MGRS tile ids + :returns: List[searchResult] representing a filtered version of the given results + """ + return tuple( + search_result + for search_result in search_results + if search_result.tileid in accepted_tile_ids + ) + + +def add_search_results_to_db_and_sqs( + session_maker: SessionMaker, search_results: Sequence[SearchResult] +): + """ + Creates a record in the `granule` table for each of the provided SearchResults and + a SQS Message in the `To Download` Queue. + If a record is already in the `granule` table, it will throw an exception which + when caught, will rollback the insertion and the SQS Message will not be added. + :param session_maker: sessionmaker representing the SQLAlchemy sessionmaker to use + for adding results + :param search_results: list of search results to add to the + `granule` table + """ + sqs_client = boto3.client("sqs") + to_download_queue_url = os.environ["TO_DOWNLOAD_SQS_QUEUE_URL"] + + with session_maker() as session: + for result in search_results: + try: + session.add( + Granule( + id=result.image_id, + filename=result.filename, + tileid=result.tileid, + size=result.size, + beginposition=result.beginposition, + endposition=result.endposition, + ingestiondate=result.ingestiondate, + download_url=result.download_url, + ) # type: ignore + ) + session.commit() + add_search_result_to_sqs(result, sqs_client, to_download_queue_url) + except IntegrityError: + print(f"{result.image_id} already in Database, not adding") + session.rollback() + + +def add_search_result_to_sqs( + search_result: SearchResult, sqs_client: "SQSClient", queue_url: str +): + """ + Creates a message in the provided SQS queue for the provided + SearchResult. The message is in the form {"id": , "download_url": } + :param search_result: search result to add to the SQS queue + :param sqs_client: SQSClient representing a boto3 SQS client + :param queue_url: str presenting the URL of the queue to send the message to + """ + sqs_client.send_message( + QueueUrl=queue_url, + MessageBody=json.dumps( + { + "id": search_result.image_id, + "filename": search_result.filename, + "download_url": search_result.download_url, + } + ), + ) diff --git a/lambdas/link_fetcher/handler.py b/lambdas/link_fetcher/app/search_handler.py similarity index 68% rename from lambdas/link_fetcher/handler.py rename to lambdas/link_fetcher/app/search_handler.py index b34cb0d4..73ebc955 100644 --- a/lambdas/link_fetcher/handler.py +++ b/lambdas/link_fetcher/app/search_handler.py @@ -1,40 +1,32 @@ -import json import os -import re -from dataclasses import dataclass from datetime import date, datetime, timedelta from typing import ( - TYPE_CHECKING, Any, - Callable, Final, Literal, Mapping, Protocol, Sequence, - Set, Tuple, TypedDict, ) -import boto3 import humanfriendly import iso8601 import requests -from db.models.granule import Granule from db.models.granule_count import GranuleCount from db.models.status import Status from db.session import get_session_maker -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session -from typing_extensions import TypeAlias -if TYPE_CHECKING: - from mypy_boto3_sqs.client import SQSClient - -SessionMaker: TypeAlias = Callable[[], Session] +from app.common import ( + SearchResult, + SessionMaker, + add_search_results_to_db_and_sqs, + filter_search_results, + get_accepted_tile_ids, + parse_tile_id_from_title, +) -ACCEPTED_TILE_IDS_FILENAME: Final = "allowed_tiles.txt" MIN_REMAINING_MILLIS: Final = 60_000 SEARCH_URL: Final = os.environ.get( "SEARCH_URL", @@ -43,18 +35,6 @@ Platform = Literal["S2A", "S2B"] -@dataclass(frozen=True) -class SearchResult: - image_id: str - filename: str - tileid: str - size: int - beginposition: datetime - endposition: datetime - ingestiondate: datetime - download_url: str - - class Context(Protocol): def get_remaining_time_in_millis(self) -> int: ... @@ -114,66 +94,6 @@ def _handler( } -def add_search_results_to_db_and_sqs( - session_maker: SessionMaker, search_results: Sequence[SearchResult] -): - """ - Creates a record in the `granule` table for each of the provided SearchResults and - a SQS Message in the `To Download` Queue. - If a record is already in the `granule` table, it will throw an exception which - when caught, will rollback the insertion and the SQS Message will not be added. - :param session_maker: sessionmaker representing the SQLAlchemy sessionmaker to use - for adding results - :param search_results: list of search results to add to the - `granule` table - """ - sqs_client = boto3.client("sqs") - to_download_queue_url = os.environ["TO_DOWNLOAD_SQS_QUEUE_URL"] - - with session_maker() as session: - for result in search_results: - try: - session.add( - Granule( - id=result.image_id, - filename=result.filename, - tileid=result.tileid, - size=result.size, - beginposition=result.beginposition, - endposition=result.endposition, - ingestiondate=result.ingestiondate, - download_url=result.download_url, - ) # type: ignore - ) - session.commit() - add_search_result_to_sqs(result, sqs_client, to_download_queue_url) - except IntegrityError: - print(f"{result.image_id} already in Database, not adding") - session.rollback() - - -def add_search_result_to_sqs( - search_result: SearchResult, sqs_client: "SQSClient", queue_url: str -): - """ - Creates a message in the provided SQS queue for the provided - SearchResult. The message is in the form {"id": , "download_url": } - :param search_result: search result to add to the SQS queue - :param sqs_client: SQSClient representing a boto3 SQS client - :param queue_url: str presenting the URL of the queue to send the message to - """ - sqs_client.send_message( - QueueUrl=queue_url, - MessageBody=json.dumps( - { - "id": search_result.image_id, - "filename": search_result.filename, - "download_url": search_result.download_url, - } - ), - ) - - def get_fetched_links( session_maker: SessionMaker, day: date, platform: Platform ) -> int: @@ -278,40 +198,6 @@ def update_fetched_links( session.commit() -def get_accepted_tile_ids() -> Set[str]: - """ - Return MGRS square IDs acceptable for processing within the downloader. - - :returns: set of all acceptable MGRS square IDs - """ - accepted_tile_ids_filepath = os.path.join( - os.path.dirname(os.path.abspath(__file__)), ACCEPTED_TILE_IDS_FILENAME - ) - - with open(accepted_tile_ids_filepath) as tile_ids_in: - return {line.strip() for line in tile_ids_in} - - -def filter_search_results( - search_results: Sequence[SearchResult], - accepted_tile_ids: Set[str], -) -> Sequence[SearchResult]: - """ - Filters the given search results list and returns a list of results that tile ids - are within our accepted list of ids. - - :param search_results: List[SearchResult] representing the results of a query to - search - :param accepted_tile_ids: Set[str] representing acceptable MGRS tile ids - :returns: List[searchResult] representing a filtered version of the given results - """ - return tuple( - search_result - for search_result in search_results - if search_result.tileid in accepted_tile_ids - ) - - def get_query_parameters( start: int, day: date, platform: Platform ) -> Mapping[str, Any]: @@ -355,12 +241,7 @@ def create_search_result(search_item: Mapping[str, Any]) -> SearchResult: download = properties["services"]["download"] size = humanfriendly.parse_size(str(download["size"]), binary=True) title = properties["title"] - - # The tile ID is encoded into the filename (title). It is embedded as - # `_TXXXXX_`, where `XXXXX` is the 5-character alphanumeric tile ID. - # https://sentinels.copernicus.eu/ca/web/sentinel/user-guides/sentinel-2-msi/naming-convention - match = re.search("_T(?P[0-9A-Z]{5})_", title) - tile_id = match["tile_id"] if match else "" + tile_id = parse_tile_id_from_title(title) return SearchResult( image_id=search_item["id"], diff --git a/lambdas/link_fetcher/app/subscription_endpoint.py b/lambdas/link_fetcher/app/subscription_endpoint.py new file mode 100644 index 00000000..fd8e0c05 --- /dev/null +++ b/lambdas/link_fetcher/app/subscription_endpoint.py @@ -0,0 +1,215 @@ +import json +import logging +import os +import secrets +from dataclasses import asdict, dataclass, field +from datetime import datetime, timedelta, timezone +from typing import TYPE_CHECKING, Any, Callable +from urllib.parse import urljoin + +import boto3 +import iso8601 +from db.session import get_session_maker +from fastapi import Depends, FastAPI, HTTPException, Response +from fastapi.middleware.cors import CORSMiddleware +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from starlette.requests import Request + +from app.common import ( + SearchResult, + SessionMaker, + add_search_results_to_db_and_sqs, + filter_search_results, + get_accepted_tile_ids, + parse_tile_id_from_title, +) + +if TYPE_CHECKING: + from mypy_boto3_ssm.client import SSMClient + +logger = logging.getLogger(__name__) + + +@dataclass +class EndpointConfig: + """Configuration settings for subscription 'push' endpoint""" + + stage: str = field(default_factory=lambda: os.getenv("STAGE")) + + # user auth + notification_username: str = field( + default_factory=lambda: os.getenv("NOTIFICATION_USERNAME") + ) + notification_password: str = field( + default_factory=lambda: os.getenv("NOTIFICATION_PASSWORD") + ) + + def __post_init__(self): + for attr, value in asdict(self).items(): + if value is None: + raise ValueError( + f"EndpointConfig attribute '{attr}' must be defined (got None)" + ) + + @classmethod + def load_from_secrets_manager(cls, stage: str) -> "EndpointConfig": + """Load from AWS Secret Manager for some `stage`""" + secret_id = f"hls-s2-downloader-serverless/{stage}/esa-subscription-credentials" + try: + secrets_manager_client = boto3.client("secretsmanager") + secret = json.loads( + secrets_manager_client.get_secret_value( + SecretId=secret_id, + )["SecretString"] + ) + except Exception as ex: + raise RuntimeError( + "Could not retrieve ESA subscription credentials from Secrets Manager" + ) from ex + + return cls( + stage=stage, + notification_username=secret["notification_username"], + notification_password=secret["notification_password"], + ) + + def get_endpoint_url( + self, + ssm_client: "SSMClient", + ) -> str: + """Return the endpoint URL stored in SSM parameter store""" + param_name = ( + f"/hls-s2-downloader-serverless/{self.stage}/link_subscription_endpoint_url" + ) + result = ssm_client.get_parameter(Name=param_name) + if (url := result["Parameter"].get("Value")) is None: + raise ValueError(f"No such SSM parameter {param_name}") + return urljoin(url, "events") + + +def parse_search_result( + payload: dict, +) -> SearchResult: + """Parse a subscription event payload to a SearchResult""" + # There should only be 1 link to "extracted" data file + extracted_links = [ + location + for location in payload["Locations"] + if location["FormatType"] == "Extracted" + ] + if len(extracted_links) != 1: + raise ValueError( + f"Got {len(extracted_links)} 'Extracted' links, expected just 1" + ) + + # The "extracted" data information looks like, + # * FormatType: "Extracted" + # * DownloadLink: str + # * ContentLength: int + # * Checksum: { "Value": str, "Algorithm": "MD5" | "BLAKE3", "ChecksumDate": datetime} + # * S3Path: str + extracted = extracted_links[0] + + search_result = SearchResult( + image_id=payload["Id"], + filename=payload["Name"], + tileid=parse_tile_id_from_title(payload["Name"]), + size=extracted["ContentLength"], + beginposition=iso8601.parse_date(payload["ContentDate"]["Start"]), + endposition=iso8601.parse_date(payload["ContentDate"]["End"]), + ingestiondate=iso8601.parse_date(payload["PublicationDate"]), + download_url=extracted["DownloadLink"], + ) + return search_result + + +def process_notification( + notification: dict[str, Any], + accepted_tile_ids: set[str], + session_maker: SessionMaker, + now_utc: Callable[[], datetime] = lambda: datetime.now(tz=timezone.utc), +): + """Parse, filter, and potentially add new granule results to download queue""" + # Parse subscription notification to SearchResult + search_result = parse_search_result(notification["value"]) + + # Only consider imagery acquired in the last 30 days to avoid reprocessing of older imagery + oldest_acquisition_date = now_utc() - timedelta(days=30) + if search_result.beginposition < oldest_acquisition_date: + logger.info(f"Rejected {search_result=} (acquisition date too old)") + return + + # Check tile ID + accepted_search_results = filter_search_results( + [search_result], + accepted_tile_ids, + ) + if accepted_search_results: + logger.info(f"Adding {search_result=} to granule download queue") + add_search_results_to_db_and_sqs( + session_maker, + accepted_search_results, + ) + else: + logger.info(f"Rejected {search_result=} (unacceptable tile)") + + +def build_app( + config: EndpointConfig, + now_utc: Callable[[], datetime] = lambda: datetime.now(tz=timezone.utc), +) -> FastAPI: + """Create FastAPI app""" + app = FastAPI() + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + security = HTTPBasic() + + accepted_tile_ids = get_accepted_tile_ids() + + @app.post("/events", status_code=204) + def post_notification( + request: Request, + notification: dict[str, Any], + credentials: HTTPBasicCredentials = Depends(security), + session_maker: SessionMaker = Depends(get_session_maker), + ) -> Response: + """ + Endpoint which uses Basic Auth and processes acquired notification. + """ + # check Basic authorization + if not ( + secrets.compare_digest( + credentials.username.encode("utf-8"), + config.notification_username.encode("utf-8"), + ) + and secrets.compare_digest( + credentials.password.encode("utf-8"), + config.notification_password.encode("utf-8"), + ) + ): + logging.error("Unauthorized") + raise HTTPException(status_code=401, detail="Unauthorized") + + process_notification( + notification=notification, + accepted_tile_ids=accepted_tile_ids, + session_maker=session_maker, + now_utc=now_utc, + ) + return Response(status_code=204) + + return app + + +if __name__ == "__main__": + # for local dev + import uvicorn + + config = EndpointConfig() + app = build_app(config) + uvicorn.run(app) diff --git a/lambdas/link_fetcher/app/subscription_handler.py b/lambdas/link_fetcher/app/subscription_handler.py new file mode 100644 index 00000000..99928933 --- /dev/null +++ b/lambdas/link_fetcher/app/subscription_handler.py @@ -0,0 +1,15 @@ +import logging +import os + +from mangum import Mangum + +from app.subscription_endpoint import ( + EndpointConfig, + build_app, +) + +logging.getLogger("app").setLevel(logging.INFO) + +config = EndpointConfig.load_from_secrets_manager(os.environ["STAGE"]) +app = build_app(config) +handler = Mangum(app) diff --git a/lambdas/link_fetcher/manage_subscription.py b/lambdas/link_fetcher/manage_subscription.py new file mode 100644 index 00000000..8f4c0c07 --- /dev/null +++ b/lambdas/link_fetcher/manage_subscription.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python +""" +This script file is copied with modification from the ESA "push" subscription +example code repository, "push_subscription_endpoint_example": +https://gitlab.cloudferro.com/cat_public/push_subscription_endpoint_example + +This script is originally MIT licensed by CloudFerro, + + Copyright (c) 2024 CloudFerro + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Modifications are largely replacing Pydantic with "dataclasses" from stdlib and +adding a CLI to run list/create/terminate subscriptions. +""" + +import datetime as dt +import json +import os +import urllib.parse +from dataclasses import dataclass +from typing import Literal + +import boto3 +import click +import requests + +from app.subscription_endpoint import EndpointConfig + +ACCESS_TOKEN_REQUEST_DATA: str = ( + "client_id={client_id}&" + "username={user_email}&" + "password={password}&" + "grant_type=password&" +) + +REFRESH_ACCESS_TOKEN_DATA: str = ( + "client_id={client_id}&" + "refresh_token={refresh_token}&" + "grant_type=refresh_token&" +) + + +@dataclass +class SubscriptionAPIConfig: + client_id: str = os.getenv("ESA_CDSE_CLIENT_ID", "cdse-public") + user_email: str = os.getenv("ESA_CDSE_USER_EMAIL") + user_password: str = os.getenv("ESA_CDSE_USER_PASSWORD") + + identity_token_api_url: str = os.getenv( + "ESA_CDSE_TOKEN_API_URL", + "https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token", + ) + subscriptions_api_base_url: str = os.getenv( + "ESA_CDSE_SUBSCRIPTION_API_BASE_URL", + "https://catalogue.dataspace.copernicus.eu/odata/v1/Subscriptions", + ) + + def __post_init__(self): + if self.user_email is None: + raise ValueError("Must set user_email") + if self.user_password is None: + raise ValueError("Must set user_password") + + +@dataclass +class Token: + """ESA token""" + + access_token: str + refresh_token: str + expires_at: dt.datetime + refresh_expires_at: dt.datetime + + @property + def is_expired(self) -> bool: + return dt.datetime.now() >= self.expires_at + + @property + def is_refreshable(self) -> bool: + return dt.datetime.now() >= self.refresh_expires_at + + +@dataclass +class TokenAPI: + config: SubscriptionAPIConfig + + def get_access_token(self) -> Token: + """ + Get access token which will be used in Subscriptions API. + """ + # create data for request to get access token + data = ACCESS_TOKEN_REQUEST_DATA.format( + client_id=urllib.parse.quote(self.config.client_id), + user_email=urllib.parse.quote(self.config.user_email), + password=urllib.parse.quote(self.config.user_password), + ) + + # in headers we provide information in which format we send data + headers = {"Content-Type": "application/x-www-form-urlencoded"} + + # make request for access_token + now = dt.datetime.now() + response = requests.post( + url=self.config.identity_token_api_url, + headers=headers, + data=data, + ) + response.raise_for_status() + + # acquire access token and refresh token for future use + response_json = json.loads(response.content.decode("utf-8")) + + return Token( + access_token=response_json["access_token"], + refresh_token=response_json["refresh_token"], + expires_at=now + dt.timedelta(seconds=response_json["expires_in"]), + refresh_expires_at=now + + dt.timedelta(seconds=response_json["refresh_expires_in"]), + ) + + def refresh_token(self, token: Token) -> Token: + """ + Refresh your access token. + """ + # create data for request to get refresh access token + data = REFRESH_ACCESS_TOKEN_DATA.format( + client_id=urllib.parse.quote(self.config.client_id), + refresh_token=urllib.parse.quote(token.refresh_token), + ) + + # in headers we provide information in which format we send data + headers = {"Content-Type": "application/x-www-form-urlencoded"} + + # make request for refreshing token + now = dt.datetime.now() + response = requests.post( + url=self.config.identity_token_api_url, + headers=headers, + data=data, + ) + response.raise_for_status() + + # acquire access token and refresh token for future use + response_json = json.loads(response.content.decode("utf-8")) + + return Token( + access_token=response_json["access_token"], + refresh_token=response_json["refresh_token"], + expires_at=now + dt.timedelta(seconds=response_json["expires_in"]), + refresh_expires_at=now + + dt.timedelta(seconds=response_json["refresh_expires_in"]), + ) + + +@dataclass +class SubscriptionAPI: + """Create, list, and delete subscriptions""" + + token_api: TokenAPI + endpoint_config: EndpointConfig + + def create_subscription(self) -> str: + """ + Create example subscription, returning subscription ID + """ + token = self.token_api.get_access_token() + endpoint_url = self.endpoint_config.get_endpoint_url( + ssm_client=boto3.client("ssm") + ) + subscription_data = { + "StageOrder": True, + "FilterParam": "Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'productType' and att/OData.CSC.StringAttribute/Value eq 'S2MSI1C')", + "Priority": 1, + "NotificationEndpoint": endpoint_url, + "NotificationEpUsername": self.endpoint_config.notification_username, + "NotificationEpPassword": self.endpoint_config.notification_password, + "Status": "running", + "SubscriptionEvent": ["created"], + } + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token.access_token}", + } + response = requests.post( + url=self.token_api.config.subscriptions_api_base_url, + headers=headers, + json=subscription_data, + ) + response.raise_for_status() + subscription_information = response.json() + subscription_id = subscription_information["Id"] + print(f"Subscription created {subscription_id=}") + print("Below is full response:") + print(subscription_information) + return subscription_id + + def list_subscriptions(self) -> list[dict]: + """List subscriptions""" + token = self.token_api.get_access_token() + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token.access_token}", + } + response = requests.get( + url=f"{self.token_api.config.subscriptions_api_base_url}/Info", + headers=headers, + ) + response.raise_for_status() + subscriptions = response.json() + return subscriptions + + def terminate_subscription(self, subscription_id: str): + """ + Terminate test subscription. + """ + token = self.token_api.get_access_token() + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token.access_token}", + } + response = requests.delete( + url=f"{self.token_api.config.subscriptions_api_base_url}({subscription_id})", + headers=headers, + ) + response.raise_for_status() + print(f"Subscription terminated {subscription_id=}.") + + +@click.command() +@click.argument("command", type=click.Choice(["create", "list", "terminate"])) +@click.option( + "--email", + default=lambda: os.getenv("ESA_CDSE_EMAIL", ""), + help="CDSE user email for subscription", +) +@click.option( + "--password", + default=lambda: os.getenv("ESA_CDSE_PASSWORD", ""), + prompt=True, + help="CDSE user password for subscription", +) +def main( + command: Literal["create", "list", "terminate"], + email: str, + password: str, +): + """Manage ESA 'push' subscriptions""" + endpoint_cfg = EndpointConfig.load_from_secrets_manager(os.environ["STAGE"]) + subscription_cfg = SubscriptionAPIConfig( + user_email=email, + user_password=password, + ) + + token_api = TokenAPI(subscription_cfg) + + subscription_api = SubscriptionAPI(token_api, endpoint_cfg) + subscriptions = subscription_api.list_subscriptions() + + if command == "create": + if subscriptions: + click.echo("Cannot create a second subscription (only 1 active is allowed)") + raise click.Abort() + subscription = subscription_api.create_subscription() + click.echo(f"Created subscription id={subscription}") + + elif command == "list": + click.echo("Listing subscriptions:") + for subscription in subscriptions: + click.echo(subscription) + + elif command == "terminate": + subscription_id = subscriptions[0]["Id"] + click.echo("Terminating first listed subscription id={subscription_id}") + subscription_api.terminate_subscription(subscription_id) + + click.echo("Complete") + + +if __name__ == "__main__": + main() diff --git a/lambdas/link_fetcher/tests/conftest.py b/lambdas/link_fetcher/tests/conftest.py index 7428109e..a2feaad4 100644 --- a/lambdas/link_fetcher/tests/conftest.py +++ b/lambdas/link_fetcher/tests/conftest.py @@ -18,7 +18,7 @@ from sqlalchemy.exc import OperationalError from sqlalchemy.orm import Session -from handler import SEARCH_URL, SearchResult +from app.search_handler import SEARCH_URL, SearchResult UNIT_TEST_DIR = pathlib.Path(__file__).parent @@ -30,7 +30,7 @@ def mock_search_response(): @pytest.fixture def accepted_tile_ids() -> Set[str]: - with open(UNIT_TEST_DIR.parent / "allowed_tiles.txt") as lines: + with open(UNIT_TEST_DIR.parent / "app" / "allowed_tiles.txt") as lines: return set(map(str.strip, lines)) @@ -131,7 +131,8 @@ def sqs_client(): @pytest.fixture def mock_sqs_queue(request, sqs_resource, monkeysession, sqs_client): - queue = sqs_resource.create_queue(QueueName=f"mock-queue-{request.node.name}"[:80]) + request_name = hash(request.node.name) + queue = sqs_resource.create_queue(QueueName=f"mock-queue-{request_name}"[:80]) monkeysession.setenv("TO_DOWNLOAD_SQS_QUEUE_URL", queue.url) return queue diff --git a/lambdas/link_fetcher/tests/data/push-granule-created-s2-n1.json b/lambdas/link_fetcher/tests/data/push-granule-created-s2-n1.json new file mode 100644 index 00000000..f6cea7e7 --- /dev/null +++ b/lambdas/link_fetcher/tests/data/push-granule-created-s2-n1.json @@ -0,0 +1,209 @@ +{ + "@odata.context": "$metadata#Notification/$entity", + "SubscriptionEvent": "created", + "ProductId": "f867a59a-9336-46d0-93ae-e55bf29403f8", + "ProductName": "S2A_MSIL1C_20240912T112541_N0511_R137_T28PHQ_20240912T133420.SAFE", + "SubscriptionId": "880c4e1a-cfc7-4956-bc4c-4434069a0aa7", + "NotificationDate": "2024-09-12T14:52:52.000Z", + "AckId": "MTcyNjE1Mjc3MjMyMi0wOjg4MGM0ZTFhLWNmYzctNDk1Ni1iYzRjLTQ0MzQwNjlhMGFhNw==", + "value": { + "@odata.context": "$metadata#Products(Attributes())(Assets())(Locations())/$entity", + "@odata.mediaContentType": "application/octet-stream", + "Id": "f867a59a-9336-46d0-93ae-e55bf29403f8", + "Name": "S2A_MSIL1C_20240912T112541_N0511_R137_T28PHQ_20240912T133420.SAFE", + "ContentType": "application/octet-stream", + "ContentLength": 132384463, + "OriginDate": "2024-09-12T14:39:38.000Z", + "PublicationDate": "2024-09-12T14:52:06.118Z", + "ModificationDate": "2024-09-12T14:52:51.828Z", + "Online": true, + "EvictionDate": "9999-12-31T23:59:59.999Z", + "S3Path": "/eodata/Sentinel-2/MSI/L1C/2024/09/12/S2A_MSIL1C_20240912T112541_N0511_R137_T28PHQ_20240912T133420.SAFE", + "Checksum": [ + { + "Value": "700a3f2014ab3670408b94e4310924dd", + "Algorithm": "MD5", + "ChecksumDate": "2024-09-12T14:52:51.426545Z" + }, + { + "Value": "fd35ef962ec7a09b190dc4049fe98906cc89f16e1a4322b622a49fc62e3f6e43", + "Algorithm": "BLAKE3", + "ChecksumDate": "2024-09-12T14:52:51.657328Z" + } + ], + "ContentDate": { + "Start": "2024-09-12T11:25:41.024Z", + "End": "2024-09-12T11:25:41.024Z" + }, + "Footprint": "geography'SRID=4326;POLYGON ((-11.974999865007275 9.033981046236953, -12.271487717962122 9.03659015317746, -12.27853242584367 8.044545576909323, -12.199694512664326 8.043927404167855, -12.18648545337299 8.102176718496475, -12.15282241132278 8.250773715771231, -12.119125071984612 8.399275118590491, -12.085423254585894 8.547673138011769, -12.051758447762111 8.695945283833096, -12.018066565499025 8.844240589995735, -11.984403225613384 8.992620036679964, -11.974999865007275 9.033981046236953))'", + "GeoFootprint": { + "type": "Polygon", + "coordinates": [ + [ + [-11.974999865007275, 9.033981046236953], + [-12.271487717962122, 9.03659015317746], + [-12.27853242584367, 8.044545576909323], + [-12.199694512664326, 8.043927404167855], + [-12.18648545337299, 8.102176718496475], + [-12.15282241132278, 8.250773715771231], + [-12.119125071984612, 8.399275118590491], + [-12.085423254585894, 8.547673138011769], + [-12.051758447762111, 8.695945283833096], + [-12.018066565499025, 8.844240589995735], + [-11.984403225613384, 8.992620036679964], + [-11.974999865007275, 9.033981046236953] + ] + ] + }, + "Attributes": [ + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "origin", + "Value": "ESA", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "tileId", + "Value": "28PHQ", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.DoubleAttribute", + "Name": "cloudCover", + "Value": 97.680833965944, + "ValueType": "Double" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "datastripId", + "Value": "S2A_OPER_MSI_L1C_DS_2APS_20240912T133420_S20240912T112539_N05.11", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.IntegerAttribute", + "Name": "orbitNumber", + "Value": 48182, + "ValueType": "Integer" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "sourceProduct", + "Value": "S2A_OPER_MSI_L1C_TL_2APS_20240912T133420_A048182_T28PHQ_N05.11,S2A_OPER_MSI_L1C_DS_2APS_20240912T133420_S20240912T112539_N05.11,S2A_OPER_MSI_L1C_TC_2APS_20240912T133420_A048182_T28PHQ_N05.11.jp2", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.DateTimeOffsetAttribute", + "Name": "processingDate", + "Value": "2024-09-12T13:34:20+00:00", + "ValueType": "DateTimeOffset" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "productGroupId", + "Value": "GS2A_20240912T112541_048182_N05.11", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "operationalMode", + "Value": "INS-NOBS", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "processingLevel", + "Value": "S2MSI1C", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "processorVersion", + "Value": "05.11", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "granuleIdentifier", + "Value": "S2A_OPER_MSI_L1C_TL_2APS_20240912T133420_A048182_T28PHQ_N05.11", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "platformShortName", + "Value": "SENTINEL-2", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "instrumentShortName", + "Value": "MSI", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.IntegerAttribute", + "Name": "relativeOrbitNumber", + "Value": 137, + "ValueType": "Integer" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "sourceProductOriginDate", + "Value": "2024-09-12T14:39:37Z,2024-09-12T14:39:33Z,2024-09-12T14:39:38Z", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "platformSerialIdentifier", + "Value": "A", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.StringAttribute", + "Name": "productType", + "Value": "S2MSI1C", + "ValueType": "String" + }, + { + "@odata.type": "#OData.CSC.DateTimeOffsetAttribute", + "Name": "beginningDateTime", + "Value": "2024-09-12T11:25:41.024Z", + "ValueType": "DateTimeOffset" + }, + { + "@odata.type": "#OData.CSC.DateTimeOffsetAttribute", + "Name": "endingDateTime", + "Value": "2024-09-12T11:25:41.024Z", + "ValueType": "DateTimeOffset" + } + ], + "Assets": [ + { + "Type": "QUICKLOOK", + "Id": "97cd3cae-9999-4871-9767-9a44305415e6", + "DownloadLink": "https://catalogue.dataspace.copernicus.eu/odata/v1/Assets(97cd3cae-9999-4871-9767-9a44305415e6)/$value", + "S3Path": "/eodata/Sentinel-2/MSI/L1C/2024/09/12/S2A_MSIL1C_20240912T112541_N0511_R137_T28PHQ_20240912T133420.SAFE" + } + ], + "Locations": [ + { + "FormatType": "Extracted", + "DownloadLink": "https://catalogue.dataspace.copernicus.eu/odata/v1/Products(f867a59a-9336-46d0-93ae-e55bf29403f8)/$value", + "ContentLength": 132384463, + "Checksum": [ + { + "Value": "700a3f2014ab3670408b94e4310924dd", + "Algorithm": "MD5", + "ChecksumDate": "2024-09-12T14:52:51.426545Z" + }, + { + "Value": "fd35ef962ec7a09b190dc4049fe98906cc89f16e1a4322b622a49fc62e3f6e43", + "Algorithm": "BLAKE3", + "ChecksumDate": "2024-09-12T14:52:51.657328Z" + } + ], + "S3Path": "/eodata/Sentinel-2/MSI/L1C/2024/09/12/S2A_MSIL1C_20240912T112541_N0511_R137_T28PHQ_20240912T133420.SAFE" + } + ] + } +} diff --git a/lambdas/link_fetcher/tests/docker-compose.yml b/lambdas/link_fetcher/tests/docker-compose.yml index ff7b2f16..fb9b8806 100644 --- a/lambdas/link_fetcher/tests/docker-compose.yml +++ b/lambdas/link_fetcher/tests/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.9" services: postgres: image: postgres:10.12-alpine diff --git a/lambdas/link_fetcher/tests/test_common.py b/lambdas/link_fetcher/tests/test_common.py new file mode 100644 index 00000000..3fc57119 --- /dev/null +++ b/lambdas/link_fetcher/tests/test_common.py @@ -0,0 +1,103 @@ +import json +from typing import Callable, Sequence +from unittest.mock import patch + +from assertpy import assert_that +from db.models.granule import Granule +from sqlalchemy.orm import Session + +from app.common import ( + SearchResult, + add_search_result_to_sqs, + add_search_results_to_db_and_sqs, + get_accepted_tile_ids, +) + + +def test_that_link_fetcher_handler_correctly_loads_allowed_tiles(): + tile_ids = get_accepted_tile_ids() + assert_that(tile_ids).is_length(18952) + assert_that(tile_ids).contains("01FBE") + assert_that(tile_ids).contains("60WWV") + + +def test_that_link_fetcher_handler_correctly_adds_search_results_to_db( + db_session: Session, + search_result_maker: Callable[[int], Sequence[SearchResult]], + mock_sqs_queue, + db_session_context, +): + search_results = search_result_maker(10) + search_result_id_base = search_results[0].image_id[:-3] + search_result_url_base = search_results[0].download_url[:-3] + + with patch("app.common.add_search_result_to_sqs") as mock_add_to_sqs: + mock_add_to_sqs.return_value = None + add_search_results_to_db_and_sqs(lambda: db_session, search_results) + mock_add_to_sqs.assert_called() + + granules_in_db = db_session.query(Granule).all() + assert_that(granules_in_db).is_length(10) + + for idx, granule in enumerate(granules_in_db): + id_filled = str(idx).zfill(3) + expected_id = f"{search_result_id_base}{id_filled}" + expected_url = f"{search_result_url_base}{id_filled}" + granule_id = granule.id + granule_download_url = granule.download_url + assert_that(expected_id).is_equal_to(granule_id) + assert_that(expected_url).is_equal_to(granule_download_url) + + +def test_that_link_fetcher_handler_correctly_handles_duplicate_db_entry( + db_session: Session, + search_result_maker: Callable[[int], Sequence[SearchResult]], + mock_sqs_queue, +): + search_result = search_result_maker(1)[0] + db_session.add( + Granule( + id=search_result.image_id, + filename=search_result.filename, + tileid=search_result.tileid, + size=search_result.size, + beginposition=search_result.beginposition, + endposition=search_result.endposition, + ingestiondate=search_result.ingestiondate, + download_url=search_result.download_url, + ) # type: ignore + ) + + # Because of how the db sessions are handled in these unit tests, the rollback + # call actually undoes the insert that the test setup does, so we're just asserting + # that rollback is called, not that there is still one entry. + # That's the best we can do without a full e2e test. + with patch.object(db_session, "rollback") as rollback: + add_search_results_to_db_and_sqs(lambda: db_session, [search_result]) + rollback.assert_called_once() + + +def test_that_link_fetcher_handler_correctly_adds_search_result_to_queue( + mock_sqs_queue, + search_result_maker: Callable[[int], Sequence[SearchResult]], + sqs_client, +): + search_result = search_result_maker(1)[0] + search_result_id = search_result.image_id + search_result_url = search_result.download_url + search_result_filename = search_result.filename + + add_search_result_to_sqs(search_result, sqs_client, mock_sqs_queue.url) + + mock_sqs_queue.load() + + number_of_messages_in_queue = mock_sqs_queue.attributes[ + "ApproximateNumberOfMessages" + ] + assert_that(int(number_of_messages_in_queue)).is_equal_to(1) + + message = mock_sqs_queue.receive_messages(MaxNumberOfMessages=1)[0] + message_body = json.loads(message.body) + assert_that(search_result_id).is_equal_to(message_body["id"]) + assert_that(search_result_url).is_equal_to(message_body["download_url"]) + assert_that(search_result_filename).is_equal_to(message_body["filename"]) diff --git a/lambdas/link_fetcher/tests/test_link_fetcher_handler.py b/lambdas/link_fetcher/tests/test_link_fetcher_handler.py index 8d5af463..6840cf06 100644 --- a/lambdas/link_fetcher/tests/test_link_fetcher_handler.py +++ b/lambdas/link_fetcher/tests/test_link_fetcher_handler.py @@ -1,8 +1,5 @@ import dataclasses -import json from datetime import date, datetime, timezone -from typing import Callable, Sequence -from unittest.mock import patch import pytest import responses @@ -13,16 +10,15 @@ from freezegun import freeze_time from sqlalchemy.orm import Session -from handler import ( +from app.common import ( + SearchResult, +) +from app.search_handler import ( MIN_REMAINING_MILLIS, SEARCH_URL, - SearchResult, _handler, - add_search_result_to_sqs, - add_search_results_to_db_and_sqs, create_search_result, filter_search_results, - get_accepted_tile_ids, get_fetched_links, get_page_for_query_and_total_results, get_query_parameters, @@ -32,13 +28,6 @@ ) -def test_that_link_fetcher_handler_correctly_loads_allowed_tiles(): - tile_ids = get_accepted_tile_ids() - assert_that(tile_ids).is_length(18952) - assert_that(tile_ids).contains("01FBE") - assert_that(tile_ids).contains("60WWV") - - def test_that_link_fetcher_handler_generates_correct_query_parameters(): expected_query_parameters = { "processingLevel": "S2MSI1C", @@ -256,88 +245,6 @@ def test_that_link_fetcher_handler_correctly_filters_search_results(accepted_til assert actual_results == expected_results -def test_that_link_fetcher_handler_correctly_adds_search_results_to_db( - db_session: Session, - search_result_maker: Callable[[int], Sequence[SearchResult]], - mock_sqs_queue, - db_session_context, -): - search_results = search_result_maker(10) - search_result_id_base = search_results[0].image_id[:-3] - search_result_url_base = search_results[0].download_url[:-3] - - with patch("handler.add_search_result_to_sqs") as mock_add_to_sqs: - mock_add_to_sqs.return_value = None - add_search_results_to_db_and_sqs(lambda: db_session, search_results) - mock_add_to_sqs.assert_called() - - granules_in_db = db_session.query(Granule).all() - assert_that(granules_in_db).is_length(10) - - for idx, granule in enumerate(granules_in_db): - id_filled = str(idx).zfill(3) - expected_id = f"{search_result_id_base}{id_filled}" - expected_url = f"{search_result_url_base}{id_filled}" - granule_id = granule.id - granule_download_url = granule.download_url - assert_that(expected_id).is_equal_to(granule_id) - assert_that(expected_url).is_equal_to(granule_download_url) - - -def test_that_link_fetcher_handler_correctly_handles_duplicate_db_entry( - db_session: Session, - search_result_maker: Callable[[int], Sequence[SearchResult]], - mock_sqs_queue, -): - search_result = search_result_maker(1)[0] - db_session.add( - Granule( - id=search_result.image_id, - filename=search_result.filename, - tileid=search_result.tileid, - size=search_result.size, - beginposition=search_result.beginposition, - endposition=search_result.endposition, - ingestiondate=search_result.ingestiondate, - download_url=search_result.download_url, - ) # type: ignore - ) - - # Because of how the db sessions are handled in these unit tests, the rollback - # call actually undoes the insert that the test setup does, so we're just asserting - # that rollback is called, not that there is still one entry. - # That's the best we can do without a full e2e test. - with patch.object(db_session, "rollback") as rollback: - add_search_results_to_db_and_sqs(lambda: db_session, [search_result]) - rollback.assert_called_once() - - -def test_that_link_fetcher_handler_correctly_adds_search_result_to_queue( - mock_sqs_queue, - search_result_maker: Callable[[int], Sequence[SearchResult]], - sqs_client, -): - search_result = search_result_maker(1)[0] - search_result_id = search_result.image_id - search_result_url = search_result.download_url - search_result_filename = search_result.filename - - add_search_result_to_sqs(search_result, sqs_client, mock_sqs_queue.url) - - mock_sqs_queue.load() - - number_of_messages_in_queue = mock_sqs_queue.attributes[ - "ApproximateNumberOfMessages" - ] - assert_that(int(number_of_messages_in_queue)).is_equal_to(1) - - message = mock_sqs_queue.receive_messages(MaxNumberOfMessages=1)[0] - message_body = json.loads(message.body) - assert_that(search_result_id).is_equal_to(message_body["id"]) - assert_that(search_result_url).is_equal_to(message_body["download_url"]) - assert_that(search_result_filename).is_equal_to(message_body["filename"]) - - def test_that_link_fetcher_handler_correctly_retrieves_fetched_links_if_in_db( db_session: Session, ): diff --git a/lambdas/link_fetcher/tests/test_subscription_endpoint.py b/lambdas/link_fetcher/tests/test_subscription_endpoint.py new file mode 100644 index 00000000..47de0029 --- /dev/null +++ b/lambdas/link_fetcher/tests/test_subscription_endpoint.py @@ -0,0 +1,301 @@ +import json +from collections.abc import Iterator +from datetime import datetime, timezone +from pathlib import Path +from typing import Callable +from unittest.mock import Mock, patch + +import boto3 +import httpx +import pytest +from db.models.granule import Granule +from fastapi import FastAPI +from moto import mock_aws +from sqlalchemy.orm import Session +from starlette.testclient import TestClient + +import app.subscription_endpoint +from app.common import SearchResult +from app.subscription_endpoint import ( + EndpointConfig, + build_app, + parse_search_result, + process_notification, +) + + +class TestEndpointConfig: + """Test EndpointConfig""" + + def test_basic_init_ennvar(self, monkeypatch): + monkeypatch.setenv("STAGE", "local") + monkeypatch.setenv("NOTIFICATION_USERNAME", "bar") + monkeypatch.setenv("NOTIFICATION_PASSWORD", "baz") + config = EndpointConfig() + assert config.stage == "local" + assert config.notification_username == "bar" + assert config.notification_password == "baz" + + @pytest.fixture + def endpoint_config_secret(self) -> Iterator[EndpointConfig]: + config = EndpointConfig( + stage="local", + notification_username="bar", + notification_password="baz", + ) + + with mock_aws(): + secrets_manager_client = boto3.client("secretsmanager") + secrets_manager_client.create_secret( + Name=f"hls-s2-downloader-serverless/{config.stage}/esa-subscription-credentials", + SecretString=json.dumps( + { + "notification_username": config.notification_username, + "notification_password": config.notification_password, + } + ), + ) + yield config + + def test_local_from_ssm(self, endpoint_config_secret: EndpointConfig): + config = EndpointConfig.load_from_secrets_manager(endpoint_config_secret.stage) + assert config == endpoint_config_secret + + +@pytest.fixture +def event_s2_created() -> dict: + """Load Sentinel-2 "Created" event from ESA's push subscription + + This message contains two types of fields, + * Message metadata (event type, subscription ID, ack ID, notification date, etc) + * Message "body" - `(.value)` + """ + data = Path(__file__).parent / "data" / "push-granule-created-s2-n1.json" + with data.open() as src: + return json.load(src) + + +class TestSearchResultParsing: + """Tests for parsing subscription into a SearchResult""" + + def test_parses_created_event(self, event_s2_created: dict): + """Test happy path of parsing event to SearchResult""" + search_result = parse_search_result(event_s2_created["value"]) + assert isinstance(search_result, SearchResult) + + def test_raises_if_no_extracted_data(self, event_s2_created: dict): + """Test we catch if there's no "extracted" data""" + # this should never happen as newly published data because it'll be "Online" + payload = event_s2_created["value"] + payload["Locations"][0]["FormatType"] = "Archived" + + with pytest.raises( + ValueError, match=r"Got 0 'Extracted' links, expected just 1" + ): + parse_search_result(payload) + + def test_raises_if_multiple_extracted_data(self, event_s2_created: dict): + """Test we catch if there's >1 "extracted" data""" + # this also shouldn't happen, but if it does we want to fail because + # it won't be clear which to download + payload = event_s2_created["value"] + payload["Locations"].append(payload["Locations"][0].copy()) + + with pytest.raises( + ValueError, match=r"Got 2 'Extracted' links, expected just 1" + ): + parse_search_result(payload) + + +class TestProcessNotification: + """Test parsing and filtering of granule created notification""" + + def test_processes_notification( + self, + mock_sqs_queue, + db_session: Session, + event_s2_created: dict, + accepted_tile_ids: set[str], + ): + """Test that a recent S2 granule created event is added to queue""" + process_notification( + event_s2_created, + accepted_tile_ids, + lambda: db_session, + # provide a fake datetime based on publication date to ensure the granule notification + # is recent enough to process + now_utc=lambda: datetime.fromisoformat( + event_s2_created["value"]["PublicationDate"] + ), + ) + + assert len(db_session.query(Granule).all()) == 1 + + mock_sqs_queue.load() + number_of_messages_in_queue = mock_sqs_queue.attributes[ + "ApproximateNumberOfMessages" + ] + assert int(number_of_messages_in_queue) == 1 + + def test_filters_old_imagery( + self, + mock_sqs_queue, + db_session: Session, + event_s2_created: dict, + accepted_tile_ids: set[str], + ): + """Test we filter old imagery and do NOT add to queue or DB""" + event_s2_created["value"]["ContentDate"]["Start"] = "1999-12-31T23:59:59.999Z" + with patch( + "app.subscription_endpoint.add_search_results_to_db_and_sqs" + ) as mock_add_to_db_and_sqs: + process_notification( + event_s2_created, + accepted_tile_ids, + lambda: db_session, + ) + mock_add_to_db_and_sqs.assert_not_called() + + def test_filters_unaccepted_tile_id( + self, + mock_sqs_queue, + db_session: Session, + event_s2_created: dict, + mocker, + ): + """Test we filter unacceptable tile IDs and do NOT add to queue or DB""" + spy_filter_search_results = mocker.spy( + app.subscription_endpoint, "filter_search_results" + ) + with patch( + "app.common.add_search_results_to_db_and_sqs" + ) as mock_add_to_db_and_sqs: + process_notification( + event_s2_created, + {"none"}, + lambda: db_session, + # ensure we granule is recent enough <30 days + now_utc=lambda: datetime.fromisoformat( + event_s2_created["value"]["PublicationDate"] + ), + ) + spy_filter_search_results.assert_called_once() + mock_add_to_db_and_sqs.assert_not_called() + + +class TestApp: + """Test API endpoint""" + + @pytest.fixture() + def config(self) -> EndpointConfig: + return EndpointConfig( + stage="local", + notification_username="test_user", + notification_password="password", + ) + + @pytest.fixture + def now_utc(self, request) -> Callable[[], datetime]: + if callable(getattr(request, "param", None)): + return request.param + return lambda: datetime.now(tz=timezone.utc) + + @pytest.fixture + def test_client( + self, + config: EndpointConfig, + db_connection_secret, + mock_sqs_queue, + now_utc, + ) -> FastAPI: + self.endpoint_config = config + self.db_connection_secret = db_connection_secret + self.mock_sqs_queue = mock_sqs_queue + app = build_app(config, now_utc) + return TestClient(app) + + def test_handles_new_created_event( + self, test_client: TestClient, event_s2_created: dict + ): + """Test happy path for handling subscription event, mocking processing function""" + with patch( + "app.subscription_endpoint.process_notification", Mock() + ) as mock_process_notification: + resp = test_client.post( + "/events", + json=event_s2_created, + auth=( + self.endpoint_config.notification_username, + self.endpoint_config.notification_password, + ), + ) + # processed successfully but no content + resp.raise_for_status() + assert resp.status_code == 204 + mock_process_notification.assert_called_once() + + @pytest.mark.parametrize( + "now_utc", + [lambda: datetime.fromisoformat("2024-09-12T14:52:06.118Z")], + indirect=True, + ) + def test_handles_new_created_event_is_added( + self, + test_client: TestClient, + db_session: Session, + event_s2_created: dict, + now_utc: Callable[[], datetime], + ): + """Test happy path for handling subscription event, mocking DB and SQS + + We ensure the new event is "recent" enough to accept by redefining the + `now_utc` that our application is provided to match the publication + date of the test data. + """ + resp = test_client.post( + "/events", + json=event_s2_created, + auth=( + self.endpoint_config.notification_username, + self.endpoint_config.notification_password, + ), + ) + + # processed successfully but no content + resp.raise_for_status() + assert resp.status_code == 204 + + # Check we have a message in moto's SQS queue + self.mock_sqs_queue.load() + number_of_messages_in_queue = int( + self.mock_sqs_queue.attributes["ApproximateNumberOfMessages"] + ) + assert number_of_messages_in_queue == 1 + + def test_handles_wrong_user(self, test_client: TestClient, event_s2_created: dict): + """Test happy path for handling subscription event""" + resp = test_client.post( + "/events", + json=event_s2_created, + auth=( + "wrong", + self.endpoint_config.notification_password, + ), + ) + with pytest.raises(httpx.HTTPStatusError) as err: + resp.raise_for_status() + assert err.value.response.status_code == 401 # unauthorized + + def test_handles_wrong_pass(self, test_client: TestClient, event_s2_created: dict): + """Test happy path for handling subscription event""" + resp = test_client.post( + "/events", + json=event_s2_created, + auth=( + self.endpoint_config.notification_username, + "wrong", + ), + ) + with pytest.raises(httpx.HTTPStatusError) as err: + resp.raise_for_status() + assert err.value.response.status_code == 401 # unauthorized diff --git a/lambdas/requeuer/Pipfile b/lambdas/requeuer/Pipfile index 1ba25f6b..839259e2 100644 --- a/lambdas/requeuer/Pipfile +++ b/lambdas/requeuer/Pipfile @@ -13,7 +13,7 @@ boto3-stubs = {extras = ["lambda", "sqs", "secretsmanager"], version = "*"} db = {editable = true, path = "./../../layers/db"} moto = "==5.0.17" mypy = "*" -psycopg2 = "==2.9.10" +psycopg2-binary = "==2.9.10" pytest = "*" pytest-cov = "*" pytest-docker = "*" diff --git a/lambdas/requeuer/Pipfile.lock b/lambdas/requeuer/Pipfile.lock index 26e699c6..bc59d525 100644 --- a/lambdas/requeuer/Pipfile.lock +++ b/lambdas/requeuer/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8a91f65b2cbab84cd0afb3d6cf4603e42c2448136145b065532a7b5fe0f7ea73" + "sha256": "21b33754b2e3a192f4969255b63939699d54534ad359d6daa8eacdfe5d5e0e96" }, "pipfile-spec": 6, "requires": { @@ -27,11 +27,11 @@ }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "iso8601": { "hashes": [ @@ -60,19 +60,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "urllib3": { "hashes": [ @@ -86,20 +86,20 @@ "develop": { "alembic": { "hashes": [ - "sha256:203503117415561e203aa14541740643a611f641517f0209fcae63e9fa09f1a2", - "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e" + "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25", + "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.13.3" + "version": "==1.14.0" }, "attrs": { "hashes": [ - "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", - "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" ], - "markers": "python_version >= '3.7'", - "version": "==24.2.0" + "markers": "python_version >= '3.8'", + "version": "==24.3.0" }, "boto3": { "hashes": [ @@ -117,35 +117,35 @@ "sqs" ], "hashes": [ - "sha256:5b2887214d0953e7e6d5aeb4276e3b9e3cc3fb7375ef39a9da8aaa0c3e48b223", - "sha256:a5bddda9eaa277b615d9e394f42f51d97e6652166db6810f645cc52a4f4efccb" + "sha256:d690a7286e7bb680ddbd0493f7f4119386f4f4389401cac227b8087b19f688cf", + "sha256:e9b82779553ae5ec3b35d1a6f245b148f6c5313152b9e591f542ff915fb20bfe" ], "markers": "python_version >= '3.8'", - "version": "==1.35.46" + "version": "==1.35.81" }, "botocore": { "hashes": [ - "sha256:05f4493119a96799ff84d43e78691efac3177e1aec8840cca99511de940e342a", - "sha256:f8f703463d3cd8b6abe2bedc443a7ab29f0e2ff1588a2e83164b108748645547" + "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", + "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "botocore-stubs": { "hashes": [ - "sha256:397f78e5c9ed951fbb576cc0c544e29b626bd7a3742d463c015c0cc71e1d292c", - "sha256:f1ef0cd7c263f02f5ad3583f3ab99106e519b0d04d94f84c64c6afe146f83dff" + "sha256:818e1ec37d6a97e1aa7955f85a45c6b5d7e4d7599b00658529e0f070330791d7", + "sha256:d3759683a7834053d074d75fab12fb09fdb6ba73e491db37a3ff97ef3dff5d12" ], "markers": "python_version >= '3.8'", - "version": "==1.35.47" + "version": "==1.35.81" }, "certifi": { "hashes": [ - "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", - "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" + "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", + "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" ], "markers": "python_version >= '3.6'", - "version": "==2024.8.30" + "version": "==2024.12.14" }, "cffi": { "hashes": [ @@ -336,104 +336,104 @@ "toml" ], "hashes": [ - "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", - "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", - "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", - "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", - "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", - "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", - "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", - "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", - "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", - "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", - "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", - "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", - "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", - "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", - "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", - "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", - "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", - "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", - "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", - "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", - "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", - "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", - "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", - "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", - "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", - "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", - "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", - "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", - "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", - "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", - "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", - "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", - "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", - "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", - "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", - "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", - "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", - "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", - "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", - "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", - "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", - "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", - "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", - "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", - "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", - "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", - "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", - "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", - "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", - "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", - "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", - "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", - "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", - "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", - "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", - "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", - "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", - "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", - "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", - "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", - "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", - "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "markers": "python_version >= '3.9'", - "version": "==7.6.4" + "version": "==7.6.9" }, "cryptography": { "hashes": [ - "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", - "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", - "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", - "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", - "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", - "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", - "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", - "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", - "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", - "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", - "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", - "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", - "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", - "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", - "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", - "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", - "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", - "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", - "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", - "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", - "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", - "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", - "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", - "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", - "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", - "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", - "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7", + "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731", + "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", + "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", + "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", + "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", + "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", + "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", + "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", + "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", + "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", + "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", + "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", + "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", + "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", + "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092", + "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa", + "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", + "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02", + "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", + "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", + "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", + "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", + "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", + "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", + "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756", + "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4" ], - "markers": "python_version >= '3.7'", - "version": "==43.0.3" + "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==44.0.0" }, "db": { "editable": true, @@ -552,11 +552,11 @@ }, "mako": { "hashes": [ - "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d", - "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a" + "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627", + "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8" ], "markers": "python_version >= '3.8'", - "version": "==1.3.6" + "version": "==1.3.8" }, "markupsafe": { "hashes": [ @@ -675,10 +675,10 @@ }, "mypy-boto3-lambda": { "hashes": [ - "sha256:8473d71ee83aca8009d317e57cd2094a355ec90c7c536cf26e52db71b2f7528b", - "sha256:e42d9ce7e6a32841e4a6a2980f5f8634e2b0a35698e71d302a78e4d0de4223c6" + "sha256:00499898236fe423c9292f77644102d4bd6699b3c16b8c4062eb759c022447f5", + "sha256:577a9465ac63ac564efc2755a7e72c28a9d2f496747c1faf242cb13d5017b262" ], - "version": "==1.35.28" + "version": "==1.35.68" }, "mypy-boto3-secretsmanager": { "hashes": [ @@ -704,11 +704,11 @@ }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pluggy": { "hashes": [ @@ -718,17 +718,75 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, - "psycopg2": { + "psycopg2-binary": { "hashes": [ - "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4", - "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", - "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2", - "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e", - "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716", - "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067", - "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442", - "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b", - "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a" + "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", + "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", + "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", + "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", + "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", + "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", + "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", + "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", + "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", + "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", + "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", + "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", + "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", + "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", + "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", + "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", + "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", + "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", + "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", + "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", + "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", + "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", + "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", + "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", + "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", + "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", + "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", + "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", + "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", + "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", + "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", + "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", + "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", + "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", + "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", + "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", + "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", + "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", + "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", + "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", + "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", + "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", + "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", + "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", + "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", + "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", + "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", + "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", + "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", + "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", + "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", + "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", + "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", + "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", + "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", + "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", + "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", + "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", + "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", + "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", + "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", + "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", + "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", + "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", + "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", + "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", + "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" ], "index": "pypi", "markers": "python_version >= '3.8'", @@ -744,21 +802,21 @@ }, "pytest": { "hashes": [ - "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", - "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" + "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", + "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.3.3" + "version": "==8.3.4" }, "pytest-cov": { "hashes": [ - "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", - "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857" + "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", + "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.0.0" + "markers": "python_version >= '3.9'", + "version": "==6.0.0" }, "pytest-docker": { "hashes": [ @@ -879,19 +937,19 @@ }, "s3transfer": { "hashes": [ - "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", - "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" + "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e", + "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -935,26 +993,26 @@ }, "types-awscrt": { "hashes": [ - "sha256:3fd1edeac923d1956c0e907c973fb83bda465beae7f054716b371b293f9b5fdc", - "sha256:517d9d06f19cf58d778ca90ad01e52e0489466bf70dcf78c7f47f74fdf151a60" + "sha256:b1b9bb10f337e3fe8f5f508860eb354d9fe093f02e1485955a9e0bdd4e250074", + "sha256:eeb4bd596100927704c8b9f964ec8a246be4943d546f3fd2a8efdddebea422ea" ], "markers": "python_version >= '3.8'", - "version": "==0.23.0" + "version": "==0.23.4" }, "types-s3transfer": { "hashes": [ - "sha256:d34c5a82f531af95bb550927136ff5b737a1ed3087f90a59d545591dfde5b4cc", - "sha256:f761b2876ac4c208e6c6b75cdf5f6939009768be9950c545b11b0225e7703ee7" + "sha256:03123477e3064c81efe712bf9d372c7c72f2790711431f9baa59cf96ea607267", + "sha256:22ac1aabc98f9d7f2928eb3fb4d5c02bf7435687f0913345a97dd3b84d0c217d" ], "markers": "python_version >= '3.8'", - "version": "==0.10.3" + "version": "==0.10.4" }, "typing-extensions": { "hashes": [ "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], - "markers": "python_version >= '3.8'", + "markers": "python_version < '3.12'", "version": "==4.12.2" }, "urllib3": { @@ -967,11 +1025,11 @@ }, "werkzeug": { "hashes": [ - "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c", - "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.8'", - "version": "==3.0.4" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "xmltodict": { "hashes": [ diff --git a/setup.py b/setup.py index 3c3249ab..811aa5f9 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ f"aws-cdk.aws-lambda-python-alpha=={aws_cdk_version}a0", "boto3", "polling2", - "psycopg2", + "psycopg2-binary==2.9.10", "python-dotenv", ] @@ -27,6 +27,7 @@ "mypy", "pytest", "pytest-cov", + "requests", ], "dev": [ "assertpy",