From b92198565098b9f5e1f2306e9f0f4c1f97d23701 Mon Sep 17 00:00:00 2001 From: Lawson Lewis Date: Wed, 28 Feb 2024 09:31:53 +1000 Subject: [PATCH] remove external connegp dependencies remove external connegp dependencies non-default profile resolution not working. test passing need to add more tests clean up refactored code seems to be all working. need to implement tests debugging case of no requested profile connegp almost implemented. object function not tested --- connegp-0.1.6-py3-none-any.whl | Bin 5052 -> 0 bytes poetry.lock | 343 +++++++++--------- prez/models/profiles_and_mediatypes.py | 58 --- prez/services/connegp_service.py | 55 ++- prez/services/generate_profiles.py | 128 +------ prez/services/listings.py | 86 +++-- prez/services/objects.py | 22 +- prez/services/query_generation/connegp.py | 123 ------- pyproject.toml | 1 - tests/data/profiles/ogc_records_profile.ttl | 106 ++++++ .../profiles/spaceprez_default_profiles.ttl | 138 +++++++ tests/test_connegp.py | 152 ++++++-- 12 files changed, 613 insertions(+), 599 deletions(-) delete mode 100755 connegp-0.1.6-py3-none-any.whl delete mode 100755 prez/models/profiles_and_mediatypes.py delete mode 100644 prez/services/query_generation/connegp.py create mode 100755 tests/data/profiles/ogc_records_profile.ttl create mode 100755 tests/data/profiles/spaceprez_default_profiles.ttl diff --git a/connegp-0.1.6-py3-none-any.whl b/connegp-0.1.6-py3-none-any.whl deleted file mode 100755 index 1cc73c1f496f823f23e2b09aad796cd32cefe045..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5052 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 H)&TqmtxK%P diff --git a/poetry.lock b/poetry.lock index 82c21253..ca483b23 100755 --- a/poetry.lock +++ b/poetry.lock @@ -93,13 +93,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cachetools" -version = "5.3.2" +version = "5.3.3" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, ] [[package]] @@ -259,82 +259,65 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "connegp" -version = "0.1.6" -description = "Content negotiation by profile" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "connegp-0.1.6-py3-none-any.whl", hash = "sha256:8d4f7f605d568032243e7cfa84c22bedae66e28651acb58af82b4b43d3de899f"}, -] - -[package.dependencies] -pydantic = ">=1.8.2,<3.0.0" - -[package.source] -type = "file" -url = "connegp-0.1.6-py3-none-any.whl" - [[package]] name = "coverage" -version = "7.4.1" +version = "7.4.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, ] [package.extras] @@ -455,13 +438,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.3" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.3-py3-none-any.whl", hash = "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2"}, - {file = "httpcore-1.0.3.tar.gz", hash = "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -472,7 +455,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.24.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" @@ -919,18 +902,18 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "2.6.1" +version = "2.6.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, - {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, + {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, + {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.2" +pydantic-core = "2.16.3" typing-extensions = ">=4.6.1" [package.extras] @@ -938,90 +921,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.2" +version = "2.16.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, - {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, - {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, - {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, - {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, - {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, - {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, - {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, - {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, - {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, - {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, - {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, - {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, - {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, - {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, - {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, - {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, - {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, - {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, - {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, - {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, - {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, - {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, - {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, - {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, - {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, - {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, - {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, - {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, - {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, - {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, ] [package.dependencies] @@ -1029,13 +1012,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.2.0" +version = "2.2.1" description = "Settings management using Pydantic" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_settings-2.2.0-py3-none-any.whl", hash = "sha256:5f7bcaf9ad4419559dc5ac155c0324a9aeb2547c60471ee7c7d026f467a6b515"}, - {file = "pydantic_settings-2.2.0.tar.gz", hash = "sha256:648d0a76673e69c51278979cba2e83cf16a23d57519bfd7e553d1c3f37db5560"}, + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, ] [package.dependencies] @@ -1043,7 +1026,7 @@ pydantic = ">=2.3.0" python-dotenv = ">=0.21.0" [package.extras] -toml = ["tomlkit (>=0.12)"] +toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] [[package]] @@ -1374,19 +1357,19 @@ wheel = ">=0.36.1" [[package]] name = "setuptools" -version = "69.1.0" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"}, - {file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shapely" @@ -1458,13 +1441,13 @@ files = [ [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -1497,13 +1480,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -1543,13 +1526,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.25.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, ] [package.dependencies] @@ -1578,4 +1561,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "9e52b0cd2075bbbc6693b39f79b388a505e3744feeeb85c235b7f735afedc848" +content-hash = "86ae28eb5f2c4f08bc245ca34113f8d401778b377ca1050aa5d25565ff7ebe1e" diff --git a/prez/models/profiles_and_mediatypes.py b/prez/models/profiles_and_mediatypes.py deleted file mode 100755 index fdaa646f..00000000 --- a/prez/models/profiles_and_mediatypes.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import FrozenSet, Optional - -from pydantic import BaseModel, model_validator -from rdflib import Namespace, URIRef -from starlette.requests import Request - -from prez.services.generate_profiles import get_profiles_and_mediatypes -from prez.services.connegp_service import get_requested_profile_and_mediatype -from prez.repositories import Repo - -PREZ = Namespace("https://prez.dev/") - - -class ProfilesMediatypesInfo(BaseModel): - class Config: - arbitrary_types_allowed = True - - request: Request # TODO slim down once connegp is refactored so the whole request doesn't need to be passed through - classes: FrozenSet[URIRef] - system_repo: Repo - req_profiles: Optional[str] = None - req_profiles_token: Optional[str] = None - req_mediatypes: Optional[FrozenSet] = None - profile: Optional[URIRef] = None - mediatype: Optional[str] = None - selected_class: Optional[URIRef] = None - profile_headers: Optional[str] = None - avail_profile_uris: Optional[str] = None - listing: Optional[bool] = False - - @model_validator(mode="after") - def populate_requested_types(self): - request = self.request - ( - self.req_profiles, - self.req_profiles_token, - self.req_mediatypes, - ) = get_requested_profile_and_mediatype(request) - return self - - -async def populate_profile_and_mediatype( - prof_model: ProfilesMediatypesInfo, system_repo: Repo -): - req_profiles = prof_model.req_profiles - req_profiles_token = prof_model.req_profiles_token - req_mediatypes = prof_model.req_mediatypes - classes = prof_model.classes - listing = prof_model.listing - ( - prof_model.profile, - prof_model.mediatype, - prof_model.selected_class, - prof_model.profile_headers, - prof_model.avail_profile_uris, - ) = await get_profiles_and_mediatypes( - classes, system_repo, req_profiles, req_profiles_token, req_mediatypes, listing - ) diff --git a/prez/services/connegp_service.py b/prez/services/connegp_service.py index 08b4aaa8..1053dfc8 100755 --- a/prez/services/connegp_service.py +++ b/prez/services/connegp_service.py @@ -3,14 +3,11 @@ from textwrap import dedent from pydantic import BaseModel -from pyoxigraph import Store from rdflib import Graph, Namespace, URIRef -from prez.cache import prefix_graph, system_store -from prez.dependencies import get_system_repo from prez.models.model_exceptions import NoProfilesException from prez.repositories.base import Repo -from prez.services.curie_functions import get_curie_id_for_uri +from prez.services.curie_functions import get_curie_id_for_uri, get_uri_for_curie_id logger = logging.getLogger("prez") @@ -37,64 +34,58 @@ class NegotiatedPMTs(BaseModel): headers: dict params: dict classes: list[URIRef] + system_repo: Repo listing: bool = False default_weighting: float = 1.0 requested_profiles: list[tuple[str, float]] | None = None requested_mediatypes: list[tuple[str, float]] | None = None available: list[dict] | None = None selected: dict | None = None - _system_store: Store | None = None - _prefix_graph: Graph | None = None - _system_repo: Repo | None = None class Config: arbitrary_types_allowed = True async def setup(self) -> bool: - if self._system_store is None: - self._system_store = system_store - if self._prefix_graph is None: - self._prefix_graph = prefix_graph - if self._system_repo is None: - self._system_repo = await get_system_repo(self._system_store) self.requested_profiles = await self._get_requested_profiles() self.requested_mediatypes = await self._get_requested_mediatypes() self.available = await self._get_available() self.selected = await self._get_selected() return True if self.selected else False - def _resolve_token(self, token: str) -> str: + async def _resolve_token(self, token: str) -> str: query_str: str = dedent(""" PREFIX dcterms: PREFIX xsd: PREFIX prof: - SELECT ?s + SELECT ?profile WHERE { - ?s a prof:Profile . - ?s dcterms:identifier ?o . + ?profile a prof:Profile . + ?profile dcterms:identifier ?o . FILTER(?o=""^^xsd:token) } """.replace("", token)) try: - result = {result[0].value for result in self._system_store.query(query_str)}.pop() - except KeyError: + _, results = await self.system_repo.send_queries([], [(None, query_str)]) + result: str = results[0][1][0]["profile"]["value"] + except (KeyError, IndexError, ValueError): raise TokenError(f"Token: '{token}' could not be resolved to URI") uri = "<" + result + ">" return uri - def _tupilize(self, string: str, is_profile: bool = False) -> tuple[str, float]: + async def _tupilize(self, string: str, is_profile: bool = False) -> tuple[str, float]: parts: list[str | float] = string.split("q=") # split out the weighting parts[0] = parts[0].strip(" ;") # remove the seperator character, and any whitespace characters if is_profile and not re.search(r"^<.*>$", parts[0]): # If it doesn't look like a URI ... try: - parts[0] = self._resolve_token(parts[0]) # then try to resolve the token to a URI + parts[0] = await self._resolve_token(parts[0]) # then try to resolve the token to a URI except TokenError as e: logger.error(e.args[0]) try: # if token resolution fails, try to resolve as a curie - result = str(self._prefix_graph.namespace_manager.expand_curie(parts[0])) + result = str(get_uri_for_curie_id(parts[0])) parts[0] = "<" + result + ">" except ValueError as e: + parts[0] = "" # if curie resolution failed, then the profile is invalid logger.error(e.args[0]) if len(parts) == 1: parts.append(self.default_weighting) # If no weight given, set the default @@ -102,8 +93,7 @@ def _tupilize(self, string: str, is_profile: bool = False) -> tuple[str, float]: try: parts[1] = float(parts[1]) # Type-check the seperated weighting except ValueError as e: - log = logging.getLogger("prez") - log.debug( + logger.debug( f"Could not cast q={parts[1]} as float. Defaulting to {self.default_weighting}. {e.args[0]}") return parts[0], parts[1] @@ -114,18 +104,18 @@ def _prioritize(types: list[tuple[str, float]]) -> list[tuple[str, float]]: async def _get_requested_profiles(self) -> list[tuple[str, float]] | None: raw_profiles: str = self.params.get("_profile", "") # Prefer profiles declared in the QSA, as per the spec. if not raw_profiles: - raw_profiles: str = self.headers.get("Accept-Profile", "") + raw_profiles: str = self.headers.get("accept-profile", "") if raw_profiles: - profiles: list = [self._tupilize(profile, is_profile=True) for profile in raw_profiles.split(",")] + profiles: list = [await self._tupilize(profile, is_profile=True) for profile in raw_profiles.split(",")] return self._prioritize(profiles) return None async def _get_requested_mediatypes(self) -> list[tuple[str, float]] | None: raw_mediatypes: str = self.params.get("_media", "") # Prefer mediatypes declared in the QSA, as per the spec. if not raw_mediatypes: - raw_mediatypes: str = self.headers.get("Accept", "") + raw_mediatypes: str = self.headers.get("accept", "") if raw_mediatypes: - mediatypes: list = [self._tupilize(mediatype) for mediatype in raw_mediatypes.split(",")] + mediatypes: list = [await self._tupilize(mediatype) for mediatype in raw_mediatypes.split(",")] return self._prioritize(mediatypes) return None @@ -162,7 +152,6 @@ def generate_response_headers(self) -> dict: ] ) headers = { - "Access-Control-Allow-Origin": "*", # HACK: why is this specified here? "Content-Type": self.selected["mediatype"], "link": profile_header_links + mediatype_header_links } @@ -172,10 +161,10 @@ def _compose_select_query(self) -> str: prez = Namespace("https://prez.dev/") profile_class = prez.ListingProfile if self.listing else prez.ObjectProfile try: - requested_profile = self.requested_profiles[0] # TODO: handle multiple requested profiles + requested_profile = self.requested_profiles[0][0] # TODO: handle multiple requested profiles except TypeError as e: requested_profile = None - logger.debug(e) + logger.debug(f"{e}. normally this just means no profiles were requested") query = dedent( f""" @@ -203,7 +192,7 @@ def _compose_select_query(self) -> str: altr-ext:hasResourceFormat ?format ; dcterms:title ?title .\ {f'?profile a {profile_class.n3()} .'} - {f'BIND(?profile=<{requested_profile}> as ?req_profile)' if requested_profile else ''} + {f'BIND(?profile={requested_profile} as ?req_profile)' if requested_profile else ''} BIND(EXISTS {{ ?shape sh:targetClass ?class ; altr-ext:hasDefaultProfile ?profile }} AS ?def_profile) {self._generate_mediatype_if_statements()} @@ -241,7 +230,7 @@ def _generate_mediatype_if_statements(self) -> str: return ifs async def _do_query(self, query: str) -> tuple[Graph, list]: - response = await self._system_repo.send_queries([], [(None, query)]) + response = await self.system_repo.send_queries([], [(None, query)]) if not response[1][0][1]: raise NoProfilesException(self.classes) return response diff --git a/prez/services/generate_profiles.py b/prez/services/generate_profiles.py index 532a1785..734122bf 100755 --- a/prez/services/generate_profiles.py +++ b/prez/services/generate_profiles.py @@ -1,15 +1,9 @@ import logging from pathlib import Path -from typing import FrozenSet -from rdflib import Graph, URIRef, RDF, PROF, Literal +from rdflib import Graph -from prez.cache import profiles_graph_cache, prefix_graph -from prez.models.model_exceptions import NoProfilesException -from prez.reference_data.prez_ns import PREZ -from prez.services.curie_functions import get_curie_id_for_uri -from prez.repositories import Repo -from prez.services.query_generation.connegp import select_profile_mediatype +from prez.cache import profiles_graph_cache log = logging.getLogger(__name__) @@ -61,121 +55,3 @@ async def create_profiles_graph(repo) -> Graph: log.info(f"Remote profile(s) found and added") else: log.info("No remote profiles found") - - -async def get_profiles_and_mediatypes( - classes: FrozenSet[URIRef], - system_repo: Repo, - requested_profile: URIRef = None, - requested_profile_token: str = None, - requested_mediatype: URIRef = None, - listing: bool = False, -): - query = select_profile_mediatype( - classes, - requested_profile, - requested_profile_token, - requested_mediatype, - listing, - ) - log.debug(f"ConnegP query: {query}") - response = await system_repo.send_queries([], [(None, query)]) - # log.debug(f"ConnegP response:{results_pretty_printer(response)}") - if response[1][0][1] == []: - raise NoProfilesException(classes) - top_result = response[1][0][1][0] - profile, mediatype, selected_class = ( - URIRef(top_result["profile"]["value"]), - Literal(top_result["format"]["value"]), - URIRef(top_result["class"]["value"]), - ) - profile_headers, avail_profile_uris = generate_profiles_headers( - selected_class, response, profile, mediatype - ) - return profile, mediatype, selected_class, profile_headers, avail_profile_uris - - -def results_pretty_printer(response): - # Calculate max width for each column, including the new "#" column - max_widths = [ - len(str(len(response.bindings))) - ] # length of the highest row number as a string - for header in response.vars: - max_width = max( - len(header.n3(prefix_graph.namespace_manager)), - max( - len( - row[header].n3(prefix_graph.namespace_manager) - if row[header] - else "" - ) - for row in response.bindings - ), - ) - max_widths.append(max_width) - - # Header row - header_row = "\n" + " | ".join( - ["#".ljust(max_widths[0])] - + [ - str(header).ljust(max_widths[i + 1]) - for i, header in enumerate(response.vars) - ] - ) - pp_string = header_row + "\n" - pp_string += ("-" * len(header_row)) + "\n" # Divider - - # Data rows - row_number = 1 - for row in response.bindings: - row_data = [str(row_number).ljust(max_widths[0])] - row_data += [ - ( - row[header].n3(prefix_graph.namespace_manager) if row[header] else "" - ).ljust(max_widths[i + 1]) - for i, header in enumerate(response.vars) - ] - formatted_row = " | ".join(row_data) - pp_string += formatted_row + "\n" - row_number += 1 - - return pp_string - - -def generate_profiles_headers(selected_class, response, profile, mediatype): - headers = { - "Access-Control-Allow-Origin": "*", - "Content-Type": mediatype, - } - avail_profiles = set( - ( - get_curie_id_for_uri(i["profile"]["value"]), - i["profile"]["value"], - i["title"]["value"], - ) - for i in response[1][0][1] - ) - avail_profiles_headers = ", ".join( - [ - f'; rel="type"; title="{i[2]}"; token="{i[0]}"; anchor=<{i[1]}>' - for i in avail_profiles - ] - ) - avail_mediatypes_headers = ", ".join( - [ - f"""<{selected_class}?_profile={get_curie_id_for_uri(i["profile"]["value"])}&_mediatype={i["format"]["value"]}>; \ -rel="{"self" if i["profile"]["value"] == profile and i["format"]["value"] == mediatype else "alternate"}"; \ -type="{i["format"]["value"]}"; profile="{i["profile"]["value"]}"\ -""" - for i in response[1][0][1] - ] - ) - headers["Link"] = ", ".join( - [ - f'<{profile}>; rel="profile"', - avail_profiles_headers, - avail_mediatypes_headers, - ] - ) - avail_profile_uris = [i[1] for i in avail_profiles] - return headers, avail_profile_uris diff --git a/prez/services/listings.py b/prez/services/listings.py index a8b6cc81..e2d6f4a1 100755 --- a/prez/services/listings.py +++ b/prez/services/listings.py @@ -9,13 +9,10 @@ from prez.cache import profiles_graph_cache, endpoints_graph_cache from prez.config import settings -from prez.models.profiles_and_mediatypes import ( - ProfilesMediatypesInfo, - populate_profile_and_mediatype, -) from prez.reference_data.prez_ns import PREZ from prez.renderers.renderer import return_from_graph from prez.repositories import Repo +from prez.services.connegp_service import NegotiatedPMTs from prez.services.link_generation import add_prez_links from prez.services.query_generation.classes import get_classes from prez.services.query_generation.count import CountQuery @@ -32,17 +29,17 @@ async def listing_function( - request: Request, - repo: Repo, - system_repo: Repo, - endpoint_uri: URIRef, - hierarchy_level: int, - path_nodes: Dict[str, Var | IRI] = None, - page: int = 1, - per_page: int = 20, - cql_parser: CQLParser = None, - search_term: Optional[str] = None, - endpoint_structure: Tuple[str] = settings.endpoint_structure, + request: Request, + repo: Repo, + system_repo: Repo, + endpoint_uri: URIRef, + hierarchy_level: int, + path_nodes: Dict[str, Var | IRI] = None, + page: int = 1, + per_page: int = 20, + cql_parser: CQLParser = None, + search_term: Optional[str] = None, + endpoint_structure: Tuple[str] = settings.endpoint_structure, ): """ # determine the relevant node selection part of the query - from SHACL, CQL, Search @@ -65,29 +62,25 @@ async def listing_function( target_classes = frozenset([PREZ.CQLObjectList]) elif search_term: target_classes = frozenset([PREZ.SearchResult]) + # determine the relevant profile - prof_and_mt_info = ProfilesMediatypesInfo( - request=request, classes=target_classes, system_repo=system_repo, listing=True - ) - await populate_profile_and_mediatype(prof_and_mt_info, system_repo) - selected_class, selected_profile = ( - prof_and_mt_info.selected_class, - prof_and_mt_info.profile, - ) + pmts = NegotiatedPMTs(headers=request.headers, params=request.query_params, classes=target_classes, listing=True, + system_repo=system_repo) + success = await pmts.setup() + if not success: + log.error("ConnegP Error. NegotiatedPMTs.setup() was not successful") runtime_values = {} - if prof_and_mt_info.profile == URIRef( - "http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile" - ): + if pmts.selected["profile"] == URIRef("http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile"): ns = NodeShape( uri=URIRef("http://example.org/ns#AltProfilesForListing"), graph=endpoints_graph_cache, - path_nodes={"path_node_1": IRI(value=prof_and_mt_info.selected_class)} + path_nodes={"path_node_1": IRI(value=pmts.selected["class"])} ) ns_triples = ns.triples_list ns_gpnt = ns.gpnt_list endpoint_uri = URIRef("https://prez.dev/endpoint/system/alt-profiles-listing") - runtime_values["selectedClass"] = prof_and_mt_info.selected_class + runtime_values["selectedClass"] = pmts.selected["class"] runtime_values["limit"] = per_page runtime_values["offset"] = (page - 1) * per_page @@ -114,7 +107,7 @@ async def listing_function( profile_graph=profiles_graph_cache, listing_or_object="listing", endpoint_uri=endpoint_uri, - profile_uri=selected_profile, + profile_uri=pmts.selected["profile"], endpoint_shacl_triples=ns_triples, endpoint_shacl_gpnt=ns_gpnt, cql_triples=cql_triples_list, @@ -136,26 +129,31 @@ async def listing_function( queries.append(search_query) else: queries.append(main_query) - req_mt = prof_and_mt_info.req_mediatypes - if req_mt: - if list(req_mt)[0] == "application/sparql-query": - return PlainTextResponse(queries[0], media_type="application/sparql-query") + if pmts.requested_mediatypes is not None and pmts.requested_mediatypes[0][0] == "application/sparql-query": + return PlainTextResponse(queries[0], media_type="application/sparql-query") # add a count query if it's an annotated mediatype - if "anot+" in prof_and_mt_info.mediatype and not search_term: + if "anot+" in pmts.selected["mediatype"] and not search_term: subselect = copy.deepcopy(query_constructor.inner_select) count_query = CountQuery(subselect=subselect).render() queries.append(count_query) - if prof_and_mt_info.profile == URIRef( - "http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile" - ): + # if prof_and_mt_info.profile == URIRef( + # "http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile" + # ): + # count_class = PROF.Profile + # else: + # count_class = target_classes + # if count_class: # target_class may be unknown (None) for queries involving CQL + # queries.append(temp_listing_count(subselect, count_class)) + + if pmts.selected["profile"] == URIRef("http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile"): item_graph, _ = await system_repo.send_queries(queries, []) - if "anot+" in prof_and_mt_info.mediatype: + if "anot+" in pmts.selected["mediatype"]: await add_prez_links(item_graph, system_repo, endpoint_structure=("profiles",)) else: item_graph, _ = await repo.send_queries(queries, []) - if "anot+" in prof_and_mt_info.mediatype: + if "anot+" in pmts.selected["mediatype"]: await add_prez_links(item_graph, repo, endpoint_structure) # count search results - hard to do in SPARQL as the SELECT part of the query is NOT aggregated if search_term: @@ -163,16 +161,16 @@ async def listing_function( item_graph.add((PREZ.SearchResult, PREZ["count"], Literal(count))) return await return_from_graph( item_graph, - prof_and_mt_info.mediatype, - selected_profile, - prof_and_mt_info.profile_headers, - prof_and_mt_info.selected_class, + pmts.selected["mediatype"], + pmts.selected["profile"], + pmts.generate_response_headers(), + pmts.selected["class"], repo, ) async def get_shacl_node_selection( - endpoint_uri, hierarchy_level, path_nodes, repo, system_repo + endpoint_uri, hierarchy_level, path_nodes, repo, system_repo ): """ Determines the relevant nodeshape based on the endpoint, hierarchy level, and parent URI diff --git a/prez/services/objects.py b/prez/services/objects.py index d0562fda..48681e9b 100755 --- a/prez/services/objects.py +++ b/prez/services/objects.py @@ -20,20 +20,26 @@ async def object_function( - request: Request, - endpoint_uri: URIRef, - uri: URIRef, - repo: Repo, - system_repo: Repo, - endpoint_structure: Tuple[str] = settings.endpoint_structure, + request: Request, + endpoint_uri: URIRef, + uri: URIRef, + request_url: str, + repo: Repo, + system_repo: Repo, + endpoint_structure: Tuple[str] = settings.endpoint_structure, ): classes = await get_classes(uri=uri, repo=repo) - pmts = NegotiatedPMTs(**{"headers": request.headers, "params": request.query_params, "classes": classes}) + pmts = NegotiatedPMTs(headers=request.headers, params=request.query_params, classes=classes, + system_repo=system_repo) + success = await pmts.setup() + if not success: + log.error("ConnegP Error. NegotiatedPMTs.setup() was not successful") + # handle alternate profiles runtime_values = {} if pmts.selected["profile"] == URIRef("http://www.w3.org/ns/dx/conneg/altr-ext#alt-profile"): endpoint_uri = URIRef("https://prez.dev/endpoint/system/alt-profiles-listing") - # runtime_values["selectedClass"] = prof_and_mt_info.selected_class + # runtime_values["selectedClass"] = prof_and_mt_info.selected_class # runtime_values["object"] = uri query_constructor = PrezQueryConstructor( diff --git a/prez/services/query_generation/connegp.py b/prez/services/query_generation/connegp.py deleted file mode 100644 index 20ab5f97..00000000 --- a/prez/services/query_generation/connegp.py +++ /dev/null @@ -1,123 +0,0 @@ -import logging -from textwrap import dedent -from typing import List, Tuple - -from rdflib import URIRef, Namespace - -from prez.services.curie_functions import get_uri_for_curie_id - -log = logging.getLogger(__name__) - -ALTREXT = Namespace("http://www.w3.org/ns/dx/conneg/altr-ext#") -PREZ = Namespace("https://prez.dev/") - - -def select_profile_mediatype( - classes: List[URIRef], - requested_profile_uri: URIRef = None, - requested_profile_token: str = None, - requested_mediatypes: List[Tuple] = None, - listing: bool = False, -): - """ - Returns a SPARQL SELECT query which will determine the profile and mediatype to return based on user requests, - defaults, and the availability of these in profiles. - - NB: Most specific class refers to the rdfs:Class of an object which has the most specific rdfs:subClassOf links to - the base class delivered by that API endpoint. The base classes delivered by each API endpoint are: - - SpacePrez: - /s/catalogs -> prez:DatasetList - /s/catalogs/{ds_id} -> dcat:Dataset - /s/catalogs/{ds_id}/collections/{fc_id} -> geo:FeatureCollection - /s/catalogs/{ds_id}/collections -> prez:FeatureCollectionList - /s/catalogs/{ds_id}/collections/{fc_id}/features -> geo:Feature - - VocPrez: - /v/schemes -> skos:ConceptScheme - /v/collections -> skos:Collection - /v/schemes/{cs_id}/concepts -> skos:Concept - - CatPrez: - /c/catalogs -> dcat:Catalog - /c/catalogs/{cat_id}/datasets -> dcat:Dataset - - The following logic is used to determine the profile and mediatype to be returned: - - 1. If a profile and mediatype are requested, they are returned if a matching profile which has the requested - mediatype is found, otherwise the default profile for the most specific class is returned, with its default - mediatype. - 2. If a profile only is requested, if it can be found it is returned, otherwise the default profile for the most - specific class is returned. In both cases the default mediatype is returned. - 3. If a mediatype only is requested, the default profile for the most specific class is returned, and if the - requested mediatype is available for that profile, it is returned, otherwise the default mediatype for that profile - is returned. - 4. If neither a profile nor mediatype is requested, the default profile for the most specific class is returned, - with the default mediatype for that profile. - """ - if listing: - profile_class = PREZ.ListingProfile - else: - profile_class = PREZ.ObjectProfile - if requested_profile_token: - requested_profile_uri = get_uri_for_curie_id(requested_profile_token) - query = dedent( - f""" PREFIX altr-ext: - PREFIX dcat: - PREFIX dcterms: - PREFIX geo: - PREFIX prez: - PREFIX prof: - PREFIX rdfs: - PREFIX skos: - PREFIX sh: - - SELECT ?profile ?title ?class (count(?mid) as ?distance) ?req_profile ?def_profile ?format ?req_format ?def_format - - WHERE {{ - VALUES ?class {{{" ".join('<' + str(klass) + '>' for klass in classes)}}} - ?class rdfs:subClassOf* ?mid . - ?mid rdfs:subClassOf* ?base_class . - VALUES ?base_class {{ dcat:Dataset geo:FeatureCollection geo:Feature - skos:ConceptScheme skos:Concept skos:Collection - dcat:Catalog dcat:Resource prof:Profile prez:SPARQLQuery - prez:SearchResult prez:CQLObjectList prez:QueryablesList prez:Object }} - ?profile altr-ext:constrainsClass ?class ; - altr-ext:hasResourceFormat ?format ; - dcterms:title ?title .\ - {f'?profile a {profile_class.n3()} .'} - {f'BIND(?profile=<{requested_profile_uri}> as ?req_profile)' if requested_profile_uri else ''} - BIND(EXISTS {{ ?shape sh:targetClass ?class ; - altr-ext:hasDefaultProfile ?profile }} AS ?def_profile) - {generate_mediatype_if_statements(requested_mediatypes) if requested_mediatypes else ''} - BIND(EXISTS {{ ?profile altr-ext:hasDefaultResourceFormat ?format }} AS ?def_format) - }} - GROUP BY ?class ?profile ?req_profile ?def_profile ?format ?req_format ?def_format ?title - ORDER BY DESC(?req_profile) DESC(?distance) DESC(?def_profile) DESC(?req_format) DESC(?def_format)""" - ) - return query - - -def generate_mediatype_if_statements(requested_mediatypes: list): - """ - Generates a list of if statements which will be used to determine the mediatype to return based on user requests, - and the availability of these in profiles. - These are of the form: - BIND( - IF(?format="application/ld+json", "0.9", - IF(?format="text/html", "0.8", - IF(?format="image/apng", "0.7", ""))) AS ?req_format) - """ - # TODO ConnegP appears to return a tuple of q values and profiles for headers, and only profiles (no q values) if they - # are not specified in QSAs. - if not isinstance(next(iter(requested_mediatypes)), tuple): - requested_mediatypes = [(1, mt) for mt in requested_mediatypes] - - line_join = "," + "\n" - ifs = ( - f"BIND(\n" - f"""{line_join.join({chr(9) + 'IF(?format="' + tup[1] + '", "' + str(tup[0]) + '"' for tup in requested_mediatypes})}""" - f""", ""{')' * len(requested_mediatypes)}\n""" - f"\tAS ?req_format)" - ) - return ifs diff --git a/pyproject.toml b/pyproject.toml index 428aacc0..f0547c3e 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ python = "^3.11" uvicorn = "^0.27.1" httpx = "^0.26.0" rdflib = "^7.0.0" -connegp = { file = "connegp-0.1.6-py3-none-any.whl" } async-lru = "^2.0.4" geojson-rewind = "^1.0.3" toml = "^0.10.2" diff --git a/tests/data/profiles/ogc_records_profile.ttl b/tests/data/profiles/ogc_records_profile.ttl new file mode 100755 index 00000000..333ef557 --- /dev/null +++ b/tests/data/profiles/ogc_records_profile.ttl @@ -0,0 +1,106 @@ +PREFIX altr-ext: +PREFIX dcat: +PREFIX dcterms: +PREFIX geo: +PREFIX owl: +PREFIX prez: +PREFIX prof: +PREFIX prov: +PREFIX reg: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX skos: +PREFIX xsd: +PREFIX endpoint: +PREFIX shext: + + +prez:OGCRecordsProfile + a prof:Profile ; + dcterms:identifier "ogc"^^xsd:token ; + dcterms:description "A system profile for OGC Records conformant API" ; + dcterms:title "OGC Profile" ; + altr-ext:constrainsClass prez:CatPrez ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:hasNodeShape [ + a sh:NodeShape ; + sh:targetClass dcat:Catalog , skos:Concept , geo:Feature , geo:FeatureCollection , skos:Collection , prez:SearchResult , prez:CQLObjectList ; + altr-ext:hasDefaultProfile prez:OGCListingProfile + ] , [ + a sh:NodeShape ; + sh:targetClass skos:ConceptScheme ; + altr-ext:hasDefaultProfile prez:OGCSchemesListProfile + ] , [ + a sh:NodeShape ; + sh:targetClass dcat:Catalog , skos:ConceptScheme , skos:Concept , geo:Feature , geo:FeatureCollection , skos:Collection ; + altr-ext:hasDefaultProfile prez:OGCItemProfile + ] + . + +prez:OGCListingProfile + a prof:Profile , prez:ListingProfile , sh:NodeShape ; + dcterms:title "OGC Listing Profile" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:constrainsClass dcat:Catalog , skos:Collection , geo:Feature , geo:FeatureCollection , skos:Concept , + dcat:Resource , prof:Profile , prez:SearchResult , prez:CQLObjectList ; + sh:property [ sh:path rdf:type ] + . + +prez:OGCSchemesListProfile + a prof:Profile , prez:ListingProfile , sh:NodeShape ; + dcterms:title "OGC Concept Scheme Listing Profile" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:constrainsClass skos:ConceptScheme ; + sh:property [ + sh:minCount 0 ; + sh:path ( + sh:union ( + dcterms:publisher + reg:status + ( prov:qualifiedDerivation prov:hadRole ) + ( prov:qualifiedDerivation prov:entity ) + ) + ) + ] + . + +prez:OGCItemProfile + a prof:Profile , prez:ObjectProfile , sh:NodeShape ; + dcterms:title "OGC Object Profile" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + sh:property [ + sh:path shext:allPredicateValues ; + ] , + [ + sh:minCount 0 ; + sh:path [ sh:inversePath dcterms:hasPart ] ; + ] ; + shext:bnode-depth 2 ; + altr-ext:constrainsClass dcat:Catalog , + dcat:Resource , + skos:ConceptScheme, + skos:Collection , + skos:Concept , + geo:FeatureCollection , + geo:Feature , + prof:Profile ; + . diff --git a/tests/data/profiles/spaceprez_default_profiles.ttl b/tests/data/profiles/spaceprez_default_profiles.ttl new file mode 100755 index 00000000..9e6a3c8a --- /dev/null +++ b/tests/data/profiles/spaceprez_default_profiles.ttl @@ -0,0 +1,138 @@ +PREFIX altr-ext: +PREFIX dcat: +PREFIX dcterms: +PREFIX geo: +PREFIX owl: +PREFIX prez: +PREFIX prof: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX skos: +PREFIX xsd: +PREFIX shext: + + +prez:SpacePrezProfile + a prof:Profile ; + dcterms:identifier "spaceprez"^^xsd:token ; + dcterms:description "A system profile for SpacePrez" ; + skos:prefLabel "SpacePrez profile" ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:constrainsClass prez:SpacePrez ; + altr-ext:hasNodeShape [ + a sh:NodeShape ; + sh:targetClass dcat:Dataset ; + altr-ext:hasDefaultProfile + ] , [ + a sh:NodeShape ; + sh:targetClass geo:FeatureCollection ; + altr-ext:hasDefaultProfile prez:FeatureCollectionProfile + ] , [ + a sh:NodeShape ; + sh:targetClass geo:Feature ; + altr-ext:hasDefaultProfile prez:FeatureProfile + ] , [ + a sh:NodeShape ; + sh:targetClass prez:DatasetList ; + altr-ext:hasDefaultProfile + ] , [ + a sh:NodeShape ; + sh:targetClass prez:FeatureCollectionList ; + altr-ext:hasDefaultProfile prez:GeoListingProfile + ] , [ + a sh:NodeShape ; + sh:targetClass prez:FeatureList ; + altr-ext:hasDefaultProfile prez:GeoListingProfile + ] +. + +prez:FeatureCollectionProfile a prof:Profile ; + dcterms:description "A profile for GeoSPARQL FeatureCollections" ; + dcterms:identifier "geofc"^^xsd:token ; + dcterms:title "Feature Collection Profile" ; + altr-ext:constrainsClass geo:FeatureCollection ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + sh:targetClass geo:FeatureCollection ; + sh:property + [ + sh:maxCount 0 ; + sh:path rdfs:member ; + ] , + [ + sh:path [ sh:inversePath rdfs:member ] ; + ] ; + shext:bnode-depth 2 ; +. + +prez:FeatureProfile a prof:Profile ; + dcterms:description "A profile for GeoSPARQL Features" ; + dcterms:identifier "geofeat"^^xsd:token ; + dcterms:title "Feature Profile" ; + altr-ext:constrainsClass geo:Feature ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + sh:targetClass geo:Feature ; + sh:property + [ + sh:path [ sh:inversePath rdfs:member ] ; + ] , + [ + sh:path shext:allPredicateValues ; + ] ; + shext:bnode-depth 2 ; +. + + +prez:GeoListingProfile a prof:Profile ; + dcterms:description "A profile for listing GeoSPARQL Features and FeatureCollections" ; + dcterms:identifier "geolisting"^^xsd:token ; + dcterms:title "Geo Listing Profile" ; + altr-ext:constrainsClass prez:FeatureCollectionList , prez:FeatureList ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + sh:property + [ + sh:path [ sh:inversePath rdfs:member ] ; + ] +. + + + a prof:Profile , prez:SpacePrezProfile ; + dcterms:description "Dataset Catalog Vocabulary (DCAT) is a W3C-authored RDF vocabulary designed to facilitate interoperability between data catalogs" ; + dcterms:identifier "dcat"^^xsd:token ; + dcterms:title "DCAT" ; + altr-ext:constrainsClass + dcat:Catalog , + dcat:Dataset ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + altr-ext:hasResourceFormat + "application/ld+json" , + "application/anot+ld+json" , + "application/rdf+xml" , + "text/anot+turtle" , + "text/turtle" ; + altr-ext:hasDefaultResourceFormat "text/anot+turtle" ; + sh:property [ + sh:path shext:allPredicateValues ; + ] ; + shext:bnode-depth 2 ; + altr-ext:constrainsClass dcat:Catalog , dcat:Dataset ; +. + diff --git a/tests/test_connegp.py b/tests/test_connegp.py index 5926f6fa..d09316e6 100644 --- a/tests/test_connegp.py +++ b/tests/test_connegp.py @@ -2,50 +2,150 @@ import pytest from pyoxigraph import Store -from rdflib import Graph, URIRef +from pyoxigraph.pyoxigraph import Store +from rdflib import URIRef +from prez.app import app +from prez.dependencies import get_repo +from prez.repositories import PyoxigraphRepo, Repo from prez.services.connegp_service import NegotiatedPMTs -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def test_store() -> Store: store = Store() - profiles = Path(__file__).parent / "data" / "profiles" / "remote_profile.ttl" - store.load(profiles, mime_type="text/turtle") + file = Path(__file__).parent / "data/profiles/ogc_records_profile.ttl" + store.load(file.read_bytes(), "text/turtle") + file = Path(__file__).parent / "data/profiles/spaceprez_default_profiles.ttl" + store.load(file.read_bytes(), "text/turtle") return store -@pytest.fixture(scope="module") -def test_prefix_graph(): - graph = Graph(bind_namespaces="rdflib") - graph.bind("ex", "https://example.com/") - return graph +@pytest.fixture(scope="session") +def test_repo(test_store: Store) -> Repo: + return PyoxigraphRepo(test_store) @pytest.mark.parametrize( - "headers, params, classes, expected_selected", + "headers, params, classes, listing, expected_selected", [ [ - {"Accept-Profile": ", ;q=0.9"}, - {"_media": "text/anot+turtle, text/turtle;q=0.9"}, - [URIRef("")], + {}, # Test that profiles/mediatypes resolve to their defaults if not requested (object endpoint) + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, { - "profile": "", - "title": "", - "mediatype": "", - "class": "" + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "text/anot+turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {}, # Test that profiles/mediatypes resolve to their defaults if not requested (listing endpoint) + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + True, + { + "profile": URIRef("https://prez.dev/OGCListingProfile"), + "title": "OGC Listing Profile", + "mediatype": "text/anot+turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept": "application/ld+json"}, # Test that a valid mediatype is resolved + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "application/ld+json", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept": "application/ld+json;q=0.7,text/turtle"}, # Test resolution of multiple mediatypes + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "text/turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {}, + {"_media": "application/ld+json"}, # Test mediatype resolution as QSA + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "application/ld+json", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept": "text/turtle"}, + {"_media": "application/ld+json"}, # Test QSA mediatype is preferred + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "application/ld+json", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept-profile": "oogabooga"}, # Test that invalid profile is ignored + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "text/anot+turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept": "oogabooga"}, # Test that invalid mediatype is ignored + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + False, + { + "profile": URIRef("https://prez.dev/OGCItemProfile"), + "title": "OGC Object Profile", + "mediatype": "text/anot+turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" + } + ], + [ + {"accept-profile": ""}, # Test that a valid profile is resolved + {}, + [URIRef("http://www.w3.org/ns/dcat#Catalog")], + True, + { + "profile": URIRef("https://www.w3.org/TR/vocab-dcat/"), + "title": "DCAT", + "mediatype": "text/anot+turtle", + "class": "http://www.w3.org/ns/dcat#Catalog" } ], ] ) -def test_connegp(headers, params, classes, expected_selected, test_store, test_prefix_graph): - pmts = NegotiatedPMTs(**{ - "headers": headers, - "params": params, - "classes": classes, - "_system_store": test_store, - "_prefix_graph": test_prefix_graph - }) - pmts.setup() +@pytest.mark.asyncio +async def test_connegp(headers, params, classes, listing, expected_selected, test_repo): + def override_get_repo(): + return test_repo + app.dependency_overrides[get_repo] = override_get_repo + pmts = NegotiatedPMTs(headers=headers, params=params, classes=classes, listing=listing, system_repo=test_repo) + success = await pmts.setup() + assert success assert pmts.selected == expected_selected