diff --git a/poetry.lock b/poetry.lock index d477f5f041..2d5fc1b581 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,20 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = true +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "appdirs" version = "1.4.4" @@ -763,6 +777,26 @@ pygments = ">=2.7" sphinx = ">=5.0,<7.0" sphinx-basic-ng = "*" +[[package]] +name = "graphql-query" +version = "1.2.0" +description = "Complete GraphQL query string generation for python." +optional = true +python-versions = ">=3.7" +files = [ + {file = "graphql_query-1.2.0-py3-none-any.whl", hash = "sha256:39a69e29b67c0e962898149ad101cc00cb9ce4fe3c43f894041c8d0cb6d4d727"}, + {file = "graphql_query-1.2.0.tar.gz", hash = "sha256:80e54fea675567253ed289c7bcc9d3b9f6bfd51dcc6ec4a64478c1444606db47"}, +] + +[package.dependencies] +jinja2 = ">=3.1,<3.2" +pydantic = ">=2" + +[package.extras] +dev = ["black", "mypy", "pylint-pydantic", "ruff", "wheel"] +docs = ["sphinx", "sphinx-argparse", "sphinx-rtd-theme", "sphinxcontrib-github"] +test = ["pytest", "pytest-cov", "pytest-mock"] + [[package]] name = "greenlet" version = "3.0.3" @@ -1726,6 +1760,143 @@ files = [ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +[[package]] +name = "pydantic" +version = "2.5.3" +description = "Data validation using Python type hints" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"}, + {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +importlib-metadata = {version = "*", markers = "python_version == \"3.7\""} +pydantic-core = "2.14.6" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.14.6" +description = "" +optional = true +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"}, + {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"}, + {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"}, + {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"}, + {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"}, + {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"}, + {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"}, + {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"}, + {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"}, + {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"}, + {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"}, + {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"}, + {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"}, + {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"}, + {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"}, + {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"}, + {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"}, + {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"}, + {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"}, + {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"}, + {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"}, + {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"}, + {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"}, + {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"}, + {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"}, + {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"}, + {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"}, + {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"}, + {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"}, + {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"}, + {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"}, + {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"}, + {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"}, + {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"}, + {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + [[package]] name = "pygments" version = "2.17.2" @@ -3063,6 +3234,7 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [extras] docs = ["furo", "myst-parser", "sphinx", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinx-reredirects"] +graphql = ["graphql-query"] parquet = ["numpy", "numpy", "numpy", "pyarrow", "pyarrow"] s3 = ["fs-s3fs"] testing = ["pytest", "pytest-durations"] @@ -3070,4 +3242,4 @@ testing = ["pytest", "pytest-durations"] [metadata] lock-version = "2.0" python-versions = ">=3.7.1" -content-hash = "cbbf5badbed96af3d15f4c81e11503578badffc20dbbcc09eaeedcaff2670cf6" +content-hash = "ed31ff6345fa375d0c6af0c314db9c10064ceab97833ba08f55b7f7d035cbd41" diff --git a/pyproject.toml b/pyproject.toml index aed0aa035e..6528852334 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,9 @@ pyarrow = [ pytest = {version=">=7.2.1", optional = true} pytest-durations = {version = ">=1.2.0", optional = true} +# GraphQL +graphql-query = {version = ">=1.2.0", optional = true} + [tool.poetry.extras] docs = [ "sphinx", @@ -112,6 +115,7 @@ docs = [ "sphinx-notfound-page", "sphinx-reredirects", ] +graphql = ["graphql-query"] s3 = ["fs-s3fs"] testing = [ "pytest", diff --git a/samples/sample_tap_countries/countries_streams.py b/samples/sample_tap_countries/countries_streams.py index 3b68a55717..16c369b26b 100644 --- a/samples/sample_tap_countries/countries_streams.py +++ b/samples/sample_tap_countries/countries_streams.py @@ -9,14 +9,36 @@ from __future__ import annotations import abc +import sys +import typing as t + +from graphql_query import Field, Operation, Query from singer_sdk import typing as th +from singer_sdk.helpers._catalog import FieldTree, selection_to_tree from singer_sdk.helpers._compat import importlib_resources from singer_sdk.streams.graphql import GraphQLStream +if t.TYPE_CHECKING: + from singer_sdk._singerlib.catalog import SelectionMask + SCHEMAS_DIR = importlib_resources.files(__package__) / "schemas" +def _tree_to_fields(tree: FieldTree) -> list[Field]: + """Convert a tree to a list of GraphQL fields.""" + return [ + Field(name=name, fields=_tree_to_fields(subtree)) + for name, subtree in tree.items() + ] + + +def selection_to_fields(selection: SelectionMask) -> list[Field]: + """Convert a selection mask to a GraphQL query.""" + tree = selection_to_tree(selection) + return _tree_to_fields(tree) + + class CountriesAPIStream(GraphQLStream, metaclass=abc.ABCMeta): """Sample tap test for countries. @@ -31,25 +53,25 @@ class CountriesStream(CountriesAPIStream): name = "countries" primary_keys = ("code",) - query = """ - countries { - code - name - native - phone - continent { - code - name - } - capital - currency - languages { - code - name - } - emoji - } - """ + # query = """ + # countries { + # code + # name + # native + # phone + # continent { + # code + # name + # } + # capital + # currency + # languages { + # code + # name + # } + # emoji + # } + # """ schema = th.PropertiesList( th.Property("code", th.StringType), th.Property("name", th.StringType), @@ -76,6 +98,44 @@ class CountriesStream(CountriesAPIStream): ), ).to_dict() + @property + def query(self) -> str: + """Return the GraphQL query string.""" + countries = Query( + name="countries", + fields=selection_to_fields(self.mask), + # fields=[ + # Field(name="code"), + # Field(name="name"), + # Field(name="native"), + # Field(name="phone"), + # Field(name="capital"), + # Field(name="currency"), + # Field(name="emoji"), + # Field( + # name="continent", + # fields=[ + # Field(name="code"), + # Field(name="name"), + # ], + # ), + # Field( + # name="languages", + # fields=[ + # # Field(name="code"), + # # Field(name="name"), + # ], + # ), + # ], + ) + print(countries, file=sys.stderr) + return Operation( + type="query", + queries=[ + countries, + ], + ).render() + class ContinentsStream(CountriesAPIStream): """Continents stream from the Countries API.""" diff --git a/singer_sdk/_singerlib/catalog.py b/singer_sdk/_singerlib/catalog.py index 6a332b52ea..79c9bfc614 100644 --- a/singer_sdk/_singerlib/catalog.py +++ b/singer_sdk/_singerlib/catalog.py @@ -2,6 +2,7 @@ import enum import logging +import sys import typing as t from dataclasses import dataclass, fields @@ -31,7 +32,7 @@ def __missing__(self, breadcrumb: Breadcrumb) -> bool: Returns: True if the breadcrumb is selected, False otherwise. """ - return self[breadcrumb[:-2]] if len(breadcrumb) >= 2 else True # noqa: PLR2004 + return self[breadcrumb[:-2]] if len(breadcrumb) >= 2 else True @dataclass @@ -153,6 +154,65 @@ def root(self) -> StreamMetadata: """ return self[()] # type: ignore[return-value] + @staticmethod + def _get_object_fields_metadata( + properties: dict[str, dict[str, t.Any]], + breadcrumb: Breadcrumb, + ) -> t.Generator[tuple[Breadcrumb, Metadata], None, None]: + """Get metadata for nested fields in a schema. + + Args: + schema: Schema. + breadcrumb: Breadcrumb to check. + metadata: Metadata object. + + Returns: + Metadata mapping. + """ + for field_name, field_schema in properties.items(): + field_breadcrumb = (*breadcrumb, "properties", field_name) + print(field_breadcrumb, file=sys.stderr) + yield ( + field_breadcrumb, + Metadata(inclusion=Metadata.InclusionType.AVAILABLE), + ) + if "object" in field_schema.get("type"): + yield from MetadataMapping._get_object_fields_metadata( + field_schema.get("properties", {}), + field_breadcrumb, + ) + if "array" in field_schema.get("type"): + yield from MetadataMapping._get_array_fields_metadata( + field_schema, + field_breadcrumb, + ) + + @staticmethod + def _get_array_fields_metadata( + field_schema: dict[str, t.Any], + breadcrumb: Breadcrumb, + ) -> t.Generator[tuple[Breadcrumb, Metadata], None, None]: + """Get metadata for nested fields in a schema. + + Args: + schema: Schema. + breadcrumb: Breadcrumb to check. + metadata: Metadata object. + + Returns: + Metadata mapping. + """ + if "object" in field_schema.get("type"): + yield from MetadataMapping._get_object_fields_metadata( + field_schema.get("properties", {}), + breadcrumb, + ) + if "array" in field_schema.get("type"): + yield from MetadataMapping._get_array_fields_metadata( + field_schema.get("items", {}), + breadcrumb, + ) + @classmethod def get_standard_metadata( cls: type[MetadataMapping], @@ -191,7 +251,10 @@ def get_standard_metadata( if schema_name: root.schema_name = schema_name - for field_name in schema.get("properties", {}): + properties = schema.get("properties", {}) + + for field_name, field_schema in properties.items(): + breadcrumb = ("properties", field_name) if ( key_properties and field_name in key_properties @@ -201,7 +264,24 @@ def get_standard_metadata( else: entry = Metadata(inclusion=Metadata.InclusionType.AVAILABLE) - mapping[("properties", field_name)] = entry + mapping[breadcrumb] = entry + + print(breadcrumb, field_schema, file=sys.stderr) + if "object" in field_schema.get("type", []): + mapping.update( + cls._get_object_fields_metadata( + field_schema.get("properties", {}), + breadcrumb, + ), + ) + + if "array" in field_schema.get("type", []): + mapping.update( + cls._get_array_fields_metadata( + field_schema.get("items", {}), + breadcrumb, + ), + ) mapping[()] = root diff --git a/singer_sdk/helpers/_catalog.py b/singer_sdk/helpers/_catalog.py index 49ea2f1cc6..cefd4481d6 100644 --- a/singer_sdk/helpers/_catalog.py +++ b/singer_sdk/helpers/_catalog.py @@ -12,9 +12,13 @@ if t.TYPE_CHECKING: from logging import Logger + from typing_extensions import TypeAlias + from singer_sdk._singerlib import Catalog, SelectionMask + _MAX_LRU_CACHE = 500 +FieldTree: TypeAlias = t.Dict[str, "FieldTree"] @cached(max_size=_MAX_LRU_CACHE) @@ -142,3 +146,39 @@ def set_catalog_stream_selected( md_entry = catalog_entry.metadata[breadcrumb] md_entry.selected = selected + + +def selection_to_tree(selection: SelectionMask) -> FieldTree: + """Convert a SelectionMask to a tree consisting of selected fields. + + Args: + selection: Selection mask dictionary. + + Returns: + A dictionary tree with the selected fields. + + >>> selection = { + ... ("properties", "code",): True, + ... ("properties", "name",): True, + ... ("properties", "emoji",): True, + ... ("properties", "languages",): False, + ... ("properties", "languages", "properties", "code"): True, + ... ("properties", "languages", "properties", "name"): False, + ... ("properties", "continent",): True, + ... ("properties", "continent", "properties", "code"): True, + ... ("properties", "continent", "properties", "name"): False, + ... } + >>> selection_to_tree(selection) + {'code': {}, 'name': {}, 'emoji': {}, 'languages': {'code': {}}, 'continent': {'code': {}}} + """ # noqa: E501 + fields = {} + for crumb, selected in selection.items(): + if not crumb: + continue + if selected: + top = crumb[1] + if top not in fields: + fields[top] = {} + fields[top] = selection_to_tree({crumb[2:]: selected}) + + return fields diff --git a/singer_sdk/helpers/_typing.py b/singer_sdk/helpers/_typing.py index 8b937d9739..096a121a4f 100644 --- a/singer_sdk/helpers/_typing.py +++ b/singer_sdk/helpers/_typing.py @@ -7,7 +7,7 @@ import logging import typing as t from enum import Enum -from functools import lru_cache +from functools import lru_cache, singledispatch import pendulum @@ -469,26 +469,41 @@ def _conform_record_data_types( # noqa: PLR0912 return output_object, unmapped_properties -def _conform_primitive_property( # noqa: PLR0911 +def _conform_primitive_property( elem: t.Any, # noqa: ANN401 property_schema: dict, ) -> t.Any: # noqa: ANN401 """Converts a primitive (i.e. not object or array) to a json compatible type.""" - if isinstance(elem, (datetime.datetime, pendulum.DateTime)): - return to_json_compatible(elem) - if isinstance(elem, datetime.date): - return f"{elem.isoformat()}T00:00:00+00:00" - if isinstance(elem, datetime.timedelta): - epoch = datetime.datetime.fromtimestamp(0, UTC) - timedelta_from_epoch = epoch + elem - if timedelta_from_epoch.tzinfo is None: - timedelta_from_epoch = timedelta_from_epoch.replace(tzinfo=UTC) - return timedelta_from_epoch.isoformat() - if isinstance(elem, datetime.time): - return str(elem) - if isinstance(elem, bytes): - # for BIT value, treat 0 as False and anything else as True - return elem != b"\x00" if is_boolean_type(property_schema) else elem.hex() if is_boolean_type(property_schema): return None if elem is None else elem != 0 return elem + + +@_conform_primitive_property.register +def _(elem: datetime.datetime | pendulum.DateTime, _property_schema: dict) -> str: + return to_json_compatible(elem) + + +@_conform_primitive_property.register +def _(elem: datetime.date, _property_schema: dict) -> str: + return f"{elem.isoformat()}T00:00:00+00:00" + + +@_conform_primitive_property.register +def _(elem: datetime.timedelta, _property_schema: dict) -> str: + epoch = datetime.datetime.fromtimestamp(0, UTC) + timedelta_from_epoch = epoch + elem + if timedelta_from_epoch.tzinfo is None: + timedelta_from_epoch = timedelta_from_epoch.replace(tzinfo=UTC) + return timedelta_from_epoch.isoformat() + + +@_conform_primitive_property.register +def _(elem: datetime.time, _property_schema: dict) -> str: + return str(elem) + + +@_conform_primitive_property.register +def _(elem: bytes, property_schema: dict) -> bool | str: + # for BIT value, treat 0 as False and anything else as True + return elem != b"\x00" if is_boolean_type(property_schema) else elem.hex() diff --git a/singer_sdk/streams/core.py b/singer_sdk/streams/core.py index 567d7284d1..961a939a14 100644 --- a/singer_sdk/streams/core.py +++ b/singer_sdk/streams/core.py @@ -7,6 +7,8 @@ import datetime import json import typing as t +from collections.abc import Mapping # noqa: TCH003 +from functools import singledispatchmethod from os import PathLike from pathlib import Path from types import MappingProxyType @@ -140,25 +142,13 @@ def __init__( self._schema_filepath: Path | Traversable | None = None self._metadata: singer.MetadataMapping | None = None self._mask: singer.SelectionMask | None = None - self._schema: dict + self._schema: Mapping self._is_state_flushed: bool = True self._last_emitted_state: dict | None = None self._sync_costs: dict[str, int] = {} self.child_streams: list[Stream] = [] if schema: - if isinstance(schema, (PathLike, str)): - if not Path(schema).is_file(): - msg = f"Could not find schema file '{self.schema_filepath}'." - raise FileNotFoundError(msg) - - self._schema_filepath = Path(schema) - elif isinstance(schema, dict): - self._schema = schema - elif isinstance(schema, singer.Schema): - self._schema = schema.to_dict() - else: - msg = f"Unexpected type {type(schema).__name__} for arg 'schema'." - raise ValueError(msg) + self.register_schema(schema) if self.schema_filepath: self._schema = json.loads(self.schema_filepath.read_text()) @@ -170,6 +160,31 @@ def __init__( ) raise ValueError(msg) + @singledispatchmethod + def register_schema(self, schema: PathLike[str] | str) -> None: + """Register a schema. + + Args: + schema: TODO + """ + if not isinstance(schema, (PathLike, str)): + msg = f"Unexpected type {type(schema).__name__} for arg 'schema'." + raise ValueError(msg) + + if not Path(schema).is_file(): + msg = f"Could not find schema file '{self.schema_filepath}'." + raise FileNotFoundError(msg) + + self._schema_filepath = Path(schema) + + @register_schema.register + def _(self, schema: Mapping) -> None: + self._schema = schema + + @register_schema.register + def _(self, schema: singer.Schema) -> None: + self._schema = schema.to_dict() + @property def stream_maps(self) -> list[StreamMap]: """Get stream transformation maps. diff --git a/singer_sdk/streams/graphql.py b/singer_sdk/streams/graphql.py index fde4f99b94..764fdf2608 100644 --- a/singer_sdk/streams/graphql.py +++ b/singer_sdk/streams/graphql.py @@ -66,6 +66,7 @@ def prepare_request_payload( """ params = self.get_url_params(context, next_page_token) query = self.query + self.logger.info("Querying %s with query %s", self.name, query) if query is None: msg = "Graphql `query` property not set."