diff --git a/.isort.cfg b/.isort.cfg index 9aaf0e2d..ec7e285a 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -2,4 +2,4 @@ line_length = 88 multi_line_output = 3 include_trailing_comma = True -known_third_party = _pytest,aenum,affine,alembic,asgi_lifespan,async_lru,asyncpg,aws_utils,boto3,botocore,click,docker,ee,errors,fastapi,fiona,gdal_utils,geoalchemy2,geojson,gfw_pixetl,gino,gino_starlette,google,httpx,httpx_auth,logger,logging_utils,moto,numpy,orjson,osgeo,pandas,pendulum,pglast,psutil,psycopg2,pydantic,pyproj,pytest,pytest_asyncio,rasterio,shapely,sqlalchemy,sqlalchemy_utils,starlette,tileputty,tiles_geojson,typer +known_third_party = _pytest,aenum,affine,alembic,asgi_lifespan,async_lru,asyncpg,aws_utils,boto3,botocore,click,docker,ee,errors,fastapi,fiona,gdal_utils,geoalchemy2,geojson,gfw_pixetl,gino,gino_starlette,google,httpx,httpx_auth,logger,logging_utils,moto,numpy,orjson,osgeo,pandas,pendulum,pglast,psutil,psycopg2,pydantic,pyproj,pytest,pytest_asyncio,rasterio,shapely,sqlalchemy,sqlalchemy_utils,starlette,tileputty,tiles_geojson,typer,unidecode diff --git a/Pipfile b/Pipfile index 00392474..3eba9c38 100644 --- a/Pipfile +++ b/Pipfile @@ -45,7 +45,7 @@ google-cloud-storage = "*" httpcore = "*" httpx = "*" httpx-auth = "*" -numpy = "*" +numpy = "<2.0" orjson = "*" packaging = "*" pendulum = "<3" @@ -60,6 +60,7 @@ sqlalchemy = "<1.4" sqlalchemy-utils = "*" starlette = "*" typer = "*" +unidecode = "*" uvicorn = {version = "*", extras = ["standard"]} [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 8d2a7d8f..159a797d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "68ff4c74300e1fc2072858819f9be86db5220795fbf9032557bf4a4726d5c437" + "sha256": "70a3c575de695cae1490336a2cf51d3a67452e4ad6d142a8073216eab53fbbb4" }, "pipfile-spec": 6, "requires": { @@ -242,21 +242,21 @@ }, "boto3": { "hashes": [ - "sha256:b0874233057995a8f0c813f5b45a36c09630e74c43d7a7c64db2feef2915d493", - "sha256:dc56caaaab2157a4bfc109c88b50cd032f3ac66c06d17f8ee335b798eaf53e5c" + "sha256:43c6a7a70bb226770a82a601870136e3bb3bf2808f4576ab5b9d7d140dbf1323", + "sha256:7bc9b27ad87607256470c70a86c8b8c319ddd6ecae89cc191687cbf8ccb7b6a6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.90" + "version": "==1.35.88" }, "botocore": { "hashes": [ - "sha256:51dcbe1b32e2ac43dac17091f401a00ce5939f76afe999081802009cce1e92e4", - "sha256:f007f58e8e3c1ad0412a6ddfae40ed92a7bca571c068cb959902bcf107f2ae48" + "sha256:58dcd9a464c354b8c6c25261d8de830d175d9739eae568bf0c52e57116fb03c6", + "sha256:e60cc3fbe8d7a10f70e7e852d76be2b29f23ead418a5899d366ea32b1eacb5a5" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.90" + "version": "==1.35.88" }, "cachetools": { "hashes": [ @@ -1036,147 +1036,128 @@ }, "numpy": { "hashes": [ - "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2", - "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5", - "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60", - "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71", - "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631", - "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8", - "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2", - "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16", - "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa", - "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591", - "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964", - "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821", - "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484", - "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957", - "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800", - "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918", - "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95", - "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0", - "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e", - "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d", - "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73", - "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59", - "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51", - "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355", - "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348", - "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e", - "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440", - "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675", - "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84", - "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046", - "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab", - "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712", - "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308", - "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315", - "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3", - "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008", - "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5", - "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2", - "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e", - "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7", - "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf", - "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab", - "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd", - "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf", - "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8", - "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb", - "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268", - "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d", - "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780", - "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716", - "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e", - "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528", - "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af", - "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7", - "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51" + "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", + "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", + "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", + "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", + "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", + "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", + "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", + "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", + "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", + "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", + "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", + "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", + "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", + "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", + "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", + "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", + "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", + "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", + "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", + "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", + "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", + "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", + "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", + "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", + "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", + "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", + "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", + "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", + "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", + "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", + "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", + "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", + "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", + "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", + "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", + "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" ], "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==2.2.1" + "markers": "python_version >= '3.9'", + "version": "==1.26.4" }, "orjson": { "hashes": [ - "sha256:003721c72930dbb973f25c5d8e68d0f023d6ed138b14830cc94e57c6805a2eab", - "sha256:0065896f85d9497990731dfd4a9991a45b0a524baec42ef0a63c34630ee26fd6", - "sha256:03b0f29d485411e3c13d79604b740b14e4e5fb58811743f6f4f9693ee6480a8f", - "sha256:064b9dbb0217fd64a8d016a8929f2fae6f3312d55ab3036b00b1d17399ab2f3e", - "sha256:0bc858086088b39dc622bc8219e73d3f246fb2bce70a6104abd04b3a080a66a8", - "sha256:0dbf3b97e52e093d7c3e93eb5eb5b31dc7535b33c2ad56872c83f0160f943487", - "sha256:0e2759d3172300b2f892dee85500b22fca5ac49e0c42cfff101aaf9c12ac9617", - "sha256:0fee076134398d4e6cb827002468679ad402b22269510cf228301b787fdff5ae", - "sha256:1232c5e873a4d1638ef957c5564b4b0d6f2a6ab9e207a9b3de9de05a09d1d920", - "sha256:1884e53c6818686891cc6fc5a3a2540f2f35e8c76eac8dc3b40480fb59660b00", - "sha256:1a7d64f1db5ecbc21eb83097e5236d6ab7e86092c1cd4c216c02533332951afc", - "sha256:2149e2fcd084c3fd584881c7f9d7f9e5ad1e2e006609d8b80649655e0d52cd02", - "sha256:22f1c9a30b43d14a041a6ea190d9eca8a6b80c4beb0e8b67602c82d30d6eec3e", - "sha256:233aae4474078d82f425134bb6a10fb2b3fc5a1a1b3420c6463ddd1b6a97eda8", - "sha256:2cdaf8b028a976ebab837a2c27b82810f7fc76ed9fb243755ba650cc83d07730", - "sha256:33ef84f7e9513fb13b3999c2a64b9ca9c8143f3da9722fbf9c9ce51ce0d8076e", - "sha256:3723e137772639af8adb68230f2aa4bcb27c48b3335b1b1e2d49328fed5e244c", - "sha256:3a7df63076435f39ec024bdfeb4c9767ebe7b49abc4949068d61cf4857fa6d6c", - "sha256:3ca6f17467ebbd763f8862f1d89384a5051b461bb0e41074f583a0ebd7120e8e", - "sha256:4222881d0aab76224d7b003a8e5fdae4082e32c86768e0e8652de8afd6c4e2c1", - "sha256:46c249b4e934453be4ff2e518cd1adcd90467da7391c7a79eaf2fbb79c51e8c7", - "sha256:48a946796e390cbb803e069472de37f192b7a80f4ac82e16d6eb9909d9e39d56", - "sha256:4a11532cbfc2f5752c37e84863ef8435b68b0e6d459b329933294f65fa4bda1a", - "sha256:527afb6ddb0fa3fe02f5d9fba4920d9d95da58917826a9be93e0242da8abe94a", - "sha256:5385935a73adce85cc7faac9d396683fd813566d3857fa95a0b521ef84a5b588", - "sha256:54433e421618cd5873e51c0e9d0b9fb35f7bf76eb31c8eab20b3595bb713cd3d", - "sha256:5f00c7fb18843bad2ac42dc1ce6dd214a083c53f1e324a0fd1c8137c6436269b", - "sha256:5f74d878d1efb97a930b8a9f9898890067707d683eb5c7e20730030ecb3fb930", - "sha256:6066729cf9552d70de297b56556d14b4f49c8f638803ee3c90fd212fa43cc6af", - "sha256:62c3cc00c7e776c71c6b7b9c48c5d2701d4c04e7d1d7cdee3572998ee6dc57cc", - "sha256:63664bf12addb318dc8f032160e0f5dc17eb8471c93601e8f5e0d07f95003784", - "sha256:69b21d91c5c5ef8a201036d207b1adf3aa596b930b6ca3c71484dd11386cf6c3", - "sha256:6a428afb5720f12892f64920acd2eeb4d996595bf168a26dd9190115dbf1130d", - "sha256:711878da48f89df194edd2ba603ad42e7afed74abcd2bac164685e7ec15f96de", - "sha256:7184f608ad563032e398f311910bc536e62b9fbdca2041be889afcbc39500de8", - "sha256:8257c3fb8dd7b0b446b5e87bf85a28e4071ac50f8c04b6ce2d38cb4abd7dff57", - "sha256:89367767ed27b33c25c026696507c76e3d01958406f51d3a2239fe9e91959df2", - "sha256:8a1152e2761025c5d13b5e1908d4b1c57f3797ba662e485ae6f26e4e0c466388", - "sha256:92b4ec30d6025a9dcdfe0df77063cbce238c08d0404471ed7a79f309364a3d19", - "sha256:9c976bad3996aa027cd3aef78aa57873f3c959b6c38719de9724b71bdc7bd14b", - "sha256:a3614b00621c77f3f6487792238f9ed1dd8a42f2ec0e6540ee34c2d4e6db813a", - "sha256:a36c0d48d2f084c800763473020a12976996f1109e2fcb66cfea442fdf88047f", - "sha256:a42b9fe4b0114b51eb5cdf9887d8c94447bc59df6dbb9c5884434eab947888d8", - "sha256:a5a7624ab4d121c7e035708c8dd1f99c15ff155b69a1c0affc4d9d8b551281ba", - "sha256:a94542d12271c30044dadad1125ee060e7a2048b6c7034e432e116077e1d13d2", - "sha256:a9ecea472f3eb653e1c0a3d68085f031f18fc501ea392b98dcca3e87c24f9ebe", - "sha256:aa6fe68f0981fba0d4bf9cdc666d297a7cdba0f1b380dcd075a9a3dd5649a69e", - "sha256:ae537fcf330b3947e82c6ae4271e092e6cf16b9bc2cef68b14ffd0df1fa8832a", - "sha256:b12a63f48bb53dba8453d36ca2661f2330126d54e26c1661e550b32864b28ce3", - "sha256:b42f56821c29e697c68d7d421410d7c1d8f064ae288b525af6a50cf99a4b1200", - "sha256:b5f7c298d4b935b222f52d6c7f2ba5eafb59d690d9a3840b7b5c5cda97f6ec5c", - "sha256:ba5b13b8739ce5b630c65cb1c85aedbd257bcc2b9c256b06ab2605209af75a2e", - "sha256:c0044b0b8c85a565e7c3ce0a72acc5d35cda60793edf871ed94711e712cb637d", - "sha256:c96d2fb80467d1d0dfc4d037b4e1c0f84f1fe6229aa7fea3f070083acef7f3d7", - "sha256:cab83e67f6aabda1b45882254b2598b48b80ecc112968fc6483fa6dae609e9f0", - "sha256:cf16f06cb77ce8baf844bc222dbcb03838f61d0abda2c3341400c2b7604e436e", - "sha256:d26a0eca3035619fa366cbaf49af704c7cb1d4a0e6c79eced9f6a3f2437964b6", - "sha256:d36f689e7e1b9b6fb39dbdebc16a6f07cbe994d3644fb1c22953020fc575935f", - "sha256:d4b6acd7c9c829895e50d385a357d4b8c3fafc19c5989da2bae11783b0fd4977", - "sha256:d9c3a87abe6f849a4a7ac8a8a1dede6320a4303d5304006b90da7a3cd2b70d2c", - "sha256:dbcd7aad6bcff258f6896abfbc177d54d9b18149c4c561114f47ebfe74ae6bfd", - "sha256:dc03db4922e75bbc870b03fc49734cefbd50fe975e0878327d200022210b82d8", - "sha256:dca1d20f1af0daff511f6e26a27354a424f0b5cf00e04280279316df0f604a6f", - "sha256:dce1cc42ed75b585c0c4dc5eb53a90a34ccb493c09a10750d1a1f9b9eff2bd12", - "sha256:dd2bcde107221bb9c2fa0c4aaba735a537225104173d7e19cf73f70b3126c993", - "sha256:dda4ba4d3e6f6c53b6b9c35266788053b61656a716a7fef5c884629c2a52e7aa", - "sha256:e1ba0c5857dd743438acecc1cd0e1adf83f0a81fee558e32b2b36f89e40cee8b", - "sha256:e384e330a67cf52b3597ee2646de63407da6f8fc9e9beec3eaaaef5514c7a1c9", - "sha256:e400436950ba42110a20c50c80dff4946c8e3ec09abc1c9cf5473467e83fd1c5", - "sha256:e49333d1038bc03a25fdfe11c86360df9b890354bfe04215f1f54d030f33c342", - "sha256:e4f998bbf300690be881772ee9c5281eb9c0044e295bcd4722504f5b5c6092ff", - "sha256:eb9bfb14ab8f68d9d9492d4817ae497788a15fd7da72e14dfabc289c3bb088ec", - "sha256:ee948c6c01f6b337589c88f8e0bb11e78d32a15848b8b53d3f3b6fea48842c12", - "sha256:f47c9e7d224b86ffb086059cdcf634f4b3f32480f9838864aa09022fe2617ce2", - "sha256:f81b26c03f5fb5f0d0ee48d83cea4d7bc5e67e420d209cc1a990f5d1c62f9be0" + "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2", + "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd", + "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79", + "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff", + "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd", + "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8", + "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c", + "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b", + "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885", + "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825", + "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9", + "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559", + "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae", + "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708", + "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566", + "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4", + "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833", + "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e", + "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60", + "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef", + "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3", + "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543", + "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d", + "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e", + "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da", + "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da", + "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e", + "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296", + "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56", + "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c", + "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6", + "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755", + "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80", + "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23", + "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07", + "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2", + "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763", + "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935", + "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85", + "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b", + "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509", + "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13", + "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed", + "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc", + "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8", + "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69", + "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9", + "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192", + "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c", + "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31", + "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f", + "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74", + "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f", + "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252", + "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f", + "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e", + "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252", + "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548", + "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05", + "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b", + "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7", + "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36", + "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967", + "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d", + "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70", + "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced", + "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d", + "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6", + "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be", + "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc", + "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4", + "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb", + "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e", + "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69", + "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==3.10.13" + "version": "==3.10.12" }, "packaging": { "hashes": [ @@ -1789,6 +1770,15 @@ "markers": "python_version >= '3.8'", "version": "==4.12.2" }, + "unidecode": { + "hashes": [ + "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4", + "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39" + ], + "index": "pypi", + "markers": "python_version >= '3.5'", + "version": "==1.3.8" + }, "urllib3": { "hashes": [ "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", @@ -2149,21 +2139,21 @@ }, "boto3": { "hashes": [ - "sha256:b0874233057995a8f0c813f5b45a36c09630e74c43d7a7c64db2feef2915d493", - "sha256:dc56caaaab2157a4bfc109c88b50cd032f3ac66c06d17f8ee335b798eaf53e5c" + "sha256:43c6a7a70bb226770a82a601870136e3bb3bf2808f4576ab5b9d7d140dbf1323", + "sha256:7bc9b27ad87607256470c70a86c8b8c319ddd6ecae89cc191687cbf8ccb7b6a6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.90" + "version": "==1.35.88" }, "botocore": { "hashes": [ - "sha256:51dcbe1b32e2ac43dac17091f401a00ce5939f76afe999081802009cce1e92e4", - "sha256:f007f58e8e3c1ad0412a6ddfae40ed92a7bca571c068cb959902bcf107f2ae48" + "sha256:58dcd9a464c354b8c6c25261d8de830d175d9739eae568bf0c52e57116fb03c6", + "sha256:e60cc3fbe8d7a10f70e7e852d76be2b29f23ead418a5899d366ea32b1eacb5a5" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.90" + "version": "==1.35.88" }, "certifi": { "hashes": [ @@ -2551,11 +2541,11 @@ }, "identify": { "hashes": [ - "sha256:285a7d27e397652e8cafe537a6cc97dd470a970f48fb2e9d979aa38eae5513ac", - "sha256:993b0f01b97e0568c179bb9196391ff391bfb88a99099dbf5ce392b68f42d0af" + "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", + "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd" ], "markers": "python_version >= '3.9'", - "version": "==2.6.4" + "version": "==2.6.3" }, "idna": { "hashes": [ @@ -2764,65 +2754,46 @@ }, "numpy": { "hashes": [ - "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2", - "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5", - "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60", - "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71", - "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631", - "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8", - "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2", - "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16", - "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa", - "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591", - "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964", - "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821", - "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484", - "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957", - "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800", - "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918", - "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95", - "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0", - "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e", - "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d", - "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73", - "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59", - "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51", - "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355", - "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348", - "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e", - "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440", - "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675", - "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84", - "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046", - "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab", - "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712", - "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308", - "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315", - "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3", - "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008", - "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5", - "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2", - "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e", - "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7", - "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf", - "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab", - "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd", - "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf", - "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8", - "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb", - "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268", - "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d", - "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780", - "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716", - "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e", - "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528", - "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af", - "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7", - "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51" + "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", + "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", + "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", + "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", + "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", + "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", + "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", + "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", + "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", + "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", + "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", + "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", + "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", + "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", + "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", + "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", + "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", + "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", + "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", + "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", + "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", + "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", + "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", + "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", + "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", + "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", + "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", + "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", + "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", + "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", + "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", + "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", + "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", + "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", + "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", + "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" ], "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==2.2.1" + "markers": "python_version >= '3.9'", + "version": "==1.26.4" }, "openapi-schema-validator": { "hashes": [ diff --git a/app/main.py b/app/main.py index ef1a5004..cca38542 100644 --- a/app/main.py +++ b/app/main.py @@ -13,6 +13,7 @@ from starlette.middleware.base import BaseHTTPMiddleware from app.errors import http_error_handler +from app.routes.political import id_lookup from .application import app from .middleware import no_cache_response_header, redirect_latest, set_db_mode @@ -128,6 +129,13 @@ async def rve_error_handler( app.include_router(r, prefix="/dataset") +################ +# POLITICAL API # +################ + +app.include_router(id_lookup.router, prefix="/political") + + ############### # ASSET API ############### diff --git a/app/models/pydantic/political.py b/app/models/pydantic/political.py new file mode 100644 index 00000000..aa868204 --- /dev/null +++ b/app/models/pydantic/political.py @@ -0,0 +1,97 @@ +from typing import List, Optional + +from fastapi.params import Query +from pydantic import Field, root_validator + +from app.models.pydantic.base import StrictBaseModel +from app.models.pydantic.responses import Response +from app.settings.globals import ENV, per_env_admin_boundary_versions + + +class AdminIDLookupQueryParams(StrictBaseModel): + admin_source: str = Field( + "GADM", + description=( + "The source of administrative boundaries to use " + "(currently the only valid choice is 'GADM')." + ), + ) + admin_version: str = Query( + ..., + description=( + "The version of the administrative boundaries to use " + "(note that this represents the release of the source dataset, " + "not the GFW Data API's idea of the version in the database)." + ), + ) + country: str = Query( + ..., + description="Name of the country to match.", + ) + region: Optional[str] = Query( + None, + description="Name of the region to match.", + ) + subregion: Optional[str] = Query( + None, + description="Name of the subregion to match.", + ) + normalize_search: bool = Query( + True, + description=( + "Whether or not to perform a case- and accent-insensitive search." + ), + ) + + @root_validator(pre=True) + def validate_params(cls, values): + source = values.get("admin_source") + if source is None: + raise ValueError( + "You must provide admin_source or leave unset for the " + "default value of 'GADM'." + ) + + version = values.get("admin_version") + if version is None: + raise ValueError("You must provide an admin_version") + + sources_in_this_env = per_env_admin_boundary_versions[ENV] + + versions_of_source_in_this_env = sources_in_this_env.get(source) + if versions_of_source_in_this_env is None: + raise ValueError( + f"Invalid administrative boundary source {source}. Valid " + f"sources in this environment are {[v for v in sources_in_this_env.keys()]}" + ) + + deployed_version_in_data_api = versions_of_source_in_this_env.get(version) + if deployed_version_in_data_api is None: + raise ValueError( + f"Invalid version {version} for administrative boundary source " + f"{source}. Valid versions for this source in this environment are " + f"{[v for v in versions_of_source_in_this_env.keys()]}" + ) + + return values + + +class AdminIDLookupMatchElement(StrictBaseModel): + id: str | None + name: str | None + + +class AdminIDLookupMatch(StrictBaseModel): + country: AdminIDLookupMatchElement + region: AdminIDLookupMatchElement + subregion: AdminIDLookupMatchElement + + +class AdminIDLookupResponseData(StrictBaseModel): + adminSource: str + adminVersion: str + matches: List[AdminIDLookupMatch] + + +class AdminIDLookupResponse(Response): + data: AdminIDLookupResponseData diff --git a/app/routes/political/__init__.py b/app/routes/political/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/routes/political/id_lookup.py b/app/routes/political/id_lookup.py new file mode 100644 index 00000000..9f2f847f --- /dev/null +++ b/app/routes/political/id_lookup.py @@ -0,0 +1,168 @@ +from typing import Annotated, Any, Dict, List + +from fastapi import APIRouter, HTTPException, Query +from unidecode import unidecode + +from app.models.pydantic.political import ( + AdminIDLookupQueryParams, + AdminIDLookupResponse, + AdminIDLookupResponseData, +) +from app.routes.datasets.queries import _query_dataset_json +from app.settings.globals import ENV, per_env_admin_boundary_versions + +router = APIRouter() + + +@router.get("/id-lookup", status_code=200, include_in_schema=False) +async def id_lookup(params: Annotated[AdminIDLookupQueryParams, Query()]): + """Look up administrative boundary IDs matching a specified country name + (and region name and subregion name, if specified).""" + admin_source_to_dataset: Dict[str, str] = {"GADM": "gadm_administrative_boundaries"} + + try: + dataset: str = admin_source_to_dataset[params.admin_source] + except KeyError: + raise HTTPException( + status_code=400, + detail=( + "Invalid admin boundary source. Valid sources:" + f" {[source for source in admin_source_to_dataset.keys()]}" + ), + ) + + version_str: str = lookup_admin_source_version( + params.admin_source, params.admin_version + ) + + names: List[str | None] = normalize_names( + params.normalize_search, params.country, params.region, params.subregion + ) + + adm_level: int = determine_admin_level(*names) + + sql: str = _admin_boundary_lookup_sql( + adm_level, params.normalize_search, dataset, *names + ) + + json_data: List[Dict[str, Any]] = await _query_dataset_json( + dataset, version_str, sql, None + ) + + return form_admin_id_lookup_response( + params.admin_source, params.admin_version, adm_level, json_data + ) + + +def normalize_names( + normalize_search: bool, + country: str | None, + region: str | None, + subregion: str | None, +) -> List[str | None]: + """Turn any empty strings into Nones, enforces the admin level hierarchy, + and optionally unaccents and decapitalizes names.""" + names: List[str | None] = [] + + if subregion and not region: + raise HTTPException( + status_code=400, + detail="If subregion is specified, region must be specified as well.", + ) + + for name in (country, region, subregion): + if name and normalize_search: + names.append(unidecode(name).lower()) + elif name: + names.append(name) + else: + names.append(None) + return names + + +def determine_admin_level( + country: str | None, region: str | None, subregion: str | None +) -> int: + """Infer the native admin level of a request based on the presence of non- + empty fields.""" + if subregion: + return 2 + elif region: + return 1 + elif country: + return 0 + else: # Shouldn't get here if FastAPI route definition worked + raise HTTPException(status_code=400, detail="Country MUST be specified.") + + +def _admin_boundary_lookup_sql( + adm_level: int, + normalize_search: bool, + dataset: str, + country_name: str, + region_name: str | None, + subregion_name: str | None, +) -> str: + """Generate the SQL required to look up administrative boundary IDs by + name.""" + name_fields: List[str] = ["country", "name_1", "name_2"] + if normalize_search: + match_name_fields = [name_field + "_normalized" for name_field in name_fields] + else: + match_name_fields = name_fields + + sql = ( + f"SELECT gid_0, gid_1, gid_2, {name_fields[0]}, {name_fields[1]}, {name_fields[2]}" + f" FROM {dataset} WHERE {match_name_fields[0]}=$country${country_name}$country$" + ) + if region_name is not None: + sql += f" AND {match_name_fields[1]}=$region${region_name}$region$" + if subregion_name is not None: + sql += f" AND {match_name_fields[2]}=$subregion${subregion_name}$subregion$" + + sql += f" AND adm_level='{adm_level}'" + + return sql + + +def lookup_admin_source_version(source: str, version: str) -> str: + # The AdminIDLookupQueryParams validator should have already ensured + # that the following is safe + deployed_version_in_data_api = per_env_admin_boundary_versions[ENV][source][version] + + return deployed_version_in_data_api + + +def form_admin_id_lookup_response( + admin_source, admin_version, adm_level: int, match_list +) -> AdminIDLookupResponse: + matches = [] + + for match in match_list: + country = {"id": extract_level_gid(0, match), "name": match["country"]} + + if adm_level < 1: + region = {"id": None, "name": None} + else: + region = {"id": extract_level_gid(1, match), "name": match["name_1"]} + + if adm_level < 2: + subregion = {"id": None, "name": None} + else: + subregion = {"id": extract_level_gid(2, match), "name": match["name_2"]} + + matches.append({"country": country, "region": region, "subregion": subregion}) + + data = AdminIDLookupResponseData( + **{ + "adminSource": admin_source, + "adminVersion": admin_version, + "matches": matches, + } + ) + return AdminIDLookupResponse(data=data) + + +def extract_level_gid(gid_level: int, match): + gid_level_name = f"gid_{gid_level}" + return (match[gid_level_name].rsplit("_")[0]).split(".")[gid_level] diff --git a/app/settings/globals.py b/app/settings/globals.py index 47d9ac72..c9132776 100644 --- a/app/settings/globals.py +++ b/app/settings/globals.py @@ -1,6 +1,6 @@ import json from pathlib import Path -from typing import Optional +from typing import Dict, Optional from starlette.config import Config from starlette.datastructures import Secret @@ -185,3 +185,28 @@ RASTER_ANALYSIS_STATE_MACHINE_ARN = config( "RASTER_ANALYSIS_STATE_MACHINE_ARN", cast=str, default=None ) + +# TODO: Find a good home for this: +per_env_admin_boundary_versions: Dict[str, Dict[str, Dict]] = { + "test": { + "GADM": { + "3.6": "v3.6", + "4.1": "v4.1.64", + } + }, + "dev": { + "GADM": { + "4.1": "v4.1.64", + } + }, + "staging": { + "GADM": { + "4.1": "v4.1.64", + } + }, + "production": { + "GADM": { + "4.1": "v4.1.64", + } + }, +} diff --git a/tests_v2/unit/app/routes/political/__init__.py b/tests_v2/unit/app/routes/political/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests_v2/unit/app/routes/political/id_lookup/__init__.py b/tests_v2/unit/app/routes/political/id_lookup/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests_v2/unit/app/routes/political/id_lookup/test_id_lookup.py b/tests_v2/unit/app/routes/political/id_lookup/test_id_lookup.py new file mode 100644 index 00000000..672f00cf --- /dev/null +++ b/tests_v2/unit/app/routes/political/id_lookup/test_id_lookup.py @@ -0,0 +1,306 @@ +from typing import Any, Dict, List, Optional + +import pytest +from fastapi import HTTPException +from httpx import AsyncClient + +from app.models.pydantic.geostore import GeostoreCommon +from app.routes.political import id_lookup +from app.routes.political.id_lookup import _admin_boundary_lookup_sql, normalize_names + +ENDPOINT_UNDER_TEST = "/political/id-lookup" + + +@pytest.mark.asyncio +async def test_sanitize_names_pass_through() -> None: + country = "A Country" + region = "Some region" + subregion = "SUBREGION" + normalize = False + + names = normalize_names(normalize, country, region, subregion) + + assert names == [country, region, subregion] + + +@pytest.mark.asyncio +async def test_sanitize_names_normalize() -> None: + country = "Fictîcious de San México" + region = "Söme Reğion" + subregion = "SÜBREGION" + normalize = True + + names = normalize_names(normalize, country, region, subregion) + + assert names == ["ficticious de san mexico", "some region", "subregion"] + + +@pytest.mark.asyncio +async def test_sanitize_names_tolerate_empty() -> None: + country = "México" + region = "Tijuana" + subregion = "" + normalize = False + + names = normalize_names(normalize, country, region, subregion) + + assert names == [country, region, None] + + +@pytest.mark.asyncio +async def test_sanitize_names_tolerate_enforce_hierarchy() -> None: + country = "México" + region = None + subregion = "some subregion" + normalize = False + + try: + _ = normalize_names(normalize, country, region, subregion) + except HTTPException as e: + assert ( + e.detail == "If subregion is specified, region must be specified as well." + ) + + +@pytest.mark.asyncio +async def test__admin_boundary_lookup_sql_country() -> None: + sql = _admin_boundary_lookup_sql( + 0, False, "some_dataset", "some_country", None, None + ) + assert sql == ( + "SELECT gid_0, gid_1, gid_2, country, name_1, name_2 FROM some_dataset" + " WHERE country=$country$some_country$country$ AND adm_level='0'" + ) + + +@pytest.mark.asyncio +async def test__admin_boundary_lookup_sql_country_region() -> None: + sql = _admin_boundary_lookup_sql( + 1, False, "some_dataset", "some_country", "some_region", None + ) + assert sql == ( + "SELECT gid_0, gid_1, gid_2, country, name_1, name_2 FROM some_dataset" + " WHERE country=$country$some_country$country$" + " AND name_1=$region$some_region$region$" + " AND adm_level='1'" + ) + + +@pytest.mark.asyncio +async def test__admin_boundary_lookup_sql_all() -> None: + sql = _admin_boundary_lookup_sql( + 2, False, "some_dataset", "some_country", "some_region", "some_subregion" + ) + assert sql == ( + "SELECT gid_0, gid_1, gid_2, country, name_1, name_2 FROM some_dataset" + " WHERE country=$country$some_country$country$" + " AND name_1=$region$some_region$region$" + " AND name_2=$subregion$some_subregion$subregion$" + " AND adm_level='2'" + ) + + +@pytest.mark.asyncio +async def test__admin_boundary_lookup_sql_all_normalized() -> None: + sql = _admin_boundary_lookup_sql( + 2, True, "some_dataset", "some_country", "some_region", "some_subregion" + ) + assert sql == ( + "SELECT gid_0, gid_1, gid_2, country, name_1, name_2 FROM some_dataset" + " WHERE country_normalized=$country$some_country$country$" + " AND name_1_normalized=$region$some_region$region$" + " AND name_2_normalized=$subregion$some_subregion$subregion$" + " AND adm_level='2'" + ) + + +@pytest.mark.asyncio +async def test__admin_boundary_lookup_sql_no_single_quotes() -> None: + sql = _admin_boundary_lookup_sql( + 2, False, "some_dataset", "Côte d'Ivoire", "some_region", "some_subregion" + ) + assert sql == ( + "SELECT gid_0, gid_1, gid_2, country, name_1, name_2 FROM some_dataset" + " WHERE country=$country$Côte d'Ivoire$country$" + " AND name_1=$region$some_region$region$" + " AND name_2=$subregion$some_subregion$subregion$" + " AND adm_level='2'" + ) + + +@pytest.mark.asyncio +async def test_id_lookup_no_admin_version(async_client: AsyncClient) -> None: + params = {"country": "Canada"} + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert "You must provide an admin_version" in resp.text + assert resp.status_code == 422 + + +@pytest.mark.asyncio +async def test_id_lookup_nonexistant_version(async_client: AsyncClient) -> None: + params = {"country": "Canada", "admin_version": "4.0"} + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert "Invalid version 4.0" in resp.text + assert resp.status_code == 422 + + +@pytest.mark.asyncio +async def test_id_lookup_nonexistant_version_lists_existing( + async_client: AsyncClient, monkeypatch: pytest.MonkeyPatch +) -> None: + params = {"country": "Canada", "admin_version": "4.0"} + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert "3.6" in resp.text + assert "4.1" in resp.text + assert resp.status_code == 422 + + +@pytest.mark.asyncio +async def test_id_lookup_bad_boundary_source(async_client: AsyncClient) -> None: + params = { + "admin_source": "bobs_boundaries", + "admin_version": "4.1", + "country": "Canadiastan", + } + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert "Invalid administrative boundary source bobs_boundaries" in resp.text + assert "GADM" in resp.text + assert resp.status_code == 422 + + +@pytest.mark.asyncio +async def test_id_lookup_no_matches( + async_client: AsyncClient, monkeypatch: pytest.MonkeyPatch +) -> None: + admin_source = "GADM" + admin_version = "4.1" + + params = { + "admin_source": admin_source, + "admin_version": admin_version, + "country": "Canadiastan", + } + + monkeypatch.setattr( + id_lookup, "_query_dataset_json", _query_dataset_json_mocked_no_results + ) + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert resp.json() == { + "status": "success", + "data": { + "adminSource": admin_source, + "adminVersion": admin_version, + "matches": [], + }, + } + assert resp.status_code == 200 + + +@pytest.mark.asyncio +async def test_id_lookup_matches_full( + async_client: AsyncClient, monkeypatch: pytest.MonkeyPatch +) -> None: + admin_source = "GADM" + admin_version = "4.1" + + params = { + "admin_source": admin_source, + "admin_version": admin_version, + "country": "Taiwan", + "region": "Fujian", + "subregion": "Kinmen", + } + + monkeypatch.setattr( + id_lookup, "_query_dataset_json", _query_dataset_json_mocked_results + ) + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert resp.json() == { + "status": "success", + "data": { + "adminSource": admin_source, + "adminVersion": admin_version, + "matches": [ + { + "country": {"id": "TWN", "name": "Taiwan"}, + "region": {"id": "1", "name": "Fujian"}, + "subregion": {"id": "1", "name": "Kinmen"}, + } + ], + }, + } + assert resp.status_code == 200 + + +@pytest.mark.asyncio +async def test_id_lookup_matches_hide_extraneous( + async_client: AsyncClient, monkeypatch: pytest.MonkeyPatch +) -> None: + admin_source = "GADM" + admin_version = "4.1" + + params = { + "admin_source": admin_source, + "admin_version": admin_version, + "country": "Taiwan", + } + + monkeypatch.setattr( + id_lookup, "_query_dataset_json", _query_dataset_json_mocked_results + ) + + resp = await async_client.get(ENDPOINT_UNDER_TEST, params=params) + + assert resp.json() == { + "status": "success", + "data": { + "adminSource": admin_source, + "adminVersion": admin_version, + "matches": [ + { + "country": {"id": "TWN", "name": "Taiwan"}, + "region": {"id": None, "name": None}, + "subregion": {"id": None, "name": None}, + } + ], + }, + } + assert resp.status_code == 200 + + +async def _query_dataset_json_mocked_no_results( + dataset: str, + version: str, + sql: str, + geostore: Optional[GeostoreCommon], +) -> List[Dict[str, Any]]: + return [] + + +async def _query_dataset_json_mocked_results( + dataset: str, + version: str, + sql: str, + geostore: Optional[GeostoreCommon], +) -> List[Dict[str, Any]]: + return [ + { + "gid_0": "TWN", + "gid_1": "TWN.1_1", + "gid_2": "TWN.1.1_1", + "country": "Taiwan", + "name_1": "Fujian", + "name_2": "Kinmen", + } + ]