diff --git a/CHANGELOG.md b/CHANGELOG.md index abae5de..e77f04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and it's only available in `kfp-kubernetes` extension package didn't alter the remote pipeline execution, and only escaped the local Python processs. The timeout funcionality will be added later on, with the proper remote pipeline execution handling, and possibly per-task timeout enabled by [the new kfp feature](https://github.com/kubeflow/pipelines/pull/10481). - Assign pipelines to Vertex AI experiments +- Migrated `pydantic` library to v2 ## [0.11.1] - 2024-07-01 diff --git a/kedro_vertexai/config.py b/kedro_vertexai/config.py index bc61189..93ed7c1 100644 --- a/kedro_vertexai/config.py +++ b/kedro_vertexai/config.py @@ -4,7 +4,7 @@ from inspect import signature from typing import Dict, List, Optional -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from pydantic.networks import IPvAnyAddress DEFAULT_CONFIG_TEMPLATE = """ @@ -166,13 +166,13 @@ class GroupingConfig(BaseModel): cls: str = "kedro_vertexai.grouping.IdentityNodeGrouper" params: Optional[dict] = {} - @validator("cls") + @field_validator("cls") def class_valid(cls, v, values, **kwargs): try: grouper_class = dynamic_load_class(v) class_sig = signature(grouper_class) - if "params" in values: - class_sig.bind(None, **values["params"]) + if "params" in values.data: + class_sig.bind(None, **values.data["params"]) else: class_sig.bind(None) except: # noqa: E722 @@ -196,13 +196,13 @@ class HostAliasConfig(BaseModel): class ResourcesConfig(BaseModel): - cpu: Optional[str] - gpu: Optional[str] - memory: Optional[str] + cpu: Optional[str] = None + gpu: Optional[str] = None + memory: Optional[str] = None class NetworkConfig(BaseModel): - vpc: Optional[str] + vpc: Optional[str] = None host_aliases: Optional[List[HostAliasConfig]] = [] @@ -212,7 +212,7 @@ class DynamicConfigProviderConfig(BaseModel): class MLFlowVertexAIConfig(BaseModel): - request_header_provider_params: Optional[Dict[str, str]] + request_header_provider_params: Optional[Dict[str, str]] = None class ScheduleConfig(BaseModel): @@ -227,13 +227,13 @@ class ScheduleConfig(BaseModel): class RunConfig(BaseModel): image: str - root: Optional[str] - description: Optional[str] + root: Optional[str] = None + description: Optional[str] = None experiment_name: str experiment_description: Optional[str] = None - scheduled_run_name: Optional[str] + scheduled_run_name: Optional[str] = None grouping: Optional[GroupingConfig] = GroupingConfig() - service_account: Optional[str] + service_account: Optional[str] = None network: Optional[NetworkConfig] = NetworkConfig() ttl: int = 3600 * 24 * 7 resources: Optional[Dict[str, ResourcesConfig]] = dict( diff --git a/kedro_vertexai/context_helper.py b/kedro_vertexai/context_helper.py index 39196a4..688a625 100644 --- a/kedro_vertexai/context_helper.py +++ b/kedro_vertexai/context_helper.py @@ -82,7 +82,7 @@ def config(self) -> PluginConfig: "Missing vertexai.yml files in configuration. " "Make sure that you configure your project first" ) - return PluginConfig.parse_obj(vertex_conf) + return PluginConfig.model_validate(vertex_conf) @cached_property def vertexai_client(self) -> VertexAIPipelinesClient: diff --git a/poetry.lock b/poetry.lock index cc0eabc..8d3fd1c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,6 +145,20 @@ files = [ [package.extras] dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "antlr4-python3-runtime" version = "4.9.3" @@ -2930,55 +2944,123 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pydantic" -version = "1.10.14" -description = "Data validation and settings management using python type hints" +version = "2.8.2" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pygments" @@ -4148,4 +4230,4 @@ mlflow = ["kedro-mlflow"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.11" -content-hash = "4d35c9a848ee572627adaf86da557b493cc0a894dbc81dd2bcd64d52ced608d3" +content-hash = "de7086311b6b855c6b96b2d0942fde5fbed01925812c9045856a86ab7197777d" diff --git a/pyproject.toml b/pyproject.toml index 2de031c..531d567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ tabulate = ">=0.8.7" semver = ">=2.10,<4.0.0" toposort = ">1.0,<2.0" pyarrow = ">=14.0.1" # Stating explicitly for sub-dependency due to critical vulnerability -pydantic = ">=1.9.0,<2.0.0" # so far blocked by kedro-mlflow at 0.11.10 & kfp <2.0 +pydantic = ">=2,<3" google-auth = "<3" google-cloud-scheduler = ">=2.3.2" google-cloud-iam = "<3" diff --git a/tests/test_cli.py b/tests/test_cli.py index 53ba7e6..d8491ca 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,7 @@ import os import unittest from collections import namedtuple +from copy import deepcopy from itertools import product from pathlib import Path from tempfile import TemporaryDirectory @@ -41,7 +42,7 @@ def test_list_pipelines(self): def test_run_once(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -67,7 +68,7 @@ def test_run_once(self): def test_run_once_with_wait(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -109,7 +110,7 @@ def test_docker_push(self): def test_run_once_auto_build(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -146,7 +147,7 @@ def test_run_once_auto_build(self): @patch("webbrowser.open_new_tab") def test_ui(self, open_new_tab): context_helper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -160,7 +161,7 @@ def test_ui(self, open_new_tab): def test_compile(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -177,7 +178,7 @@ def test_compile(self): def test_store_params_empty(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -215,7 +216,7 @@ def test_store_params_exiting_config_yaml(self): Covers the case when there is an exiting config.yaml in the pwd """ context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) config = dict(context_helper=context_helper) runner = CliRunner() @@ -260,7 +261,7 @@ def test_store_params_exiting_config_yaml(self): def test_schedule(self): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) mock_schedule = MagicMock() context_helper.config.run_config.schedules = { @@ -312,7 +313,7 @@ def test_schedule(self): @patch.object(Path, "cwd") def test_init(self, cwd): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) context_helper.context.project_name = "Test Project" context_helper.context.project_path.name = "test_project_path" config = dict(context_helper=context_helper) @@ -333,7 +334,7 @@ def test_init(self, cwd): @patch.object(Path, "cwd") def test_init_with_github_actions(self, cwd): context_helper: ContextHelper = MagicMock(ContextHelper) - context_helper.config = test_config + context_helper.config = deepcopy(test_config) context_helper.context.project_name = "Test Project" context_helper.context.project_path.name = "test_project_path" config = dict(context_helper=context_helper) diff --git a/tests/test_config.py b/tests/test_config.py index 5059bb7..d544848 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,7 +18,9 @@ description: "My awesome pipeline" service_account: test@pipelines.gserviceaccount.com grouping: - cls: kedro_vertexai.grouping.IdentityNodeGrouper + cls: "kedro_vertexai.grouping.IdentityNodeGrouper" + params: + tag_prefix: "group." ttl: 300 network: vpc: my-vpc @@ -49,14 +51,13 @@ class TestPluginConfig(unittest.TestCase): def test_grouping_config(self): - cfg = PluginConfig.parse_obj(yaml.safe_load(CONFIG_MINIMAL)) + cfg = PluginConfig.model_validate(yaml.safe_load(CONFIG_MINIMAL)) assert cfg.run_config.grouping is not None assert ( cfg.run_config.grouping.cls == "kedro_vertexai.grouping.IdentityNodeGrouper" ) c_obj = dynamic_init_class(cfg.run_config.grouping.cls, None) assert isinstance(c_obj, IdentityNodeGrouper) - cfg_tag_group = """ project_id: some-project region: some-region @@ -64,11 +65,11 @@ def test_grouping_config(self): image: test experiment_name: test grouping: - cls: "kedro_vertexai.grouping.TagNodeGrouper" + cls: kedro_vertexai.grouping.TagNodeGrouper params: tag_prefix: "group." """ - cfg = PluginConfig.parse_obj(yaml.safe_load(cfg_tag_group)) + cfg = PluginConfig.model_validate(yaml.safe_load(cfg_tag_group)) assert cfg.run_config.grouping is not None c_obj = dynamic_init_class( cfg.run_config.grouping.cls, None, **cfg.run_config.grouping.params @@ -89,16 +90,18 @@ def test_grouping_config_error(self, log_error): params: foo: "bar:" """ - cfg = PluginConfig.parse_obj(yaml.safe_load(cfg_tag_group)) + cfg = yaml.safe_load(cfg_tag_group) c = dynamic_init_class( - cfg.run_config.grouping.cls, None, **cfg.run_config.grouping.params + cfg["run_config"]["grouping"]["cls"], + None, + **cfg["run_config"]["grouping"]["params"] ) assert c is None log_error.assert_called_once() def test_plugin_config(self): obj = yaml.safe_load(CONFIG_FULL) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.image == "gcr.io/project-image/test" assert cfg.run_config.experiment_name == "Test Experiment" assert cfg.run_config.experiment_description == "Test Experiment Description." @@ -116,16 +119,16 @@ def test_plugin_config(self): assert cfg.run_config.ttl == 300 def test_defaults(self): - cfg = PluginConfig.parse_obj(yaml.safe_load(CONFIG_MINIMAL)) + cfg = PluginConfig.model_validate(yaml.safe_load(CONFIG_MINIMAL)) assert cfg.run_config.description is None assert cfg.run_config.ttl == 3600 * 24 * 7 def test_missing_required_config(self): with self.assertRaises(ValidationError): - PluginConfig.parse_obj({}) + PluginConfig.model_validate({}) def test_resources_default_only(self): - cfg = PluginConfig.parse_obj(yaml.safe_load(CONFIG_MINIMAL)) + cfg = PluginConfig.model_validate(yaml.safe_load(CONFIG_MINIMAL)) assert cfg.run_config.resources_for("node2") == { "cpu": "500m", "gpu": None, @@ -138,7 +141,7 @@ def test_resources_default_only(self): } def test_node_selectors_default_only(self): - cfg = PluginConfig.parse_obj(yaml.safe_load(CONFIG_MINIMAL)) + cfg = PluginConfig.model_validate(yaml.safe_load(CONFIG_MINIMAL)) assert cfg.run_config.node_selectors_for("node2") == {} assert cfg.run_config.node_selectors_for("node3") == {} @@ -147,7 +150,7 @@ def test_resources_no_default(self): obj["run_config"].update( {"resources": {"__default__": {"cpu": None, "memory": None}}} ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.resources_for("node2") == { "cpu": None, "gpu": None, @@ -164,7 +167,7 @@ def test_resources_default_and_node_specific(self): } } ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.resources_for("node2") == { "cpu": "100m", "gpu": None, @@ -186,7 +189,7 @@ def test_resources_default_and_tag_specific(self): } } ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.resources_for("node2", {"tag1"}) == { "cpu": "100m", "gpu": "2", @@ -209,7 +212,7 @@ def test_resources_node_and_tag_specific(self): } } ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.resources_for("node2", {"tag1"}) == { "cpu": "300m", "gpu": "2", @@ -226,7 +229,7 @@ def test_node_selectors_node_and_tag_specific(self): } } ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert cfg.run_config.node_selectors_for("node2", {"tag1"}) == { "cloud.google.com/gke-accelerator": "NVIDIA_TESLA_K80", } @@ -246,7 +249,7 @@ def test_parse_network_config(self): } } ) - cfg = PluginConfig.parse_obj(obj) + cfg = PluginConfig.model_validate(obj) assert ( cfg.run_config.network.vpc == "projects/some-project-id/global/networks/some-vpc-name" @@ -255,7 +258,7 @@ def test_parse_network_config(self): assert "mlflow.internal" in cfg.run_config.network.host_aliases[0].hostnames def test_accept_default_vertex_ai_networking_config(self): - cfg = PluginConfig.parse_obj(yaml.safe_load(CONFIG_MINIMAL)) + cfg = PluginConfig.model_validate(yaml.safe_load(CONFIG_MINIMAL)) assert cfg.run_config.network.vpc is None assert cfg.run_config.network.host_aliases == [] @@ -263,7 +266,7 @@ def test_accept_default_vertex_ai_networking_config(self): "Scheduling feature is temporarily disabled https://github.com/getindata/kedro-vertexai/issues/4" ) def test_reuse_run_name_for_scheduled_run_name(self): - cfg = PluginConfig.parse_obj( + cfg = PluginConfig.model_validate( { "run_config": { "scheduled_run_name": "some run", diff --git a/tests/test_config_providers.py b/tests/test_config_providers.py index 050207c..612f687 100644 --- a/tests/test_config_providers.py +++ b/tests/test_config_providers.py @@ -1,5 +1,6 @@ """Test Dynamic Config Providers""" +import json import logging import unittest from copy import deepcopy @@ -59,7 +60,7 @@ def _get_test_config_with_dynamic_provider( self, class_name=MLFlowGoogleOAuthCredentialsProvider.full_name(), ) -> PluginConfig: - config_raw = deepcopy(test_config.dict()) + config_raw = deepcopy(json.loads(test_config.model_dump_json())) config_raw["run_config"]["dynamic_config_providers"] = [ { "cls": class_name, @@ -69,7 +70,7 @@ def _get_test_config_with_dynamic_provider( }, } ] - config = PluginConfig.parse_obj(config_raw) + config = PluginConfig.model_validate(config_raw) return config def test_initialization_from_config(self): diff --git a/tests/test_vertex_ai_client.py b/tests/test_vertex_ai_client.py index 907e9da..9bc3111 100644 --- a/tests/test_vertex_ai_client.py +++ b/tests/test_vertex_ai_client.py @@ -10,7 +10,7 @@ class TestVertexAIClient(unittest.TestCase): def create_client(self): - config = PluginConfig.parse_obj( + config = PluginConfig.model_validate( { "project_id": "PROJECT_ID", "region": "REGION", diff --git a/tests/test_vertex_ai_generator.py b/tests/test_vertex_ai_generator.py index 857b4db..87de483 100644 --- a/tests/test_vertex_ai_generator.py +++ b/tests/test_vertex_ai_generator.py @@ -403,7 +403,7 @@ def create_generator(self, config={}, params={}, catalog={}): } config_with_defaults.update(config) self.generator_under_test = PipelineGenerator( - PluginConfig.parse_obj( + PluginConfig.model_validate( { "project_id": "test-project", "region": "test-region", diff --git a/tests/utils.py b/tests/utils.py index 8591227..bae16d9 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -3,7 +3,7 @@ from kedro_vertexai.config import PluginConfig -test_config = PluginConfig.parse_obj( +test_config = PluginConfig.model_validate( { "project_id": "test-project-id", "region": "test", @@ -12,11 +12,6 @@ "experiment_name": "Test Experiment", "run_name": "test run", "root": "unit_tests", - "volume": { - "storageclass": "default", - "size": "3Gi", - "access_modes": "[ReadWriteOnce]", - }, }, } )