@typespec/http-client-python 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/emitter/code-model.d.ts +4 -0
- package/dist/emitter/code-model.d.ts.map +1 -0
- package/dist/emitter/code-model.js +195 -0
- package/dist/emitter/code-model.js.map +1 -0
- package/dist/emitter/emitter.d.ts +6 -0
- package/dist/emitter/emitter.d.ts.map +1 -0
- package/dist/emitter/emitter.js +104 -0
- package/dist/emitter/emitter.js.map +1 -0
- package/dist/emitter/external-process.d.ts +20 -0
- package/dist/emitter/external-process.d.ts.map +1 -0
- package/dist/emitter/external-process.js +44 -0
- package/dist/emitter/external-process.js.map +1 -0
- package/dist/emitter/http.d.ts +7 -0
- package/dist/emitter/http.d.ts.map +1 -0
- package/dist/emitter/http.js +268 -0
- package/dist/emitter/http.js.map +1 -0
- package/dist/emitter/index.d.ts +3 -0
- package/dist/emitter/index.d.ts.map +1 -0
- package/dist/emitter/index.js +3 -0
- package/dist/emitter/index.js.map +1 -0
- package/dist/emitter/lib.d.ts +30 -0
- package/dist/emitter/lib.d.ts.map +1 -0
- package/dist/emitter/lib.js +33 -0
- package/dist/emitter/lib.js.map +1 -0
- package/dist/emitter/types.d.ts +36 -0
- package/dist/emitter/types.d.ts.map +1 -0
- package/dist/emitter/types.js +491 -0
- package/dist/emitter/types.js.map +1 -0
- package/dist/emitter/utils.d.ts +26 -0
- package/dist/emitter/utils.d.ts.map +1 -0
- package/dist/emitter/utils.js +155 -0
- package/dist/emitter/utils.js.map +1 -0
- package/emitter/src/code-model.ts +272 -0
- package/emitter/src/emitter.ts +127 -0
- package/emitter/src/external-process.ts +52 -0
- package/emitter/src/http.ts +382 -0
- package/emitter/src/index.ts +2 -0
- package/emitter/src/lib.ts +59 -0
- package/emitter/src/types.ts +573 -0
- package/emitter/src/utils.ts +215 -0
- package/emitter/temp/tsconfig.tsbuildinfo +1 -0
- package/emitter/test/utils.test.ts +22 -0
- package/emitter/tsconfig.build.json +11 -0
- package/emitter/tsconfig.json +7 -0
- package/emitter/vitest.config.ts +4 -0
- package/eng/scripts/Build-Packages.ps1 +86 -0
- package/eng/scripts/Check-GitChanges.ps1 +22 -0
- package/eng/scripts/Functions.ps1 +26 -0
- package/eng/scripts/Generate.ps1 +11 -0
- package/eng/scripts/Generation.psm1 +22 -0
- package/eng/scripts/Initialize-Repository.ps1 +40 -0
- package/eng/scripts/Test-Packages.ps1 +65 -0
- package/eng/scripts/ci/format.ts +3 -0
- package/eng/scripts/ci/lint.ts +39 -0
- package/eng/scripts/ci/mypy.ini +38 -0
- package/eng/scripts/ci/pylintrc +59 -0
- package/eng/scripts/ci/pyproject.toml +18 -0
- package/eng/scripts/ci/pyrightconfig.json +6 -0
- package/eng/scripts/ci/regenerate.ts +299 -0
- package/eng/scripts/ci/run-ci.ts +88 -0
- package/eng/scripts/ci/run_apiview.py +40 -0
- package/eng/scripts/ci/run_mypy.py +49 -0
- package/eng/scripts/ci/run_pylint.py +50 -0
- package/eng/scripts/ci/run_pyright.py +58 -0
- package/eng/scripts/ci/util.py +72 -0
- package/eng/scripts/ci/utils.ts +48 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
- package/eng/scripts/setup/install.py +53 -0
- package/eng/scripts/setup/prepare.py +42 -0
- package/eng/scripts/setup/run-python3.ts +25 -0
- package/eng/scripts/setup/run_tsp.py +42 -0
- package/eng/scripts/setup/system-requirements.ts +261 -0
- package/eng/scripts/setup/venvtools.py +87 -0
- package/generator/LICENSE +21 -0
- package/generator/README.md +1 -0
- package/generator/dev_requirements.txt +13 -0
- package/generator/pygen/__init__.py +107 -0
- package/generator/pygen/_version.py +7 -0
- package/generator/pygen/black.py +71 -0
- package/generator/pygen/codegen/__init__.py +338 -0
- package/generator/pygen/codegen/_utils.py +17 -0
- package/generator/pygen/codegen/models/__init__.py +204 -0
- package/generator/pygen/codegen/models/base.py +186 -0
- package/generator/pygen/codegen/models/base_builder.py +118 -0
- package/generator/pygen/codegen/models/client.py +433 -0
- package/generator/pygen/codegen/models/code_model.py +237 -0
- package/generator/pygen/codegen/models/combined_type.py +149 -0
- package/generator/pygen/codegen/models/constant_type.py +129 -0
- package/generator/pygen/codegen/models/credential_types.py +214 -0
- package/generator/pygen/codegen/models/dictionary_type.py +127 -0
- package/generator/pygen/codegen/models/enum_type.py +238 -0
- package/generator/pygen/codegen/models/imports.py +291 -0
- package/generator/pygen/codegen/models/list_type.py +143 -0
- package/generator/pygen/codegen/models/lro_operation.py +142 -0
- package/generator/pygen/codegen/models/lro_paging_operation.py +32 -0
- package/generator/pygen/codegen/models/model_type.py +359 -0
- package/generator/pygen/codegen/models/operation.py +530 -0
- package/generator/pygen/codegen/models/operation_group.py +184 -0
- package/generator/pygen/codegen/models/paging_operation.py +155 -0
- package/generator/pygen/codegen/models/parameter.py +412 -0
- package/generator/pygen/codegen/models/parameter_list.py +387 -0
- package/generator/pygen/codegen/models/primitive_types.py +659 -0
- package/generator/pygen/codegen/models/property.py +170 -0
- package/generator/pygen/codegen/models/request_builder.py +189 -0
- package/generator/pygen/codegen/models/request_builder_parameter.py +115 -0
- package/generator/pygen/codegen/models/response.py +348 -0
- package/generator/pygen/codegen/models/utils.py +21 -0
- package/generator/pygen/codegen/serializers/__init__.py +574 -0
- package/generator/pygen/codegen/serializers/base_serializer.py +21 -0
- package/generator/pygen/codegen/serializers/builder_serializer.py +1507 -0
- package/generator/pygen/codegen/serializers/client_serializer.py +294 -0
- package/generator/pygen/codegen/serializers/enum_serializer.py +15 -0
- package/generator/pygen/codegen/serializers/general_serializer.py +213 -0
- package/generator/pygen/codegen/serializers/import_serializer.py +126 -0
- package/generator/pygen/codegen/serializers/metadata_serializer.py +198 -0
- package/generator/pygen/codegen/serializers/model_init_serializer.py +33 -0
- package/generator/pygen/codegen/serializers/model_serializer.py +317 -0
- package/generator/pygen/codegen/serializers/operation_groups_serializer.py +89 -0
- package/generator/pygen/codegen/serializers/operations_init_serializer.py +44 -0
- package/generator/pygen/codegen/serializers/parameter_serializer.py +221 -0
- package/generator/pygen/codegen/serializers/patch_serializer.py +19 -0
- package/generator/pygen/codegen/serializers/request_builders_serializer.py +52 -0
- package/generator/pygen/codegen/serializers/sample_serializer.py +168 -0
- package/generator/pygen/codegen/serializers/test_serializer.py +292 -0
- package/generator/pygen/codegen/serializers/types_serializer.py +31 -0
- package/generator/pygen/codegen/serializers/utils.py +68 -0
- package/generator/pygen/codegen/templates/client.py.jinja2 +37 -0
- package/generator/pygen/codegen/templates/client_container.py.jinja2 +12 -0
- package/generator/pygen/codegen/templates/config.py.jinja2 +73 -0
- package/generator/pygen/codegen/templates/config_container.py.jinja2 +16 -0
- package/generator/pygen/codegen/templates/conftest.py.jinja2 +28 -0
- package/generator/pygen/codegen/templates/enum.py.jinja2 +13 -0
- package/generator/pygen/codegen/templates/enum_container.py.jinja2 +10 -0
- package/generator/pygen/codegen/templates/init.py.jinja2 +24 -0
- package/generator/pygen/codegen/templates/keywords.jinja2 +19 -0
- package/generator/pygen/codegen/templates/lro_operation.py.jinja2 +16 -0
- package/generator/pygen/codegen/templates/lro_paging_operation.py.jinja2 +18 -0
- package/generator/pygen/codegen/templates/macros.jinja2 +12 -0
- package/generator/pygen/codegen/templates/metadata.json.jinja2 +167 -0
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +1157 -0
- package/generator/pygen/codegen/templates/model_container.py.jinja2 +12 -0
- package/generator/pygen/codegen/templates/model_dpg.py.jinja2 +97 -0
- package/generator/pygen/codegen/templates/model_init.py.jinja2 +28 -0
- package/generator/pygen/codegen/templates/model_msrest.py.jinja2 +92 -0
- package/generator/pygen/codegen/templates/operation.py.jinja2 +21 -0
- package/generator/pygen/codegen/templates/operation_group.py.jinja2 +75 -0
- package/generator/pygen/codegen/templates/operation_groups_container.py.jinja2 +19 -0
- package/generator/pygen/codegen/templates/operation_tools.jinja2 +81 -0
- package/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 +17 -0
- package/generator/pygen/codegen/templates/packaging_templates/CHANGELOG.md.jinja2 +6 -0
- package/generator/pygen/codegen/templates/packaging_templates/LICENSE.jinja2 +21 -0
- package/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +8 -0
- package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +107 -0
- package/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +9 -0
- package/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +108 -0
- package/generator/pygen/codegen/templates/paging_operation.py.jinja2 +21 -0
- package/generator/pygen/codegen/templates/patch.py.jinja2 +19 -0
- package/generator/pygen/codegen/templates/pkgutil_init.py.jinja2 +1 -0
- package/generator/pygen/codegen/templates/request_builder.py.jinja2 +28 -0
- package/generator/pygen/codegen/templates/request_builders.py.jinja2 +10 -0
- package/generator/pygen/codegen/templates/rest_init.py.jinja2 +12 -0
- package/generator/pygen/codegen/templates/sample.py.jinja2 +44 -0
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +2114 -0
- package/generator/pygen/codegen/templates/test.py.jinja2 +50 -0
- package/generator/pygen/codegen/templates/testpreparer.py.jinja2 +26 -0
- package/generator/pygen/codegen/templates/types.py.jinja2 +7 -0
- package/generator/pygen/codegen/templates/validation.py.jinja2 +38 -0
- package/generator/pygen/codegen/templates/vendor.py.jinja2 +95 -0
- package/generator/pygen/codegen/templates/version.py.jinja2 +4 -0
- package/generator/pygen/m2r.py +65 -0
- package/generator/pygen/postprocess/__init__.py +183 -0
- package/generator/pygen/postprocess/get_all.py +19 -0
- package/generator/pygen/postprocess/venvtools.py +75 -0
- package/generator/pygen/preprocess/__init__.py +515 -0
- package/generator/pygen/preprocess/helpers.py +27 -0
- package/generator/pygen/preprocess/python_mappings.py +224 -0
- package/generator/pygen/utils.py +163 -0
- package/generator/pygen.egg-info/PKG-INFO +25 -0
- package/generator/pygen.egg-info/SOURCES.txt +66 -0
- package/generator/pygen.egg-info/dependency_links.txt +1 -0
- package/generator/pygen.egg-info/requires.txt +4 -0
- package/generator/pygen.egg-info/top_level.txt +1 -0
- package/generator/requirements.txt +12 -0
- package/generator/setup.py +55 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_arm_models_common_types_managed_identity_async.py +63 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_arm_models_resource_async.py +284 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_access_async.py +101 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_flatten_async.py +93 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_usage_async.py +31 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_basic_async.py +76 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_lro_rpc_async.py +22 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_lro_standard_async.py +39 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_model_async.py +33 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_page_async.py +58 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_scalar_async.py +41 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_core_traits_async.py +87 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_example_basic_async.py +30 -0
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_special_headers_client_request_id_async.py +30 -0
- package/generator/test/azure/mock_api_tests/conftest.py +150 -0
- package/generator/test/azure/mock_api_tests/test_azure_arm_models_common_types_managed_identity.py +60 -0
- package/generator/test/azure/mock_api_tests/test_azure_arm_models_resource.py +254 -0
- package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_access.py +92 -0
- package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_flatten.py +84 -0
- package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_usage.py +28 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_basic.py +70 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_lro_rpc.py +20 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_lro_standard.py +32 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_model.py +30 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_page.py +51 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_scalar.py +35 -0
- package/generator/test/azure/mock_api_tests/test_azure_core_traits.py +85 -0
- package/generator/test/azure/mock_api_tests/test_azure_example_basic.py +29 -0
- package/generator/test/azure/mock_api_tests/test_azure_special_headers_client_request_id.py +29 -0
- package/generator/test/azure/requirements.txt +89 -0
- package/generator/test/azure/tox.ini +56 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_authentication_async.py +121 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_client_naming_async.py +69 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_client_structure_async.py +62 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_encode_bytes_async.py +133 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_encode_datetime_async.py +127 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_encode_duration_async.py +63 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_encode_numeric_async.py +35 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_headasboolean_async.py +35 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_parameters_basic_async.py +24 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_parameters_body_optionality_async.py +30 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_parameters_collection_format_async.py +44 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_parameters_spread_async.py +76 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_content_negotiation_async.py +37 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_json_merge_patch_async.py +98 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_media_type_async.py +27 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_multipart_async.py +153 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_pageable_async.py +19 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_payload_xml_async.py +103 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_resiliency_srv_driven_async.py +128 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_routes_async.py +331 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_serialization_encoded_name_json_async.py +24 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_server_endpoint_not_defined_async.py +18 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_server_path_multiple_async.py +25 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_server_path_single_async.py +18 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_server_versions_not_versioned_async.py +28 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_server_versions_versioned_async.py +34 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_special_headers_conditional_request_async.py +38 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_special_headers_repeatability_async.py +19 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_special_words_async.py +42 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_array_async.py +118 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_dictionary_async.py +98 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_enum_extensible_async.py +25 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_enum_fixed_async.py +27 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_empty_async.py +32 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_inheritance_enum_discriminator_async.py +70 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_inheritance_nested_discriminator_async.py +85 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_inheritance_not_discriminated_async.py +34 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_inheritance_recursive_async.py +34 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_inheritance_single_discriminator_async.py +67 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_usage_async.py +32 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_model_visibility_async.py +47 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_property_additionalproperties_async.py +352 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_property_nullable_async.py +110 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_property_optional_async.py +197 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_property_valuetypes_async.py +315 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_scalar_async.py +60 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_typetest_union_async.py +90 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_added_async.py +36 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_made_optional_async.py +21 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_removed_async.py +21 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_renamed_from_async.py +29 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_return_type_changed_from_async.py +18 -0
- package/generator/test/generic_mock_api_tests/asynctests/test_versioning_type_changed_from_async.py +22 -0
- package/generator/test/generic_mock_api_tests/conftest.py +113 -0
- package/generator/test/generic_mock_api_tests/data/image.jpg +0 -0
- package/generator/test/generic_mock_api_tests/data/image.png +0 -0
- package/generator/test/generic_mock_api_tests/test_authentication.py +113 -0
- package/generator/test/generic_mock_api_tests/test_client_naming.py +57 -0
- package/generator/test/generic_mock_api_tests/test_client_structure.py +57 -0
- package/generator/test/generic_mock_api_tests/test_encode_bytes.py +128 -0
- package/generator/test/generic_mock_api_tests/test_encode_datetime.py +123 -0
- package/generator/test/generic_mock_api_tests/test_encode_duration.py +60 -0
- package/generator/test/generic_mock_api_tests/test_encode_numeric.py +31 -0
- package/generator/test/generic_mock_api_tests/test_headasboolean.py +33 -0
- package/generator/test/generic_mock_api_tests/test_parameters_basic.py +22 -0
- package/generator/test/generic_mock_api_tests/test_parameters_body_optionality.py +27 -0
- package/generator/test/generic_mock_api_tests/test_parameters_collection_format.py +37 -0
- package/generator/test/generic_mock_api_tests/test_parameters_spread.py +66 -0
- package/generator/test/generic_mock_api_tests/test_payload_content_negotiation.py +33 -0
- package/generator/test/generic_mock_api_tests/test_payload_json_merge_patch.py +93 -0
- package/generator/test/generic_mock_api_tests/test_payload_media_type.py +25 -0
- package/generator/test/generic_mock_api_tests/test_payload_multipart.py +140 -0
- package/generator/test/generic_mock_api_tests/test_payload_pageable.py +18 -0
- package/generator/test/generic_mock_api_tests/test_payload_xml.py +93 -0
- package/generator/test/generic_mock_api_tests/test_resiliency_srv_driven.py +122 -0
- package/generator/test/generic_mock_api_tests/test_routes.py +285 -0
- package/generator/test/generic_mock_api_tests/test_serialization_encoded_name_json.py +21 -0
- package/generator/test/generic_mock_api_tests/test_server_endpoint_not_defined.py +17 -0
- package/generator/test/generic_mock_api_tests/test_server_path_multiple.py +21 -0
- package/generator/test/generic_mock_api_tests/test_server_path_single.py +17 -0
- package/generator/test/generic_mock_api_tests/test_server_versions_not_versioned.py +25 -0
- package/generator/test/generic_mock_api_tests/test_server_versions_versioned.py +30 -0
- package/generator/test/generic_mock_api_tests/test_special_headers_conditional_request.py +34 -0
- package/generator/test/generic_mock_api_tests/test_special_headers_repeatability.py +18 -0
- package/generator/test/generic_mock_api_tests/test_special_words.py +37 -0
- package/generator/test/generic_mock_api_tests/test_typetest_array.py +103 -0
- package/generator/test/generic_mock_api_tests/test_typetest_dictionary.py +86 -0
- package/generator/test/generic_mock_api_tests/test_typetest_enum_extensible.py +23 -0
- package/generator/test/generic_mock_api_tests/test_typetest_enum_fixed.py +25 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_empty.py +29 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_inheritance_enum_discriminator.py +58 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_inheritance_nested_discriminator.py +79 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_inheritance_not_discriminated.py +31 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_inheritance_recursive.py +32 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_inheritance_single_discriminator.py +60 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_usage.py +28 -0
- package/generator/test/generic_mock_api_tests/test_typetest_model_visibility.py +40 -0
- package/generator/test/generic_mock_api_tests/test_typetest_property_additionalproperties.py +313 -0
- package/generator/test/generic_mock_api_tests/test_typetest_property_nullable.py +102 -0
- package/generator/test/generic_mock_api_tests/test_typetest_property_optional.py +174 -0
- package/generator/test/generic_mock_api_tests/test_typetest_property_valuetypes.py +286 -0
- package/generator/test/generic_mock_api_tests/test_typetest_scalar.py +53 -0
- package/generator/test/generic_mock_api_tests/test_typetest_union.py +80 -0
- package/generator/test/generic_mock_api_tests/test_versioning_added.py +33 -0
- package/generator/test/generic_mock_api_tests/test_versioning_made_optional.py +20 -0
- package/generator/test/generic_mock_api_tests/test_versioning_removed.py +20 -0
- package/generator/test/generic_mock_api_tests/test_versioning_renamed_from.py +27 -0
- package/generator/test/generic_mock_api_tests/test_versioning_return_type_changed_from.py +17 -0
- package/generator/test/generic_mock_api_tests/test_versioning_type_changed_from.py +21 -0
- package/generator/test/generic_mock_api_tests/unittests/test_model_base_serialization.py +4067 -0
- package/generator/test/generic_mock_api_tests/unittests/test_model_base_xml_serialization.py +1001 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_unbranded_async.py +24 -0
- package/generator/test/unbranded/mock_api_tests/cadl-ranch-config.yaml +27 -0
- package/generator/test/unbranded/mock_api_tests/conftest.py +35 -0
- package/generator/test/unbranded/mock_api_tests/test_unbranded.py +57 -0
- package/generator/test/unbranded/requirements.txt +72 -0
- package/generator/test/unbranded/tox.ini +56 -0
- package/package.json +93 -0
|
@@ -0,0 +1,4067 @@
|
|
|
1
|
+
# ------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation.
|
|
3
|
+
# Licensed under the MIT License.
|
|
4
|
+
# ------------------------------------
|
|
5
|
+
import copy
|
|
6
|
+
import decimal
|
|
7
|
+
import json
|
|
8
|
+
import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import (
|
|
11
|
+
Any,
|
|
12
|
+
Iterable,
|
|
13
|
+
List,
|
|
14
|
+
Literal,
|
|
15
|
+
Dict,
|
|
16
|
+
Mapping,
|
|
17
|
+
Sequence,
|
|
18
|
+
Set,
|
|
19
|
+
Tuple,
|
|
20
|
+
Optional,
|
|
21
|
+
overload,
|
|
22
|
+
Union,
|
|
23
|
+
)
|
|
24
|
+
import pytest
|
|
25
|
+
import isodate
|
|
26
|
+
import sys
|
|
27
|
+
from enum import Enum
|
|
28
|
+
|
|
29
|
+
from specialwords._model_base import (
|
|
30
|
+
SdkJSONEncoder,
|
|
31
|
+
Model,
|
|
32
|
+
rest_field,
|
|
33
|
+
_is_model,
|
|
34
|
+
rest_discriminator,
|
|
35
|
+
_deserialize,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if sys.version_info >= (3, 9):
|
|
39
|
+
from collections.abc import MutableMapping
|
|
40
|
+
else:
|
|
41
|
+
from typing import MutableMapping # type: ignore # pylint: disable=ungrouped-imports
|
|
42
|
+
JSON = MutableMapping[str, Any] # pylint: disable=unsubscriptable-object
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BasicResource(Model):
|
|
46
|
+
platform_update_domain_count: int = rest_field(
|
|
47
|
+
name="platformUpdateDomainCount"
|
|
48
|
+
) # How many times the platform update domain has been counted
|
|
49
|
+
platform_fault_domain_count: int = rest_field(
|
|
50
|
+
name="platformFaultDomainCount"
|
|
51
|
+
) # How many times the platform fault domain has been counted
|
|
52
|
+
virtual_machines: List[Any] = rest_field(name="virtualMachines") # List of virtual machines
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
platform_update_domain_count: int,
|
|
59
|
+
platform_fault_domain_count: int,
|
|
60
|
+
virtual_machines: List[Any],
|
|
61
|
+
): ...
|
|
62
|
+
|
|
63
|
+
@overload
|
|
64
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
65
|
+
|
|
66
|
+
def __init__(self, *args, **kwargs):
|
|
67
|
+
super().__init__(*args, **kwargs)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Pet(Model):
|
|
71
|
+
name: str = rest_field() # my name
|
|
72
|
+
species: str = rest_field() # my species
|
|
73
|
+
|
|
74
|
+
@overload
|
|
75
|
+
def __init__(self, *, name: str, species: str): ...
|
|
76
|
+
|
|
77
|
+
@overload
|
|
78
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
79
|
+
|
|
80
|
+
def __init__(self, *args, **kwargs):
|
|
81
|
+
super().__init__(*args, **kwargs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_model_and_dict_equal():
|
|
85
|
+
dict_response = {
|
|
86
|
+
"platformUpdateDomainCount": 5,
|
|
87
|
+
"platformFaultDomainCount": 3,
|
|
88
|
+
"virtualMachines": [],
|
|
89
|
+
}
|
|
90
|
+
model = BasicResource(
|
|
91
|
+
platform_update_domain_count=5,
|
|
92
|
+
platform_fault_domain_count=3,
|
|
93
|
+
virtual_machines=[],
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert model == dict_response
|
|
97
|
+
assert (
|
|
98
|
+
model.platform_update_domain_count
|
|
99
|
+
== model["platformUpdateDomainCount"]
|
|
100
|
+
== dict_response["platformUpdateDomainCount"]
|
|
101
|
+
== 5
|
|
102
|
+
)
|
|
103
|
+
assert (
|
|
104
|
+
model.platform_fault_domain_count
|
|
105
|
+
== model["platformFaultDomainCount"]
|
|
106
|
+
== dict_response["platformFaultDomainCount"]
|
|
107
|
+
== 3
|
|
108
|
+
)
|
|
109
|
+
assert model.virtual_machines == model["virtualMachines"] == dict_response["virtualMachines"]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_json_roundtrip():
|
|
113
|
+
dict_response = {
|
|
114
|
+
"platformUpdateDomainCount": 5,
|
|
115
|
+
"platformFaultDomainCount": 3,
|
|
116
|
+
"virtualMachines": [],
|
|
117
|
+
}
|
|
118
|
+
model = BasicResource(
|
|
119
|
+
platform_update_domain_count=5,
|
|
120
|
+
platform_fault_domain_count=3,
|
|
121
|
+
virtual_machines=[],
|
|
122
|
+
)
|
|
123
|
+
with pytest.raises(TypeError):
|
|
124
|
+
json.dumps(model)
|
|
125
|
+
assert (
|
|
126
|
+
json.dumps(dict(model))
|
|
127
|
+
== '{"platformUpdateDomainCount": 5, "platformFaultDomainCount": 3, "virtualMachines": []}'
|
|
128
|
+
)
|
|
129
|
+
assert json.loads(json.dumps(dict(model))) == model == dict_response
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_has_no_property():
|
|
133
|
+
dict_response = {
|
|
134
|
+
"platformUpdateDomainCount": 5,
|
|
135
|
+
"platformFaultDomainCount": 3,
|
|
136
|
+
"virtualMachines": [],
|
|
137
|
+
"noProp": "bonjour!",
|
|
138
|
+
}
|
|
139
|
+
model = BasicResource(dict_response)
|
|
140
|
+
assert (
|
|
141
|
+
model.platform_update_domain_count
|
|
142
|
+
== model["platformUpdateDomainCount"]
|
|
143
|
+
== dict_response["platformUpdateDomainCount"]
|
|
144
|
+
== 5
|
|
145
|
+
)
|
|
146
|
+
assert not hasattr(model, "no_prop")
|
|
147
|
+
with pytest.raises(AttributeError) as ex:
|
|
148
|
+
model.no_prop
|
|
149
|
+
|
|
150
|
+
assert str(ex.value) == "'BasicResource' object has no attribute 'no_prop'"
|
|
151
|
+
assert model["noProp"] == dict_response["noProp"] == "bonjour!"
|
|
152
|
+
|
|
153
|
+
# let's add it to model now
|
|
154
|
+
|
|
155
|
+
class BasicResourceWithProperty(BasicResource):
|
|
156
|
+
no_prop: str = rest_field(name="noProp")
|
|
157
|
+
|
|
158
|
+
model = BasicResourceWithProperty(
|
|
159
|
+
platform_update_domain_count=5,
|
|
160
|
+
platform_fault_domain_count=3,
|
|
161
|
+
virtual_machines=[],
|
|
162
|
+
no_prop="bonjour!",
|
|
163
|
+
)
|
|
164
|
+
assert model.no_prop == model["noProp"] == dict_response["noProp"] == "bonjour!"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_original_and_attr_name_same():
|
|
168
|
+
class MyModel(Model):
|
|
169
|
+
hello: str = rest_field()
|
|
170
|
+
|
|
171
|
+
@overload
|
|
172
|
+
def __init__(self, *, hello: str): ...
|
|
173
|
+
|
|
174
|
+
@overload
|
|
175
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
176
|
+
|
|
177
|
+
def __init__(self, *args, **kwargs):
|
|
178
|
+
super().__init__(*args, **kwargs)
|
|
179
|
+
|
|
180
|
+
dict_response = {"hello": "nihao"}
|
|
181
|
+
model = MyModel(hello="nihao")
|
|
182
|
+
assert model.hello == model["hello"] == dict_response["hello"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class OptionalModel(Model):
|
|
186
|
+
optional_str: Optional[str] = rest_field()
|
|
187
|
+
optional_time: Optional[datetime.time] = rest_field()
|
|
188
|
+
optional_dict: Optional[Dict[str, Optional[Pet]]] = rest_field(name="optionalDict")
|
|
189
|
+
optional_model: Optional[Pet] = rest_field()
|
|
190
|
+
optional_myself: Optional["OptionalModel"] = rest_field()
|
|
191
|
+
|
|
192
|
+
@overload
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
*,
|
|
196
|
+
optional_str: Optional[str] = None,
|
|
197
|
+
optional_time: Optional[datetime.time] = None,
|
|
198
|
+
optional_dict: Optional[Dict[str, Optional[Pet]]] = None,
|
|
199
|
+
optional_myself: Optional["OptionalModel"] = None,
|
|
200
|
+
): ...
|
|
201
|
+
|
|
202
|
+
@overload
|
|
203
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
204
|
+
|
|
205
|
+
def __init__(self, *args, **kwargs):
|
|
206
|
+
super().__init__(*args, **kwargs)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_optional_property():
|
|
210
|
+
dict_response = {
|
|
211
|
+
"optional_str": "hello!",
|
|
212
|
+
"optional_time": None,
|
|
213
|
+
"optionalDict": {
|
|
214
|
+
"Eugene": {
|
|
215
|
+
"name": "Eugene",
|
|
216
|
+
"species": "Dog",
|
|
217
|
+
},
|
|
218
|
+
"Lady": None,
|
|
219
|
+
},
|
|
220
|
+
"optional_model": None,
|
|
221
|
+
"optional_myself": {
|
|
222
|
+
"optional_str": None,
|
|
223
|
+
"optional_time": "11:34:56",
|
|
224
|
+
"optionalDict": None,
|
|
225
|
+
"optional_model": {"name": "Lady", "species": "Newt"},
|
|
226
|
+
"optional_myself": None,
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
model = OptionalModel(dict_response)
|
|
231
|
+
assert model.optional_str == model["optional_str"] == "hello!"
|
|
232
|
+
assert model.optional_time == model["optional_time"] == None
|
|
233
|
+
assert (
|
|
234
|
+
model.optional_dict
|
|
235
|
+
== model["optionalDict"]
|
|
236
|
+
== {
|
|
237
|
+
"Eugene": {
|
|
238
|
+
"name": "Eugene",
|
|
239
|
+
"species": "Dog",
|
|
240
|
+
},
|
|
241
|
+
"Lady": None,
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
assert model.optional_dict
|
|
245
|
+
assert model.optional_dict["Eugene"].name == model.optional_dict["Eugene"]["name"] == "Eugene"
|
|
246
|
+
assert model.optional_dict["Lady"] is None
|
|
247
|
+
|
|
248
|
+
assert (
|
|
249
|
+
model.optional_myself
|
|
250
|
+
== model["optional_myself"]
|
|
251
|
+
== {
|
|
252
|
+
"optional_str": None,
|
|
253
|
+
"optional_time": "11:34:56",
|
|
254
|
+
"optionalDict": None,
|
|
255
|
+
"optional_model": {"name": "Lady", "species": "Newt"},
|
|
256
|
+
"optional_myself": None,
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
assert model.optional_myself
|
|
260
|
+
assert model.optional_myself.optional_str is None
|
|
261
|
+
assert model.optional_myself.optional_time == datetime.time(11, 34, 56)
|
|
262
|
+
assert model.optional_myself.optional_dict is None
|
|
263
|
+
assert model.optional_myself.optional_model
|
|
264
|
+
assert model.optional_myself.optional_model.name == "Lady"
|
|
265
|
+
assert model.optional_myself.optional_model.species == "Newt"
|
|
266
|
+
assert model.optional_myself.optional_myself is None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_model_pass_in_none():
|
|
270
|
+
model = OptionalModel(optional_str=None)
|
|
271
|
+
assert model.optional_str == None
|
|
272
|
+
with pytest.raises(KeyError):
|
|
273
|
+
model["optionalStr"]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_modify_dict():
|
|
277
|
+
model = BasicResource(
|
|
278
|
+
platform_update_domain_count=5,
|
|
279
|
+
platform_fault_domain_count=3,
|
|
280
|
+
virtual_machines=[],
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# now let's modify the model as a dict
|
|
284
|
+
model["platformUpdateDomainCount"] = 100
|
|
285
|
+
assert model.platform_update_domain_count == model["platformUpdateDomainCount"] == 100
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def test_modify_property():
|
|
289
|
+
dict_response = {
|
|
290
|
+
"platformUpdateDomainCount": 5,
|
|
291
|
+
"platformFaultDomainCount": 3,
|
|
292
|
+
"virtualMachines": [],
|
|
293
|
+
}
|
|
294
|
+
model = BasicResource(
|
|
295
|
+
platform_update_domain_count=5,
|
|
296
|
+
platform_fault_domain_count=3,
|
|
297
|
+
virtual_machines=[],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# now let's modify the model through it's properties
|
|
301
|
+
model.platform_fault_domain_count = 2000
|
|
302
|
+
model["platformFaultDomainCount"]
|
|
303
|
+
assert model.platform_fault_domain_count == model["platformFaultDomainCount"] == 2000
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_property_is_a_type():
|
|
307
|
+
class Fish(Model):
|
|
308
|
+
name: str = rest_field()
|
|
309
|
+
species: Literal["Salmon", "Halibut"] = rest_field()
|
|
310
|
+
|
|
311
|
+
@overload
|
|
312
|
+
def __init__(self, *, name: str, species: Literal["Salmon", "Halibut"]): ...
|
|
313
|
+
|
|
314
|
+
@overload
|
|
315
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
316
|
+
|
|
317
|
+
def __init__(self, *args, **kwargs):
|
|
318
|
+
super().__init__(*args, **kwargs)
|
|
319
|
+
|
|
320
|
+
class Fishery(Model):
|
|
321
|
+
fish: Fish = rest_field()
|
|
322
|
+
|
|
323
|
+
@overload
|
|
324
|
+
def __init__(self, *, fish: Fish): ...
|
|
325
|
+
|
|
326
|
+
@overload
|
|
327
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
328
|
+
|
|
329
|
+
def __init__(self, *args, **kwargs):
|
|
330
|
+
super().__init__(*args, **kwargs)
|
|
331
|
+
|
|
332
|
+
fishery = Fishery({"fish": {"name": "Benjamin", "species": "Salmon"}})
|
|
333
|
+
assert isinstance(fishery.fish, Fish)
|
|
334
|
+
assert fishery.fish.name == fishery.fish["name"] == fishery["fish"]["name"] == "Benjamin"
|
|
335
|
+
assert fishery.fish.species == fishery.fish["species"] == fishery["fish"]["species"] == "Salmon"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_datetime_deserialization():
|
|
339
|
+
class DatetimeModel(Model):
|
|
340
|
+
datetime_value: datetime.datetime = rest_field(name="datetimeValue")
|
|
341
|
+
|
|
342
|
+
@overload
|
|
343
|
+
def __init__(self, *, datetime_value: datetime.datetime): ...
|
|
344
|
+
|
|
345
|
+
@overload
|
|
346
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
347
|
+
|
|
348
|
+
def __init__(self, *args, **kwargs):
|
|
349
|
+
super().__init__(*args, **kwargs)
|
|
350
|
+
|
|
351
|
+
val_str = "9999-12-31T23:59:59.999Z"
|
|
352
|
+
val = isodate.parse_datetime(val_str)
|
|
353
|
+
model = DatetimeModel({"datetimeValue": val_str})
|
|
354
|
+
assert model["datetimeValue"] == val_str
|
|
355
|
+
assert model.datetime_value == val
|
|
356
|
+
|
|
357
|
+
class BaseModel(Model):
|
|
358
|
+
my_prop: DatetimeModel = rest_field(name="myProp")
|
|
359
|
+
|
|
360
|
+
model = BaseModel({"myProp": {"datetimeValue": val_str}})
|
|
361
|
+
assert isinstance(model.my_prop, DatetimeModel)
|
|
362
|
+
model.my_prop["datetimeValue"]
|
|
363
|
+
assert model.my_prop["datetimeValue"] == model["myProp"]["datetimeValue"] == val_str
|
|
364
|
+
assert model.my_prop.datetime_value == val
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_date_deserialization():
|
|
368
|
+
class DateModel(Model):
|
|
369
|
+
date_value: datetime.date = rest_field(name="dateValue")
|
|
370
|
+
|
|
371
|
+
@overload
|
|
372
|
+
def __init__(self, *, date_value: datetime.date): ...
|
|
373
|
+
|
|
374
|
+
@overload
|
|
375
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
376
|
+
|
|
377
|
+
def __init__(self, *args, **kwargs):
|
|
378
|
+
super().__init__(*args, **kwargs)
|
|
379
|
+
|
|
380
|
+
val_str = "2016-02-29"
|
|
381
|
+
val = isodate.parse_date(val_str)
|
|
382
|
+
model = DateModel({"dateValue": val_str})
|
|
383
|
+
assert model["dateValue"] == val_str
|
|
384
|
+
assert model.date_value == val
|
|
385
|
+
|
|
386
|
+
class BaseModel(Model):
|
|
387
|
+
my_prop: DateModel = rest_field(name="myProp")
|
|
388
|
+
|
|
389
|
+
model = BaseModel({"myProp": {"dateValue": val_str}})
|
|
390
|
+
assert isinstance(model.my_prop, DateModel)
|
|
391
|
+
assert model.my_prop["dateValue"] == model["myProp"]["dateValue"] == val_str
|
|
392
|
+
assert model.my_prop.date_value == val
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def test_time_deserialization():
|
|
396
|
+
class TimeModel(Model):
|
|
397
|
+
time_value: datetime.time = rest_field(name="timeValue")
|
|
398
|
+
|
|
399
|
+
@overload
|
|
400
|
+
def __init__(self, *, time_value: datetime.time): ...
|
|
401
|
+
|
|
402
|
+
@overload
|
|
403
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
404
|
+
|
|
405
|
+
def __init__(self, *args, **kwargs):
|
|
406
|
+
super().__init__(*args, **kwargs)
|
|
407
|
+
|
|
408
|
+
val_str = "11:34:56"
|
|
409
|
+
val = datetime.time(11, 34, 56)
|
|
410
|
+
model = TimeModel({"timeValue": val_str})
|
|
411
|
+
assert model["timeValue"] == val_str
|
|
412
|
+
assert model.time_value == val
|
|
413
|
+
|
|
414
|
+
class BaseModel(Model):
|
|
415
|
+
my_prop: TimeModel = rest_field(name="myProp")
|
|
416
|
+
|
|
417
|
+
model = BaseModel({"myProp": {"timeValue": val_str}})
|
|
418
|
+
assert isinstance(model.my_prop, TimeModel)
|
|
419
|
+
assert model.my_prop["timeValue"] == model["myProp"]["timeValue"] == val_str
|
|
420
|
+
assert model.my_prop.time_value == val
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class SimpleRecursiveModel(Model):
|
|
424
|
+
name: str = rest_field()
|
|
425
|
+
me: "SimpleRecursiveModel" = rest_field()
|
|
426
|
+
|
|
427
|
+
@overload
|
|
428
|
+
def __init__(self, *, name: str, me: "SimpleRecursiveModel"): ...
|
|
429
|
+
|
|
430
|
+
@overload
|
|
431
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
432
|
+
|
|
433
|
+
def __init__(self, *args, **kwargs):
|
|
434
|
+
super().__init__(*args, **kwargs)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def test_model_recursion():
|
|
438
|
+
dict_response = {"name": "Snoopy", "me": {"name": "Egg", "me": {"name": "Chicken"}}}
|
|
439
|
+
|
|
440
|
+
model = SimpleRecursiveModel(dict_response)
|
|
441
|
+
assert model["name"] == model.name == "Snoopy"
|
|
442
|
+
assert model["me"] == {"name": "Egg", "me": {"name": "Chicken"}}
|
|
443
|
+
assert isinstance(model.me, SimpleRecursiveModel)
|
|
444
|
+
assert model.me["name"] == model.me.name == "Egg"
|
|
445
|
+
assert model.me["me"] == {"name": "Chicken"}
|
|
446
|
+
assert model.me.me.name == "Chicken"
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def test_dictionary_deserialization():
|
|
450
|
+
class DictionaryModel(Model):
|
|
451
|
+
prop: Dict[str, datetime.datetime] = rest_field()
|
|
452
|
+
|
|
453
|
+
@overload
|
|
454
|
+
def __init__(self, *, prop: datetime.datetime): ...
|
|
455
|
+
|
|
456
|
+
@overload
|
|
457
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
458
|
+
|
|
459
|
+
def __init__(self, *args, **kwargs):
|
|
460
|
+
super().__init__(*args, **kwargs)
|
|
461
|
+
|
|
462
|
+
val_str = "9999-12-31T23:59:59.999Z"
|
|
463
|
+
val = isodate.parse_datetime(val_str)
|
|
464
|
+
dict_response = {"prop": {"datetime": val_str}}
|
|
465
|
+
model = DictionaryModel(dict_response)
|
|
466
|
+
assert model["prop"] == {"datetime": val_str}
|
|
467
|
+
assert model.prop == {"datetime": val}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def test_attr_and_rest_case():
|
|
471
|
+
class ModelTest(Model):
|
|
472
|
+
our_attr: str = rest_field(name="ourAttr")
|
|
473
|
+
|
|
474
|
+
@overload
|
|
475
|
+
def __init__(self, *, our_attr: str): ...
|
|
476
|
+
|
|
477
|
+
@overload
|
|
478
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
479
|
+
|
|
480
|
+
def __init__(self, *args, **kwargs):
|
|
481
|
+
super().__init__(*args, **kwargs)
|
|
482
|
+
|
|
483
|
+
test_model = ModelTest({"ourAttr": "camel"})
|
|
484
|
+
assert test_model.our_attr == test_model["ourAttr"] == "camel"
|
|
485
|
+
|
|
486
|
+
test_model = ModelTest(ModelTest({"ourAttr": "camel"}))
|
|
487
|
+
assert test_model.our_attr == test_model["ourAttr"] == "camel"
|
|
488
|
+
|
|
489
|
+
test_model = ModelTest(our_attr="snake")
|
|
490
|
+
assert test_model.our_attr == test_model["ourAttr"] == "snake"
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def test_dictionary_deserialization_model():
|
|
494
|
+
class DictionaryModel(Model):
|
|
495
|
+
prop: Dict[str, Pet] = rest_field()
|
|
496
|
+
|
|
497
|
+
@overload
|
|
498
|
+
def __init__(self, *, prop: Dict[str, Pet]): ...
|
|
499
|
+
|
|
500
|
+
@overload
|
|
501
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
502
|
+
|
|
503
|
+
def __init__(self, *args, **kwargs):
|
|
504
|
+
super().__init__(*args, **kwargs)
|
|
505
|
+
|
|
506
|
+
dict_response = {
|
|
507
|
+
"prop": {
|
|
508
|
+
"Eugene": {
|
|
509
|
+
"name": "Eugene",
|
|
510
|
+
"species": "Dog",
|
|
511
|
+
},
|
|
512
|
+
"Lady": {
|
|
513
|
+
"name": "Lady",
|
|
514
|
+
"species": "Newt",
|
|
515
|
+
},
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
model = DictionaryModel(dict_response)
|
|
520
|
+
assert model["prop"] == {
|
|
521
|
+
"Eugene": {
|
|
522
|
+
"name": "Eugene",
|
|
523
|
+
"species": "Dog",
|
|
524
|
+
},
|
|
525
|
+
"Lady": {
|
|
526
|
+
"name": "Lady",
|
|
527
|
+
"species": "Newt",
|
|
528
|
+
},
|
|
529
|
+
}
|
|
530
|
+
assert model.prop == {
|
|
531
|
+
"Eugene": Pet({"name": "Eugene", "species": "Dog"}),
|
|
532
|
+
"Lady": Pet({"name": "Lady", "species": "Newt"}),
|
|
533
|
+
}
|
|
534
|
+
assert model.prop["Eugene"].name == model.prop["Eugene"]["name"] == "Eugene"
|
|
535
|
+
assert model.prop["Eugene"].species == model.prop["Eugene"]["species"] == "Dog"
|
|
536
|
+
assert model.prop["Lady"].name == model.prop["Lady"]["name"] == "Lady"
|
|
537
|
+
assert model.prop["Lady"].species == model.prop["Lady"]["species"] == "Newt"
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def test_list_deserialization():
|
|
541
|
+
class ListModel(Model):
|
|
542
|
+
prop: List[datetime.datetime] = rest_field()
|
|
543
|
+
|
|
544
|
+
@overload
|
|
545
|
+
def __init__(self, *, prop: List[datetime.datetime]): ...
|
|
546
|
+
|
|
547
|
+
@overload
|
|
548
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
549
|
+
|
|
550
|
+
def __init__(self, *args, **kwargs):
|
|
551
|
+
super().__init__(*args, **kwargs)
|
|
552
|
+
|
|
553
|
+
val_str = "9999-12-31T23:59:59.999Z"
|
|
554
|
+
val = isodate.parse_datetime(val_str)
|
|
555
|
+
dict_response = {"prop": [val_str, val_str]}
|
|
556
|
+
model = ListModel(dict_response)
|
|
557
|
+
assert model["prop"] == [val_str, val_str]
|
|
558
|
+
assert model.prop == [val, val]
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_list_deserialization_model():
|
|
562
|
+
class ListModel(Model):
|
|
563
|
+
prop: List[Pet] = rest_field()
|
|
564
|
+
|
|
565
|
+
@overload
|
|
566
|
+
def __init__(self, *, prop: List[Pet]): ...
|
|
567
|
+
|
|
568
|
+
@overload
|
|
569
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
570
|
+
|
|
571
|
+
def __init__(self, *args, **kwargs):
|
|
572
|
+
super().__init__(*args, **kwargs)
|
|
573
|
+
|
|
574
|
+
dict_response = {
|
|
575
|
+
"prop": [
|
|
576
|
+
{"name": "Eugene", "species": "Dog"},
|
|
577
|
+
{"name": "Lady", "species": "Newt"},
|
|
578
|
+
]
|
|
579
|
+
}
|
|
580
|
+
model = ListModel(dict_response)
|
|
581
|
+
assert model["prop"] == [
|
|
582
|
+
{"name": "Eugene", "species": "Dog"},
|
|
583
|
+
{"name": "Lady", "species": "Newt"},
|
|
584
|
+
]
|
|
585
|
+
assert model.prop == [
|
|
586
|
+
Pet({"name": "Eugene", "species": "Dog"}),
|
|
587
|
+
Pet({"name": "Lady", "species": "Newt"}),
|
|
588
|
+
]
|
|
589
|
+
assert len(model.prop) == 2
|
|
590
|
+
assert model.prop[0].name == model.prop[0]["name"] == "Eugene"
|
|
591
|
+
assert model.prop[0].species == model.prop[0]["species"] == "Dog"
|
|
592
|
+
assert model.prop[1].name == model.prop[1]["name"] == "Lady"
|
|
593
|
+
assert model.prop[1].species == model.prop[1]["species"] == "Newt"
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def test_set_deserialization():
|
|
597
|
+
class SetModel(Model):
|
|
598
|
+
prop: Set[datetime.datetime] = rest_field()
|
|
599
|
+
|
|
600
|
+
@overload
|
|
601
|
+
def __init__(self, *, prop: Set[datetime.datetime]): ...
|
|
602
|
+
|
|
603
|
+
@overload
|
|
604
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
605
|
+
|
|
606
|
+
def __init__(self, *args, **kwargs):
|
|
607
|
+
super().__init__(*args, **kwargs)
|
|
608
|
+
|
|
609
|
+
val_str = "9999-12-31T23:59:59.999Z"
|
|
610
|
+
val = isodate.parse_datetime(val_str)
|
|
611
|
+
dict_response = {"prop": set([val_str, val_str])}
|
|
612
|
+
model = SetModel(dict_response)
|
|
613
|
+
assert model["prop"] == set([val_str, val_str])
|
|
614
|
+
assert model.prop == set([val, val])
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def test_tuple_deserialization():
|
|
618
|
+
class TupleModel(Model):
|
|
619
|
+
prop: Tuple[str, datetime.datetime] = rest_field()
|
|
620
|
+
|
|
621
|
+
@overload
|
|
622
|
+
def __init__(self, *, prop: Tuple[str, datetime.datetime]): ...
|
|
623
|
+
|
|
624
|
+
@overload
|
|
625
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
626
|
+
|
|
627
|
+
def __init__(self, *args, **kwargs):
|
|
628
|
+
super().__init__(*args, **kwargs)
|
|
629
|
+
|
|
630
|
+
val_str = "9999-12-31T23:59:59.999Z"
|
|
631
|
+
val = isodate.parse_datetime(val_str)
|
|
632
|
+
dict_response = {"prop": (val_str, val_str)}
|
|
633
|
+
model = TupleModel(dict_response)
|
|
634
|
+
assert model["prop"] == (val_str, val_str)
|
|
635
|
+
assert model.prop == (val_str, val)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def test_list_of_tuple_deserialization_model():
|
|
639
|
+
class Owner(Model):
|
|
640
|
+
name: str = rest_field()
|
|
641
|
+
pet: Pet = rest_field()
|
|
642
|
+
|
|
643
|
+
@overload
|
|
644
|
+
def __init__(self, *, name: str, pet: Pet): ...
|
|
645
|
+
|
|
646
|
+
@overload
|
|
647
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
648
|
+
|
|
649
|
+
def __init__(self, *args, **kwargs):
|
|
650
|
+
super().__init__(*args, **kwargs)
|
|
651
|
+
|
|
652
|
+
class ListOfTupleModel(Model):
|
|
653
|
+
prop: List[Tuple[Pet, Owner]] = rest_field()
|
|
654
|
+
|
|
655
|
+
@overload
|
|
656
|
+
def __init__(self, *, prop: List[Tuple[Pet, Owner]]): ...
|
|
657
|
+
|
|
658
|
+
@overload
|
|
659
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
660
|
+
|
|
661
|
+
def __init__(self, *args, **kwargs):
|
|
662
|
+
super().__init__(*args, **kwargs)
|
|
663
|
+
|
|
664
|
+
eugene = {"name": "Eugene", "species": "Dog"}
|
|
665
|
+
lady = {"name": "Lady", "species": "Newt"}
|
|
666
|
+
giacamo = {"name": "Giacamo", "pet": eugene}
|
|
667
|
+
elizabeth = {"name": "Elizabeth", "pet": lady}
|
|
668
|
+
|
|
669
|
+
dict_response: Dict[str, Any] = {"prop": [(eugene, giacamo), (lady, elizabeth)]}
|
|
670
|
+
model = ListOfTupleModel(dict_response)
|
|
671
|
+
assert (
|
|
672
|
+
model["prop"]
|
|
673
|
+
== model.prop
|
|
674
|
+
== [(eugene, giacamo), (lady, elizabeth)]
|
|
675
|
+
== [(Pet(eugene), Owner(giacamo)), (Pet(lady), Owner(elizabeth))]
|
|
676
|
+
)
|
|
677
|
+
assert len(model.prop[0]) == len(model["prop"][0]) == 2
|
|
678
|
+
assert model.prop[0][0].name == model.prop[0][0]["name"] == "Eugene"
|
|
679
|
+
assert model.prop[0][0].species == model.prop[0][0]["species"] == "Dog"
|
|
680
|
+
assert model.prop[0][1].name == "Giacamo"
|
|
681
|
+
assert model.prop[0][1].pet == model.prop[0][0]
|
|
682
|
+
assert model.prop[0][1].pet.name == model.prop[0][1]["pet"]["name"] == "Eugene"
|
|
683
|
+
assert model.prop[1][0] == model.prop[1][1].pet
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
class RecursiveModel(Model):
|
|
687
|
+
name: str = rest_field()
|
|
688
|
+
list_of_me: Optional[List["RecursiveModel"]] = rest_field(name="listOfMe")
|
|
689
|
+
dict_of_me: Optional[Dict[str, "RecursiveModel"]] = rest_field(name="dictOfMe")
|
|
690
|
+
dict_of_list_of_me: Optional[Dict[str, List["RecursiveModel"]]] = rest_field(name="dictOfListOfMe")
|
|
691
|
+
list_of_dict_of_me: Optional[List[Dict[str, "RecursiveModel"]]] = rest_field(name="listOfDictOfMe")
|
|
692
|
+
|
|
693
|
+
@overload
|
|
694
|
+
def __init__(
|
|
695
|
+
self,
|
|
696
|
+
*,
|
|
697
|
+
name: str,
|
|
698
|
+
list_of_me: Optional[List["RecursiveModel"]] = None,
|
|
699
|
+
dict_of_me: Optional[Dict[str, "RecursiveModel"]] = None,
|
|
700
|
+
dict_of_list_of_me: Optional[Dict[str, List["RecursiveModel"]]] = None,
|
|
701
|
+
list_of_dict_of_me: Optional[List[Dict[str, "RecursiveModel"]]] = None,
|
|
702
|
+
): ...
|
|
703
|
+
|
|
704
|
+
@overload
|
|
705
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
706
|
+
|
|
707
|
+
def __init__(self, *args, **kwargs):
|
|
708
|
+
super().__init__(*args, **kwargs)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def test_model_recursion_complex():
|
|
712
|
+
dict_response = {
|
|
713
|
+
"name": "it's me!",
|
|
714
|
+
"listOfMe": [
|
|
715
|
+
{
|
|
716
|
+
"name": "it's me!",
|
|
717
|
+
"listOfMe": None,
|
|
718
|
+
"dictOfMe": None,
|
|
719
|
+
"dictOfListOfMe": None,
|
|
720
|
+
"listOfDictOfMe": None,
|
|
721
|
+
}
|
|
722
|
+
],
|
|
723
|
+
"dictOfMe": {
|
|
724
|
+
"me": {
|
|
725
|
+
"name": "it's me!",
|
|
726
|
+
"listOfMe": None,
|
|
727
|
+
"dictOfMe": None,
|
|
728
|
+
"dictOfListOfMe": None,
|
|
729
|
+
"listOfDictOfMe": None,
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
"dictOfListOfMe": {
|
|
733
|
+
"many mes": [
|
|
734
|
+
{
|
|
735
|
+
"name": "it's me!",
|
|
736
|
+
"listOfMe": None,
|
|
737
|
+
"dictOfMe": None,
|
|
738
|
+
"dictOfListOfMe": None,
|
|
739
|
+
"listOfDictOfMe": None,
|
|
740
|
+
}
|
|
741
|
+
]
|
|
742
|
+
},
|
|
743
|
+
"listOfDictOfMe": [
|
|
744
|
+
{
|
|
745
|
+
"me": {
|
|
746
|
+
"name": "it's me!",
|
|
747
|
+
"listOfMe": None,
|
|
748
|
+
"dictOfMe": None,
|
|
749
|
+
"dictOfListOfMe": None,
|
|
750
|
+
"listOfDictOfMe": None,
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
],
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
model = RecursiveModel(dict_response)
|
|
757
|
+
assert model.name == model["name"] == "it's me!"
|
|
758
|
+
assert model["listOfMe"] == [
|
|
759
|
+
{
|
|
760
|
+
"name": "it's me!",
|
|
761
|
+
"listOfMe": None,
|
|
762
|
+
"dictOfMe": None,
|
|
763
|
+
"dictOfListOfMe": None,
|
|
764
|
+
"listOfDictOfMe": None,
|
|
765
|
+
}
|
|
766
|
+
]
|
|
767
|
+
assert model.list_of_me == [
|
|
768
|
+
RecursiveModel(
|
|
769
|
+
{
|
|
770
|
+
"name": "it's me!",
|
|
771
|
+
"listOfMe": None,
|
|
772
|
+
"dictOfMe": None,
|
|
773
|
+
"dictOfListOfMe": None,
|
|
774
|
+
"listOfDictOfMe": None,
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
]
|
|
778
|
+
assert model.list_of_me
|
|
779
|
+
assert model.list_of_me[0].name == "it's me!"
|
|
780
|
+
assert model.list_of_me[0].list_of_me is None
|
|
781
|
+
assert isinstance(model.list_of_me, List)
|
|
782
|
+
assert isinstance(model.list_of_me[0], RecursiveModel)
|
|
783
|
+
|
|
784
|
+
assert model["dictOfMe"] == {
|
|
785
|
+
"me": {
|
|
786
|
+
"name": "it's me!",
|
|
787
|
+
"listOfMe": None,
|
|
788
|
+
"dictOfMe": None,
|
|
789
|
+
"dictOfListOfMe": None,
|
|
790
|
+
"listOfDictOfMe": None,
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
assert model.dict_of_me == {
|
|
794
|
+
"me": RecursiveModel(
|
|
795
|
+
{
|
|
796
|
+
"name": "it's me!",
|
|
797
|
+
"listOfMe": None,
|
|
798
|
+
"dictOfMe": None,
|
|
799
|
+
"dictOfListOfMe": None,
|
|
800
|
+
"listOfDictOfMe": None,
|
|
801
|
+
}
|
|
802
|
+
)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
assert isinstance(model.dict_of_me, Dict)
|
|
806
|
+
assert isinstance(model.dict_of_me["me"], RecursiveModel)
|
|
807
|
+
|
|
808
|
+
assert model["dictOfListOfMe"] == {
|
|
809
|
+
"many mes": [
|
|
810
|
+
{
|
|
811
|
+
"name": "it's me!",
|
|
812
|
+
"listOfMe": None,
|
|
813
|
+
"dictOfMe": None,
|
|
814
|
+
"dictOfListOfMe": None,
|
|
815
|
+
"listOfDictOfMe": None,
|
|
816
|
+
}
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
assert model.dict_of_list_of_me == {
|
|
820
|
+
"many mes": [
|
|
821
|
+
RecursiveModel(
|
|
822
|
+
{
|
|
823
|
+
"name": "it's me!",
|
|
824
|
+
"listOfMe": None,
|
|
825
|
+
"dictOfMe": None,
|
|
826
|
+
"dictOfListOfMe": None,
|
|
827
|
+
"listOfDictOfMe": None,
|
|
828
|
+
}
|
|
829
|
+
)
|
|
830
|
+
]
|
|
831
|
+
}
|
|
832
|
+
assert isinstance(model.dict_of_list_of_me, Dict)
|
|
833
|
+
assert isinstance(model.dict_of_list_of_me["many mes"], List)
|
|
834
|
+
assert isinstance(model.dict_of_list_of_me["many mes"][0], RecursiveModel)
|
|
835
|
+
|
|
836
|
+
assert model["listOfDictOfMe"] == [
|
|
837
|
+
{
|
|
838
|
+
"me": {
|
|
839
|
+
"name": "it's me!",
|
|
840
|
+
"listOfMe": None,
|
|
841
|
+
"dictOfMe": None,
|
|
842
|
+
"dictOfListOfMe": None,
|
|
843
|
+
"listOfDictOfMe": None,
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
]
|
|
847
|
+
assert model.list_of_dict_of_me == [
|
|
848
|
+
{
|
|
849
|
+
"me": RecursiveModel(
|
|
850
|
+
{
|
|
851
|
+
"name": "it's me!",
|
|
852
|
+
"listOfMe": None,
|
|
853
|
+
"dictOfMe": None,
|
|
854
|
+
"dictOfListOfMe": None,
|
|
855
|
+
"listOfDictOfMe": None,
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
}
|
|
859
|
+
]
|
|
860
|
+
assert isinstance(model.list_of_dict_of_me, List)
|
|
861
|
+
assert isinstance(model.list_of_dict_of_me[0], Dict)
|
|
862
|
+
assert isinstance(model.list_of_dict_of_me[0]["me"], RecursiveModel)
|
|
863
|
+
|
|
864
|
+
assert model.as_dict() == model == dict_response
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def test_literals():
|
|
868
|
+
class LiteralModel(Model):
|
|
869
|
+
species: Literal["Mongoose", "Eagle", "Penguin"] = rest_field()
|
|
870
|
+
age: Literal[1, 2, 3] = rest_field()
|
|
871
|
+
|
|
872
|
+
@overload
|
|
873
|
+
def __init__(
|
|
874
|
+
self,
|
|
875
|
+
*,
|
|
876
|
+
species: Literal["Mongoose", "Eagle", "Penguin"],
|
|
877
|
+
age: Literal[1, 2, 3],
|
|
878
|
+
): ...
|
|
879
|
+
|
|
880
|
+
@overload
|
|
881
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
882
|
+
|
|
883
|
+
def __init__(self, *args, **kwargs):
|
|
884
|
+
super().__init__(*args, **kwargs)
|
|
885
|
+
|
|
886
|
+
dict_response = {"species": "Mongoose", "age": 3}
|
|
887
|
+
model = LiteralModel(dict_response)
|
|
888
|
+
assert model.species == model["species"] == "Mongoose"
|
|
889
|
+
assert model.age == model["age"] == 3
|
|
890
|
+
|
|
891
|
+
dict_response = {"species": "invalid", "age": 5}
|
|
892
|
+
model = LiteralModel(dict_response)
|
|
893
|
+
assert model["species"] == "invalid"
|
|
894
|
+
assert model["age"] == 5
|
|
895
|
+
|
|
896
|
+
assert model.species == "invalid"
|
|
897
|
+
|
|
898
|
+
assert model.age == 5
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def test_deserialization_callback_override():
|
|
902
|
+
def _callback(obj):
|
|
903
|
+
return [str(entry) for entry in obj]
|
|
904
|
+
|
|
905
|
+
class MyModel(Model):
|
|
906
|
+
prop: Sequence[float] = rest_field()
|
|
907
|
+
|
|
908
|
+
@overload
|
|
909
|
+
def __init__(self, *, prop: Sequence[float]): ...
|
|
910
|
+
|
|
911
|
+
@overload
|
|
912
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
913
|
+
|
|
914
|
+
def __init__(self, *args, **kwargs):
|
|
915
|
+
super().__init__(*args, **kwargs)
|
|
916
|
+
|
|
917
|
+
model_without_callback = MyModel(prop=[1.3, 2.4, 3.5])
|
|
918
|
+
assert model_without_callback.prop == [1.3, 2.4, 3.5]
|
|
919
|
+
assert model_without_callback["prop"] == [1.3, 2.4, 3.5]
|
|
920
|
+
|
|
921
|
+
class MyModel2(Model):
|
|
922
|
+
prop: Sequence[int] = rest_field(type=_callback)
|
|
923
|
+
|
|
924
|
+
@overload
|
|
925
|
+
def __init__(self, *, prop: Any): ...
|
|
926
|
+
|
|
927
|
+
@overload
|
|
928
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
929
|
+
|
|
930
|
+
def __init__(self, *args, **kwargs):
|
|
931
|
+
super().__init__(*args, **kwargs)
|
|
932
|
+
|
|
933
|
+
model_with_callback = MyModel2(prop=[1.3, 2.4, 3.5])
|
|
934
|
+
assert model_with_callback.prop == ["1.3", "2.4", "3.5"]
|
|
935
|
+
# since the deserialize function is not roundtrip-able, once we deserialize
|
|
936
|
+
# the serialized version is the same
|
|
937
|
+
assert model_with_callback["prop"] == [1.3, 2.4, 3.5]
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def test_deserialization_callback_override_parent():
|
|
941
|
+
class ParentNoCallback(Model):
|
|
942
|
+
prop: Sequence[float] = rest_field()
|
|
943
|
+
|
|
944
|
+
@overload
|
|
945
|
+
def __init__(self, *, prop: Sequence[float]): ...
|
|
946
|
+
|
|
947
|
+
@overload
|
|
948
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
949
|
+
|
|
950
|
+
def __init__(self, *args, **kwargs):
|
|
951
|
+
super().__init__(*args, **kwargs)
|
|
952
|
+
|
|
953
|
+
def _callback(obj):
|
|
954
|
+
return set([str(entry) for entry in obj])
|
|
955
|
+
|
|
956
|
+
class ChildWithCallback(ParentNoCallback):
|
|
957
|
+
prop: Sequence[float] = rest_field(type=_callback)
|
|
958
|
+
|
|
959
|
+
@overload
|
|
960
|
+
def __init__(self, *, prop: Sequence[float]): ...
|
|
961
|
+
|
|
962
|
+
@overload
|
|
963
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
964
|
+
|
|
965
|
+
def __init__(self, *args, **kwargs):
|
|
966
|
+
super().__init__(*args, **kwargs)
|
|
967
|
+
|
|
968
|
+
parent_model = ParentNoCallback(prop=[1, 1, 2, 3])
|
|
969
|
+
assert parent_model.prop == parent_model["prop"] == [1, 1, 2, 3]
|
|
970
|
+
|
|
971
|
+
child_model = ChildWithCallback(prop=[1, 1, 2, 3])
|
|
972
|
+
assert child_model.prop == set(["1", "1", "2", "3"])
|
|
973
|
+
assert child_model["prop"] == [1, 1, 2, 3]
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def test_inheritance_basic():
|
|
977
|
+
def _callback(obj):
|
|
978
|
+
return [str(e) for e in obj]
|
|
979
|
+
|
|
980
|
+
class Parent(Model):
|
|
981
|
+
parent_prop: List[int] = rest_field(name="parentProp", type=_callback)
|
|
982
|
+
prop: str = rest_field()
|
|
983
|
+
|
|
984
|
+
@overload
|
|
985
|
+
def __init__(self, *, parent_prop: List[int], prop: str): ...
|
|
986
|
+
|
|
987
|
+
@overload
|
|
988
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
989
|
+
|
|
990
|
+
def __init__(self, *args, **kwargs):
|
|
991
|
+
super().__init__(*args, **kwargs)
|
|
992
|
+
|
|
993
|
+
class Child(Parent):
|
|
994
|
+
pass
|
|
995
|
+
|
|
996
|
+
c = Child(parent_prop=[1, 2, 3], prop="hello")
|
|
997
|
+
assert c == {"parentProp": [1, 2, 3], "prop": "hello"}
|
|
998
|
+
assert c.parent_prop == ["1", "2", "3"]
|
|
999
|
+
assert c.prop == "hello"
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
class ParentA(Model):
|
|
1003
|
+
prop: float = rest_field()
|
|
1004
|
+
|
|
1005
|
+
@overload
|
|
1006
|
+
def __init__(self, *, prop: Any): ...
|
|
1007
|
+
|
|
1008
|
+
@overload
|
|
1009
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1010
|
+
|
|
1011
|
+
def __init__(self, *args, **kwargs):
|
|
1012
|
+
super().__init__(*args, **kwargs)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
class ParentB(ParentA):
|
|
1016
|
+
prop: str = rest_field()
|
|
1017
|
+
bcd_prop: Optional[List["ParentB"]] = rest_field(name="bcdProp")
|
|
1018
|
+
|
|
1019
|
+
@overload
|
|
1020
|
+
def __init__(self, *, prop: Any, bcd_prop: Optional[List["ParentB"]] = None): ...
|
|
1021
|
+
|
|
1022
|
+
@overload
|
|
1023
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1024
|
+
|
|
1025
|
+
def __init__(self, *args, **kwargs):
|
|
1026
|
+
super().__init__(*args, **kwargs)
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
class ParentC(ParentB):
|
|
1030
|
+
prop: float = rest_field()
|
|
1031
|
+
cd_prop: ParentA = rest_field(name="cdProp")
|
|
1032
|
+
|
|
1033
|
+
@overload
|
|
1034
|
+
def __init__(self, *, prop: Any, bcd_prop: List[ParentB], cd_prop: ParentA, **kwargs): ...
|
|
1035
|
+
|
|
1036
|
+
@overload
|
|
1037
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1038
|
+
|
|
1039
|
+
def __init__(self, *args, **kwargs):
|
|
1040
|
+
super().__init__(*args, **kwargs)
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
class ChildD(ParentC):
|
|
1044
|
+
d_prop: Tuple[ParentA, ParentB, ParentC, Optional["ChildD"]] = rest_field(name="dProp")
|
|
1045
|
+
|
|
1046
|
+
@overload
|
|
1047
|
+
def __init__(
|
|
1048
|
+
self,
|
|
1049
|
+
*,
|
|
1050
|
+
prop: Any,
|
|
1051
|
+
bcd_prop: List[ParentB],
|
|
1052
|
+
cd_prop: ParentA,
|
|
1053
|
+
d_prop: Tuple[ParentA, ParentB, ParentC, Optional["ChildD"]],
|
|
1054
|
+
): ...
|
|
1055
|
+
|
|
1056
|
+
@overload
|
|
1057
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1058
|
+
|
|
1059
|
+
def __init__(self, *args, **kwargs):
|
|
1060
|
+
super().__init__(*args, **kwargs)
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def test_model_dict_comparisons():
|
|
1064
|
+
class Inner(Model):
|
|
1065
|
+
prop: str = rest_field()
|
|
1066
|
+
|
|
1067
|
+
@overload
|
|
1068
|
+
def __init__(
|
|
1069
|
+
self,
|
|
1070
|
+
*,
|
|
1071
|
+
prop: str,
|
|
1072
|
+
): ...
|
|
1073
|
+
|
|
1074
|
+
@overload
|
|
1075
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1076
|
+
|
|
1077
|
+
def __init__(self, *args, **kwargs):
|
|
1078
|
+
super().__init__(*args, **kwargs)
|
|
1079
|
+
|
|
1080
|
+
class Outer(Model):
|
|
1081
|
+
inner: Inner = rest_field()
|
|
1082
|
+
|
|
1083
|
+
@overload
|
|
1084
|
+
def __init__(
|
|
1085
|
+
self,
|
|
1086
|
+
*,
|
|
1087
|
+
inner: Inner,
|
|
1088
|
+
): ...
|
|
1089
|
+
|
|
1090
|
+
@overload
|
|
1091
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1092
|
+
|
|
1093
|
+
def __init__(self, *args, **kwargs):
|
|
1094
|
+
super().__init__(*args, **kwargs)
|
|
1095
|
+
|
|
1096
|
+
def _tests(outer):
|
|
1097
|
+
assert outer.inner.prop == outer["inner"].prop == outer.inner["prop"] == outer["inner"]["prop"] == "hello"
|
|
1098
|
+
assert outer.inner == outer["inner"] == {"prop": "hello"}
|
|
1099
|
+
assert outer == {"inner": {"prop": "hello"}}
|
|
1100
|
+
|
|
1101
|
+
_tests(Outer(inner=Inner(prop="hello")))
|
|
1102
|
+
_tests(Outer({"inner": {"prop": "hello"}}))
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def test_model_dict_comparisons_list():
|
|
1106
|
+
class Inner(Model):
|
|
1107
|
+
prop: str = rest_field()
|
|
1108
|
+
|
|
1109
|
+
@overload
|
|
1110
|
+
def __init__(
|
|
1111
|
+
self,
|
|
1112
|
+
*,
|
|
1113
|
+
prop: str,
|
|
1114
|
+
): ...
|
|
1115
|
+
|
|
1116
|
+
@overload
|
|
1117
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1118
|
+
|
|
1119
|
+
def __init__(self, *args, **kwargs):
|
|
1120
|
+
super().__init__(*args, **kwargs)
|
|
1121
|
+
|
|
1122
|
+
class Outer(Model):
|
|
1123
|
+
inner: List[Inner] = rest_field()
|
|
1124
|
+
|
|
1125
|
+
@overload
|
|
1126
|
+
def __init__(
|
|
1127
|
+
self,
|
|
1128
|
+
*,
|
|
1129
|
+
inner: List[Inner],
|
|
1130
|
+
): ...
|
|
1131
|
+
|
|
1132
|
+
@overload
|
|
1133
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1134
|
+
|
|
1135
|
+
def __init__(self, *args, **kwargs):
|
|
1136
|
+
super().__init__(*args, **kwargs)
|
|
1137
|
+
|
|
1138
|
+
def _tests(outer):
|
|
1139
|
+
assert (
|
|
1140
|
+
outer.inner[0].prop
|
|
1141
|
+
== outer["inner"][0].prop
|
|
1142
|
+
== outer.inner[0]["prop"]
|
|
1143
|
+
== outer["inner"][0]["prop"]
|
|
1144
|
+
== "hello"
|
|
1145
|
+
)
|
|
1146
|
+
assert outer.inner == outer["inner"] == [{"prop": "hello"}]
|
|
1147
|
+
assert outer == {"inner": [{"prop": "hello"}]}
|
|
1148
|
+
|
|
1149
|
+
_tests(Outer(inner=[Inner(prop="hello")]))
|
|
1150
|
+
_tests(Outer({"inner": [{"prop": "hello"}]}))
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def test_model_dict_comparisons_dict():
|
|
1154
|
+
class Inner(Model):
|
|
1155
|
+
prop: str = rest_field()
|
|
1156
|
+
|
|
1157
|
+
@overload
|
|
1158
|
+
def __init__(
|
|
1159
|
+
self,
|
|
1160
|
+
*,
|
|
1161
|
+
prop: str,
|
|
1162
|
+
): ...
|
|
1163
|
+
|
|
1164
|
+
@overload
|
|
1165
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1166
|
+
|
|
1167
|
+
def __init__(self, *args, **kwargs):
|
|
1168
|
+
super().__init__(*args, **kwargs)
|
|
1169
|
+
|
|
1170
|
+
class Outer(Model):
|
|
1171
|
+
inner: Dict[str, Inner] = rest_field()
|
|
1172
|
+
|
|
1173
|
+
@overload
|
|
1174
|
+
def __init__(
|
|
1175
|
+
self,
|
|
1176
|
+
*,
|
|
1177
|
+
inner: Dict[str, Inner],
|
|
1178
|
+
): ...
|
|
1179
|
+
|
|
1180
|
+
@overload
|
|
1181
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1182
|
+
|
|
1183
|
+
def __init__(self, *args, **kwargs):
|
|
1184
|
+
super().__init__(*args, **kwargs)
|
|
1185
|
+
|
|
1186
|
+
def _tests(outer):
|
|
1187
|
+
assert (
|
|
1188
|
+
outer.inner["key"].prop
|
|
1189
|
+
== outer["inner"]["key"].prop
|
|
1190
|
+
== outer.inner["key"]["prop"]
|
|
1191
|
+
== outer["inner"]["key"]["prop"]
|
|
1192
|
+
== "hello"
|
|
1193
|
+
)
|
|
1194
|
+
assert outer.inner == outer["inner"] == {"key": {"prop": "hello"}}
|
|
1195
|
+
with pytest.raises(AttributeError):
|
|
1196
|
+
outer.inner.key
|
|
1197
|
+
assert outer.inner["key"] == outer["inner"]["key"] == {"prop": "hello"}
|
|
1198
|
+
assert outer == {"inner": {"key": {"prop": "hello"}}}
|
|
1199
|
+
|
|
1200
|
+
_tests(Outer(inner={"key": Inner(prop="hello")}))
|
|
1201
|
+
_tests(Outer({"inner": {"key": {"prop": "hello"}}}))
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def test_inheritance_4_levels():
|
|
1205
|
+
a = ParentA(prop=3.4)
|
|
1206
|
+
assert a.prop == 3.4
|
|
1207
|
+
assert a["prop"] == 3.4
|
|
1208
|
+
assert a == {"prop": 3.4}
|
|
1209
|
+
assert isinstance(a, Model)
|
|
1210
|
+
|
|
1211
|
+
b = ParentB(prop=3.4, bcd_prop=[ParentB(prop=4.3)])
|
|
1212
|
+
assert b.prop == "3.4"
|
|
1213
|
+
assert b["prop"] == 3.4
|
|
1214
|
+
assert b.bcd_prop == [ParentB(prop=4.3)]
|
|
1215
|
+
assert b["bcdProp"] != [{"prop": 4.3, "bcdProp": None}]
|
|
1216
|
+
assert b["bcdProp"] == [{"prop": 4.3}]
|
|
1217
|
+
assert b.bcd_prop
|
|
1218
|
+
assert b.bcd_prop[0].prop == "4.3"
|
|
1219
|
+
assert b.bcd_prop[0].bcd_prop is None
|
|
1220
|
+
assert b == {"prop": 3.4, "bcdProp": [{"prop": 4.3}]}
|
|
1221
|
+
assert isinstance(b, ParentB)
|
|
1222
|
+
assert isinstance(b, ParentA)
|
|
1223
|
+
|
|
1224
|
+
c = ParentC(prop=3.4, bcd_prop=[b], cd_prop=a)
|
|
1225
|
+
assert c.prop == c["prop"] == 3.4
|
|
1226
|
+
assert c.bcd_prop == [b]
|
|
1227
|
+
assert c.bcd_prop
|
|
1228
|
+
assert isinstance(c.bcd_prop[0], ParentB)
|
|
1229
|
+
assert c["bcdProp"] == [b] == [{"prop": 3.4, "bcdProp": [{"prop": 4.3}]}]
|
|
1230
|
+
assert c.cd_prop == a
|
|
1231
|
+
assert c["cdProp"] == a == {"prop": 3.4}
|
|
1232
|
+
assert isinstance(c.cd_prop, ParentA)
|
|
1233
|
+
|
|
1234
|
+
d = ChildD(
|
|
1235
|
+
prop=3.4,
|
|
1236
|
+
bcd_prop=[b],
|
|
1237
|
+
cd_prop=a,
|
|
1238
|
+
d_prop=(
|
|
1239
|
+
a,
|
|
1240
|
+
b,
|
|
1241
|
+
c,
|
|
1242
|
+
ChildD(prop=3.4, bcd_prop=[b], cd_prop=a, d_prop=(a, b, c, None)),
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1245
|
+
assert d == {
|
|
1246
|
+
"prop": 3.4,
|
|
1247
|
+
"bcdProp": [b],
|
|
1248
|
+
"cdProp": a,
|
|
1249
|
+
"dProp": (
|
|
1250
|
+
a,
|
|
1251
|
+
b,
|
|
1252
|
+
c,
|
|
1253
|
+
{"prop": 3.4, "bcdProp": [b], "cdProp": a, "dProp": (a, b, c, None)},
|
|
1254
|
+
),
|
|
1255
|
+
}
|
|
1256
|
+
assert d.prop == d["prop"] == 3.4
|
|
1257
|
+
assert d.bcd_prop == [b]
|
|
1258
|
+
assert d.bcd_prop
|
|
1259
|
+
assert isinstance(d.bcd_prop[0], ParentB)
|
|
1260
|
+
assert d.cd_prop == a
|
|
1261
|
+
assert isinstance(d.cd_prop, ParentA)
|
|
1262
|
+
assert d.d_prop[0] == a # at a
|
|
1263
|
+
assert isinstance(d.d_prop[0], ParentA)
|
|
1264
|
+
assert d.d_prop[1] == b
|
|
1265
|
+
assert isinstance(d.d_prop[1], ParentB)
|
|
1266
|
+
assert d.d_prop[2] == c
|
|
1267
|
+
assert isinstance(d.d_prop[2], ParentC)
|
|
1268
|
+
assert isinstance(d.d_prop[3], ChildD)
|
|
1269
|
+
|
|
1270
|
+
assert isinstance(d.d_prop[3].d_prop[0], ParentA)
|
|
1271
|
+
assert isinstance(d.d_prop[3].d_prop[1], ParentB)
|
|
1272
|
+
assert isinstance(d.d_prop[3].d_prop[2], ParentC)
|
|
1273
|
+
assert d.d_prop[3].d_prop[3] is None
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
def test_multiple_inheritance_basic():
|
|
1277
|
+
class ParentOne(Model):
|
|
1278
|
+
parent_one_prop: str = rest_field(name="parentOneProp")
|
|
1279
|
+
|
|
1280
|
+
@overload
|
|
1281
|
+
def __init__(
|
|
1282
|
+
self,
|
|
1283
|
+
*,
|
|
1284
|
+
parent_one_prop: str,
|
|
1285
|
+
): ...
|
|
1286
|
+
|
|
1287
|
+
@overload
|
|
1288
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1289
|
+
|
|
1290
|
+
def __init__(self, *args, **kwargs):
|
|
1291
|
+
super().__init__(*args, **kwargs)
|
|
1292
|
+
|
|
1293
|
+
class ParentTwo(Model):
|
|
1294
|
+
parent_two_prop: int = rest_field(name="parentTwoProp", type=lambda x: str(x))
|
|
1295
|
+
|
|
1296
|
+
@overload
|
|
1297
|
+
def __init__(
|
|
1298
|
+
self,
|
|
1299
|
+
*,
|
|
1300
|
+
parent_two_prop: int,
|
|
1301
|
+
): ...
|
|
1302
|
+
|
|
1303
|
+
@overload
|
|
1304
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1305
|
+
|
|
1306
|
+
def __init__(self, *args, **kwargs):
|
|
1307
|
+
super().__init__(*args, **kwargs)
|
|
1308
|
+
|
|
1309
|
+
class Child(ParentOne, ParentTwo):
|
|
1310
|
+
@overload
|
|
1311
|
+
def __init__(
|
|
1312
|
+
self,
|
|
1313
|
+
*,
|
|
1314
|
+
parent_one_prop: str,
|
|
1315
|
+
parent_two_prop: int,
|
|
1316
|
+
): ...
|
|
1317
|
+
|
|
1318
|
+
@overload
|
|
1319
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1320
|
+
|
|
1321
|
+
def __init__(self, *args, **kwargs):
|
|
1322
|
+
super().__init__(*args, **kwargs)
|
|
1323
|
+
|
|
1324
|
+
c = Child(parent_one_prop="Hello", parent_two_prop=3)
|
|
1325
|
+
assert c == {"parentOneProp": "Hello", "parentTwoProp": 3}
|
|
1326
|
+
assert c.parent_one_prop == "Hello"
|
|
1327
|
+
assert c.parent_two_prop == "3"
|
|
1328
|
+
assert isinstance(c, Child)
|
|
1329
|
+
assert isinstance(c, ParentOne)
|
|
1330
|
+
assert isinstance(c, ParentTwo)
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def test_multiple_inheritance_mro():
|
|
1334
|
+
class A(Model):
|
|
1335
|
+
prop: str = rest_field()
|
|
1336
|
+
|
|
1337
|
+
@overload
|
|
1338
|
+
def __init__(self, *, prop: str) -> None: ...
|
|
1339
|
+
|
|
1340
|
+
@overload
|
|
1341
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1342
|
+
|
|
1343
|
+
def __init__(self, *args, **kwargs):
|
|
1344
|
+
super().__init__(*args, **kwargs)
|
|
1345
|
+
|
|
1346
|
+
class B(Model):
|
|
1347
|
+
prop: int = rest_field(type=lambda x: int(x))
|
|
1348
|
+
|
|
1349
|
+
@overload
|
|
1350
|
+
def __init__(self, *, prop: str) -> None: ...
|
|
1351
|
+
|
|
1352
|
+
@overload
|
|
1353
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1354
|
+
|
|
1355
|
+
def __init__(self, *args, **kwargs):
|
|
1356
|
+
super().__init__(*args, **kwargs)
|
|
1357
|
+
|
|
1358
|
+
class C(A, B):
|
|
1359
|
+
pass
|
|
1360
|
+
|
|
1361
|
+
assert A(prop="1").prop == "1"
|
|
1362
|
+
assert B(prop="1").prop == 1
|
|
1363
|
+
assert C(prop="1").prop == "1" # A should take precedence over B
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
class Feline(Model):
|
|
1367
|
+
meows: bool = rest_field()
|
|
1368
|
+
hisses: bool = rest_field()
|
|
1369
|
+
siblings: Optional[List["Feline"]] = rest_field()
|
|
1370
|
+
|
|
1371
|
+
@overload
|
|
1372
|
+
def __init__(
|
|
1373
|
+
self,
|
|
1374
|
+
*,
|
|
1375
|
+
meows: bool,
|
|
1376
|
+
hisses: bool,
|
|
1377
|
+
siblings: Optional[List["Feline"]] = None,
|
|
1378
|
+
): ...
|
|
1379
|
+
|
|
1380
|
+
@overload
|
|
1381
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1382
|
+
|
|
1383
|
+
def __init__(self, *args, **kwargs):
|
|
1384
|
+
super().__init__(*args, **kwargs)
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
class Owner(Model):
|
|
1388
|
+
first_name: str = rest_field(name="firstName", type=lambda x: x.capitalize())
|
|
1389
|
+
last_name: str = rest_field(name="lastName", type=lambda x: x.capitalize())
|
|
1390
|
+
|
|
1391
|
+
@overload
|
|
1392
|
+
def __init__(
|
|
1393
|
+
self,
|
|
1394
|
+
*,
|
|
1395
|
+
first_name: str,
|
|
1396
|
+
last_name: str,
|
|
1397
|
+
): ...
|
|
1398
|
+
|
|
1399
|
+
@overload
|
|
1400
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1401
|
+
|
|
1402
|
+
def __init__(self, *args, **kwargs):
|
|
1403
|
+
super().__init__(*args, **kwargs)
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
class PetModel(Model):
|
|
1407
|
+
name: str = rest_field()
|
|
1408
|
+
owner: Owner = rest_field()
|
|
1409
|
+
|
|
1410
|
+
@overload
|
|
1411
|
+
def __init__(self, *, name: str, owner: Owner): ...
|
|
1412
|
+
|
|
1413
|
+
@overload
|
|
1414
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1415
|
+
|
|
1416
|
+
def __init__(self, *args, **kwargs):
|
|
1417
|
+
super().__init__(*args, **kwargs)
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
class Cat(PetModel, Feline):
|
|
1421
|
+
likes_milk: bool = rest_field(name="likesMilk", type=lambda x: True)
|
|
1422
|
+
|
|
1423
|
+
@overload
|
|
1424
|
+
def __init__(
|
|
1425
|
+
self,
|
|
1426
|
+
*,
|
|
1427
|
+
name: str,
|
|
1428
|
+
owner: Owner,
|
|
1429
|
+
meows: bool,
|
|
1430
|
+
hisses: bool,
|
|
1431
|
+
likes_milk: bool,
|
|
1432
|
+
siblings: Optional[List[Feline]],
|
|
1433
|
+
): ...
|
|
1434
|
+
|
|
1435
|
+
@overload
|
|
1436
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1437
|
+
|
|
1438
|
+
def __init__(self, *args, **kwargs):
|
|
1439
|
+
super().__init__(*args, **kwargs)
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
class CuteThing(Model):
|
|
1443
|
+
how_cute_am_i: float = rest_field(name="howCuteAmI")
|
|
1444
|
+
|
|
1445
|
+
@overload
|
|
1446
|
+
def __init__(self, *, how_cute_am_i: float): ...
|
|
1447
|
+
|
|
1448
|
+
@overload
|
|
1449
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1450
|
+
|
|
1451
|
+
def __init__(self, *args, **kwargs):
|
|
1452
|
+
super().__init__(*args, **kwargs)
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
class Kitten(Cat, CuteThing):
|
|
1456
|
+
eats_mice_yet: bool = rest_field(name="eatsMiceYet")
|
|
1457
|
+
|
|
1458
|
+
@overload
|
|
1459
|
+
def __init__(
|
|
1460
|
+
self,
|
|
1461
|
+
*,
|
|
1462
|
+
name: str,
|
|
1463
|
+
owner: Owner,
|
|
1464
|
+
meows: bool,
|
|
1465
|
+
hisses: bool,
|
|
1466
|
+
likes_milk: bool,
|
|
1467
|
+
siblings: Optional[List[Feline]],
|
|
1468
|
+
how_cute_am_i: float,
|
|
1469
|
+
eats_mice_yet: bool,
|
|
1470
|
+
): ...
|
|
1471
|
+
|
|
1472
|
+
@overload
|
|
1473
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1474
|
+
|
|
1475
|
+
def __init__(self, *args, **kwargs):
|
|
1476
|
+
super().__init__(*args, **kwargs)
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
def test_multiple_inheritance_complex():
|
|
1480
|
+
cat = Cat(
|
|
1481
|
+
name="Stephanie",
|
|
1482
|
+
owner=Owner(first_name="cecil", last_name="cai"), # gets capitalized in attr
|
|
1483
|
+
meows=True,
|
|
1484
|
+
hisses=True,
|
|
1485
|
+
likes_milk=False, # likes_milk will change to True on the attribute
|
|
1486
|
+
siblings=[Feline(meows=True, hisses=False)],
|
|
1487
|
+
)
|
|
1488
|
+
assert cat == {
|
|
1489
|
+
"name": "Stephanie",
|
|
1490
|
+
"owner": {
|
|
1491
|
+
"firstName": "cecil",
|
|
1492
|
+
"lastName": "cai",
|
|
1493
|
+
},
|
|
1494
|
+
"meows": True,
|
|
1495
|
+
"hisses": True,
|
|
1496
|
+
"likesMilk": False,
|
|
1497
|
+
"siblings": [
|
|
1498
|
+
{
|
|
1499
|
+
"meows": True,
|
|
1500
|
+
"hisses": False,
|
|
1501
|
+
}
|
|
1502
|
+
],
|
|
1503
|
+
}
|
|
1504
|
+
assert cat.name == "Stephanie"
|
|
1505
|
+
assert isinstance(cat.owner, Owner)
|
|
1506
|
+
assert cat.owner.first_name == "Cecil"
|
|
1507
|
+
assert cat.owner.last_name == "Cai"
|
|
1508
|
+
assert cat.meows
|
|
1509
|
+
assert cat.hisses
|
|
1510
|
+
assert cat.likes_milk
|
|
1511
|
+
assert cat.siblings
|
|
1512
|
+
assert len(cat.siblings) == 1
|
|
1513
|
+
assert isinstance(cat.siblings[0], Feline)
|
|
1514
|
+
|
|
1515
|
+
kitten = Kitten(
|
|
1516
|
+
name="Stephanie",
|
|
1517
|
+
owner=Owner(first_name="cecil", last_name="cai"), # gets capitalized in attr
|
|
1518
|
+
meows=True,
|
|
1519
|
+
hisses=True,
|
|
1520
|
+
likes_milk=False, # likes_milk will change to True on the attribute
|
|
1521
|
+
siblings=[Feline(meows=True, hisses=False)],
|
|
1522
|
+
how_cute_am_i=1.0,
|
|
1523
|
+
eats_mice_yet=True,
|
|
1524
|
+
)
|
|
1525
|
+
assert kitten != {
|
|
1526
|
+
"name": "Stephanie",
|
|
1527
|
+
"owner": {
|
|
1528
|
+
"firstName": "cecil",
|
|
1529
|
+
"lastName": "cai",
|
|
1530
|
+
},
|
|
1531
|
+
"meows": True,
|
|
1532
|
+
"hisses": True,
|
|
1533
|
+
"likesMilk": False,
|
|
1534
|
+
"siblings": [{"meows": True, "hisses": False, "siblings": None}], # we don't automatically set None here
|
|
1535
|
+
"howCuteAmI": 1.0,
|
|
1536
|
+
"eatsMiceYet": True,
|
|
1537
|
+
}
|
|
1538
|
+
assert kitten == {
|
|
1539
|
+
"name": "Stephanie",
|
|
1540
|
+
"owner": {
|
|
1541
|
+
"firstName": "cecil",
|
|
1542
|
+
"lastName": "cai",
|
|
1543
|
+
},
|
|
1544
|
+
"meows": True,
|
|
1545
|
+
"hisses": True,
|
|
1546
|
+
"likesMilk": False,
|
|
1547
|
+
"siblings": [
|
|
1548
|
+
{
|
|
1549
|
+
"meows": True,
|
|
1550
|
+
"hisses": False,
|
|
1551
|
+
}
|
|
1552
|
+
],
|
|
1553
|
+
"howCuteAmI": 1.0,
|
|
1554
|
+
"eatsMiceYet": True,
|
|
1555
|
+
}
|
|
1556
|
+
assert kitten.name == "Stephanie"
|
|
1557
|
+
assert isinstance(kitten.owner, Owner)
|
|
1558
|
+
assert kitten.owner.first_name == "Cecil"
|
|
1559
|
+
assert kitten.owner.last_name == "Cai"
|
|
1560
|
+
assert kitten.meows
|
|
1561
|
+
assert kitten.hisses
|
|
1562
|
+
assert kitten.likes_milk
|
|
1563
|
+
assert kitten.siblings
|
|
1564
|
+
assert len(kitten.siblings) == 1
|
|
1565
|
+
assert isinstance(kitten.siblings[0], Feline)
|
|
1566
|
+
assert kitten.eats_mice_yet
|
|
1567
|
+
assert kitten.how_cute_am_i == 1.0
|
|
1568
|
+
assert isinstance(kitten, PetModel)
|
|
1569
|
+
assert isinstance(kitten, Cat)
|
|
1570
|
+
assert isinstance(kitten, Feline)
|
|
1571
|
+
assert isinstance(kitten, CuteThing)
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
class A(Model):
|
|
1575
|
+
b: "B" = rest_field()
|
|
1576
|
+
|
|
1577
|
+
@overload
|
|
1578
|
+
def __init__(self, b: "B"): ...
|
|
1579
|
+
|
|
1580
|
+
@overload
|
|
1581
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1582
|
+
|
|
1583
|
+
def __init__(self, *args, **kwargs):
|
|
1584
|
+
super().__init__(*args, **kwargs)
|
|
1585
|
+
|
|
1586
|
+
|
|
1587
|
+
class B(Model):
|
|
1588
|
+
c: "C" = rest_field()
|
|
1589
|
+
|
|
1590
|
+
@overload
|
|
1591
|
+
def __init__(self, *, c: "C"): ...
|
|
1592
|
+
|
|
1593
|
+
@overload
|
|
1594
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1595
|
+
|
|
1596
|
+
def __init__(self, *args, **kwargs):
|
|
1597
|
+
super().__init__(*args, **kwargs)
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
class C(Model):
|
|
1601
|
+
d: str = rest_field()
|
|
1602
|
+
|
|
1603
|
+
@overload
|
|
1604
|
+
def __init__(self, *, d: str): ...
|
|
1605
|
+
|
|
1606
|
+
@overload
|
|
1607
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1608
|
+
|
|
1609
|
+
def __init__(self, *args, **kwargs):
|
|
1610
|
+
super().__init__(*args, **kwargs)
|
|
1611
|
+
|
|
1612
|
+
|
|
1613
|
+
def test_nested_creation():
|
|
1614
|
+
a = A({"b": {"c": {"d": "hello"}}})
|
|
1615
|
+
assert isinstance(a["b"], Model)
|
|
1616
|
+
assert isinstance(a["b"]["c"], Model)
|
|
1617
|
+
assert a["b"]["c"] == a["b"].c == a.b.c == {"d": "hello"}
|
|
1618
|
+
|
|
1619
|
+
assert (
|
|
1620
|
+
a["b"]["c"]["d"]
|
|
1621
|
+
== a["b"].c.d
|
|
1622
|
+
== a.b["c"].d
|
|
1623
|
+
== a["b"]["c"].d
|
|
1624
|
+
== a["b"].c["d"]
|
|
1625
|
+
== a.b["c"]["d"]
|
|
1626
|
+
== a.b.c.d
|
|
1627
|
+
== "hello"
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
def test_nested_setting():
|
|
1632
|
+
a = A({"b": {"c": {"d": "hello"}}})
|
|
1633
|
+
|
|
1634
|
+
# set with dict
|
|
1635
|
+
a["b"]["c"]["d"] = "setwithdict"
|
|
1636
|
+
assert (
|
|
1637
|
+
a["b"]["c"]["d"]
|
|
1638
|
+
== a["b"].c.d
|
|
1639
|
+
== a.b["c"].d
|
|
1640
|
+
== a["b"]["c"].d
|
|
1641
|
+
== a["b"].c["d"]
|
|
1642
|
+
== a.b["c"]["d"]
|
|
1643
|
+
== a.b.c.d
|
|
1644
|
+
== "setwithdict"
|
|
1645
|
+
)
|
|
1646
|
+
|
|
1647
|
+
# set with attr
|
|
1648
|
+
a.b.c.d = "setwithattr"
|
|
1649
|
+
assert a["b"]["c"]["d"] == "setwithattr"
|
|
1650
|
+
assert (
|
|
1651
|
+
a["b"]["c"]["d"]
|
|
1652
|
+
== a["b"].c.d
|
|
1653
|
+
== a.b["c"].d
|
|
1654
|
+
== a["b"]["c"].d
|
|
1655
|
+
== a["b"].c["d"]
|
|
1656
|
+
== a.b["c"]["d"]
|
|
1657
|
+
== a.b.c.d
|
|
1658
|
+
== "setwithattr"
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
class BaseModel(Model):
|
|
1663
|
+
inner_model: "InnerModel" = rest_field(name="innerModel")
|
|
1664
|
+
|
|
1665
|
+
@overload
|
|
1666
|
+
def __init__(self, *, inner_model: "InnerModel"): ...
|
|
1667
|
+
|
|
1668
|
+
@overload
|
|
1669
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1670
|
+
|
|
1671
|
+
def __init__(self, *args, **kwargs):
|
|
1672
|
+
super().__init__(*args, **kwargs)
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
class InnerModel(Model):
|
|
1676
|
+
datetime_field: datetime.datetime = rest_field(name="datetimeField")
|
|
1677
|
+
|
|
1678
|
+
@overload
|
|
1679
|
+
def __init__(self, *, datetime_field: datetime.datetime): ...
|
|
1680
|
+
|
|
1681
|
+
@overload
|
|
1682
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1683
|
+
|
|
1684
|
+
def __init__(self, *args, **kwargs):
|
|
1685
|
+
super().__init__(*args, **kwargs)
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
def test_nested_deserialization():
|
|
1689
|
+
serialized_datetime = "9999-12-31T23:59:59.999Z"
|
|
1690
|
+
|
|
1691
|
+
model = BaseModel({"innerModel": {"datetimeField": serialized_datetime}})
|
|
1692
|
+
assert model.inner_model["datetimeField"] == model["innerModel"]["datetimeField"] == serialized_datetime
|
|
1693
|
+
assert (
|
|
1694
|
+
model.inner_model.datetime_field
|
|
1695
|
+
== model["innerModel"].datetime_field
|
|
1696
|
+
== isodate.parse_datetime(serialized_datetime)
|
|
1697
|
+
)
|
|
1698
|
+
|
|
1699
|
+
new_serialized_datetime = "2022-12-31T23:59:59.999Z"
|
|
1700
|
+
model.inner_model.datetime_field = isodate.parse_datetime(new_serialized_datetime)
|
|
1701
|
+
assert model.inner_model["datetimeField"] == "2022-12-31T23:59:59.999000Z"
|
|
1702
|
+
|
|
1703
|
+
|
|
1704
|
+
class X(Model):
|
|
1705
|
+
y: "Y" = rest_field()
|
|
1706
|
+
|
|
1707
|
+
@overload
|
|
1708
|
+
def __init__(self, *, y: "Y"): ...
|
|
1709
|
+
|
|
1710
|
+
@overload
|
|
1711
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1712
|
+
|
|
1713
|
+
def __init__(self, *args, **kwargs):
|
|
1714
|
+
super().__init__(*args, **kwargs)
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
class Y(Model):
|
|
1718
|
+
z: "Z" = rest_field()
|
|
1719
|
+
|
|
1720
|
+
@overload
|
|
1721
|
+
def __init__(self, *, z: "Z"): ...
|
|
1722
|
+
|
|
1723
|
+
@overload
|
|
1724
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1725
|
+
|
|
1726
|
+
def __init__(self, *args, **kwargs):
|
|
1727
|
+
super().__init__(*args, **kwargs)
|
|
1728
|
+
|
|
1729
|
+
|
|
1730
|
+
class Z(Model):
|
|
1731
|
+
z_val: datetime.datetime = rest_field(name="zVal")
|
|
1732
|
+
|
|
1733
|
+
@overload
|
|
1734
|
+
def __init__(self, *, z_val: datetime.datetime): ...
|
|
1735
|
+
|
|
1736
|
+
@overload
|
|
1737
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1738
|
+
|
|
1739
|
+
def __init__(self, *args, **kwargs):
|
|
1740
|
+
super().__init__(*args, **kwargs)
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
def test_nested_update():
|
|
1744
|
+
serialized_datetime = "9999-12-31T23:59:59.999Z"
|
|
1745
|
+
parsed_datetime = isodate.parse_datetime(serialized_datetime)
|
|
1746
|
+
x = X({"y": {"z": {"zVal": serialized_datetime}}})
|
|
1747
|
+
assert x.y.z.z_val == x["y"].z.z_val == x.y["z"].z_val == x["y"]["z"].z_val == parsed_datetime
|
|
1748
|
+
assert x.y.z["zVal"] == x.y["z"]["zVal"] == x["y"].z["zVal"] == x["y"]["z"]["zVal"] == serialized_datetime
|
|
1749
|
+
|
|
1750
|
+
|
|
1751
|
+
def test_deserialization_is():
|
|
1752
|
+
# test without datetime deserialization
|
|
1753
|
+
a = A({"b": {"c": {"d": "hello"}}})
|
|
1754
|
+
assert a.b is a.b
|
|
1755
|
+
assert a.b.c is a.b.c
|
|
1756
|
+
assert a.b.c.d is a.b.c.d
|
|
1757
|
+
|
|
1758
|
+
serialized_datetime = "9999-12-31T23:59:59.999Z"
|
|
1759
|
+
x = X({"y": {"z": {"zVal": serialized_datetime}}})
|
|
1760
|
+
assert x.y is x.y
|
|
1761
|
+
assert x.y.z is x.y.z
|
|
1762
|
+
|
|
1763
|
+
assert x.y.z.z_val == isodate.parse_datetime(serialized_datetime)
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
class InnerModelWithReadonly(Model):
|
|
1767
|
+
normal_property: str = rest_field(name="normalProperty")
|
|
1768
|
+
readonly_property: str = rest_field(name="readonlyProperty", visibility=["read"])
|
|
1769
|
+
|
|
1770
|
+
@overload
|
|
1771
|
+
def __init__(self, *, normal_property: str): ...
|
|
1772
|
+
|
|
1773
|
+
@overload
|
|
1774
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1775
|
+
|
|
1776
|
+
def __init__(self, *args, **kwargs):
|
|
1777
|
+
super().__init__(*args, **kwargs)
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
class ModelWithReadonly(Model):
|
|
1781
|
+
normal_property: str = rest_field(name="normalProperty")
|
|
1782
|
+
readonly_property: str = rest_field(name="readonlyProperty", visibility=["read"])
|
|
1783
|
+
inner_model: InnerModelWithReadonly = rest_field(name="innerModel")
|
|
1784
|
+
|
|
1785
|
+
@overload
|
|
1786
|
+
def __init__(self, *, normal_property: str): ...
|
|
1787
|
+
|
|
1788
|
+
@overload
|
|
1789
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1790
|
+
|
|
1791
|
+
def __init__(self, *args, **kwargs):
|
|
1792
|
+
super().__init__(*args, **kwargs)
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
def test_readonly():
|
|
1796
|
+
# we pass the dict to json, so readonly shouldn't show up in the JSON version
|
|
1797
|
+
value = {
|
|
1798
|
+
"normalProperty": "normal",
|
|
1799
|
+
"readonlyProperty": "readonly",
|
|
1800
|
+
"innerModel": {"normalProperty": "normal", "readonlyProperty": "readonly"},
|
|
1801
|
+
}
|
|
1802
|
+
model = ModelWithReadonly(value)
|
|
1803
|
+
assert model.as_dict(exclude_readonly=True) == {
|
|
1804
|
+
"normalProperty": "normal",
|
|
1805
|
+
"innerModel": {"normalProperty": "normal"},
|
|
1806
|
+
}
|
|
1807
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == value
|
|
1808
|
+
assert model == value
|
|
1809
|
+
assert model["readonlyProperty"] == model.readonly_property == "readonly"
|
|
1810
|
+
assert model["innerModel"]["readonlyProperty"] == model.inner_model.readonly_property == "readonly"
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
def test_readonly_set():
|
|
1814
|
+
value = {
|
|
1815
|
+
"normalProperty": "normal",
|
|
1816
|
+
"readonlyProperty": "readonly",
|
|
1817
|
+
"innerModel": {"normalProperty": "normal", "readonlyProperty": "readonly"},
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
model = ModelWithReadonly(value)
|
|
1821
|
+
assert model.normal_property == model["normalProperty"] == "normal"
|
|
1822
|
+
assert model.readonly_property == model["readonlyProperty"] == "readonly"
|
|
1823
|
+
assert model.inner_model.normal_property == model.inner_model["normalProperty"] == "normal"
|
|
1824
|
+
assert model.inner_model.readonly_property == model.inner_model["readonlyProperty"] == "readonly"
|
|
1825
|
+
|
|
1826
|
+
assert model.as_dict(exclude_readonly=True) == {
|
|
1827
|
+
"normalProperty": "normal",
|
|
1828
|
+
"innerModel": {"normalProperty": "normal"},
|
|
1829
|
+
}
|
|
1830
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == value
|
|
1831
|
+
|
|
1832
|
+
model["normalProperty"] = "setWithDict"
|
|
1833
|
+
model["readonlyProperty"] = "setWithDict"
|
|
1834
|
+
model.inner_model["normalProperty"] = "setWithDict"
|
|
1835
|
+
model.inner_model["readonlyProperty"] = "setWithDict"
|
|
1836
|
+
|
|
1837
|
+
assert model.normal_property == model["normalProperty"] == "setWithDict"
|
|
1838
|
+
assert model.readonly_property == model["readonlyProperty"] == "setWithDict"
|
|
1839
|
+
assert model.inner_model.normal_property == model.inner_model["normalProperty"] == "setWithDict"
|
|
1840
|
+
assert model.inner_model.readonly_property == model.inner_model["readonlyProperty"] == "setWithDict"
|
|
1841
|
+
assert model.as_dict(exclude_readonly=True) == {
|
|
1842
|
+
"normalProperty": "setWithDict",
|
|
1843
|
+
"innerModel": {"normalProperty": "setWithDict"},
|
|
1844
|
+
}
|
|
1845
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == {
|
|
1846
|
+
"normalProperty": "setWithDict",
|
|
1847
|
+
"readonlyProperty": "setWithDict",
|
|
1848
|
+
"innerModel": {
|
|
1849
|
+
"normalProperty": "setWithDict",
|
|
1850
|
+
"readonlyProperty": "setWithDict",
|
|
1851
|
+
},
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
def test_incorrect_initialization():
|
|
1856
|
+
class MyModel(Model):
|
|
1857
|
+
id: int = rest_field()
|
|
1858
|
+
field: str = rest_field()
|
|
1859
|
+
|
|
1860
|
+
@overload
|
|
1861
|
+
def __init__(
|
|
1862
|
+
self,
|
|
1863
|
+
*,
|
|
1864
|
+
id: int,
|
|
1865
|
+
field: str,
|
|
1866
|
+
): ...
|
|
1867
|
+
|
|
1868
|
+
@overload
|
|
1869
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1870
|
+
|
|
1871
|
+
def __init__(self, *args, **kwargs):
|
|
1872
|
+
super().__init__(*args, **kwargs)
|
|
1873
|
+
|
|
1874
|
+
with pytest.raises(TypeError):
|
|
1875
|
+
MyModel(1, "field")
|
|
1876
|
+
|
|
1877
|
+
with pytest.raises(TypeError):
|
|
1878
|
+
MyModel(id=1, field="field", unknown="me")
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
def test_serialization_initialization_and_setting():
|
|
1882
|
+
serialized_datetime = "9999-12-31T23:59:59.999000Z"
|
|
1883
|
+
parsed_datetime = isodate.parse_datetime(serialized_datetime)
|
|
1884
|
+
|
|
1885
|
+
# pass in parsed
|
|
1886
|
+
z = Z(z_val=parsed_datetime)
|
|
1887
|
+
assert z.z_val == parsed_datetime
|
|
1888
|
+
assert z["zVal"] == serialized_datetime
|
|
1889
|
+
|
|
1890
|
+
# pass in dict
|
|
1891
|
+
z = Z({"zVal": serialized_datetime})
|
|
1892
|
+
assert z.z_val == parsed_datetime
|
|
1893
|
+
assert z["zVal"] == serialized_datetime
|
|
1894
|
+
|
|
1895
|
+
# assert setting
|
|
1896
|
+
serialized_datetime = "2022-12-31T23:59:59.999000Z"
|
|
1897
|
+
z.z_val = isodate.parse_datetime(serialized_datetime)
|
|
1898
|
+
assert z["zVal"] == serialized_datetime
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
def test_copy_of_input():
|
|
1902
|
+
class TestModel(Model):
|
|
1903
|
+
data: List[int] = rest_field()
|
|
1904
|
+
|
|
1905
|
+
@overload
|
|
1906
|
+
def __init__(self, *, data: List[int]): ...
|
|
1907
|
+
|
|
1908
|
+
@overload
|
|
1909
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1910
|
+
|
|
1911
|
+
def __init__(self, *args, **kwargs):
|
|
1912
|
+
super().__init__(*args, **kwargs)
|
|
1913
|
+
|
|
1914
|
+
raw = [1, 2, 3]
|
|
1915
|
+
m = TestModel(data=raw)
|
|
1916
|
+
assert not m.data is raw
|
|
1917
|
+
assert m.data == raw
|
|
1918
|
+
raw.append(4)
|
|
1919
|
+
assert m.data == [1, 2, 3]
|
|
1920
|
+
|
|
1921
|
+
|
|
1922
|
+
def test_inner_model_custom_serializer():
|
|
1923
|
+
class InnerModel(Model):
|
|
1924
|
+
prop: str = rest_field(type=lambda x: x[::-1])
|
|
1925
|
+
|
|
1926
|
+
@overload
|
|
1927
|
+
def __init__(self, *, prop: str): ...
|
|
1928
|
+
|
|
1929
|
+
@overload
|
|
1930
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1931
|
+
|
|
1932
|
+
def __init__(self, *args, **kwargs):
|
|
1933
|
+
super().__init__(*args, **kwargs)
|
|
1934
|
+
|
|
1935
|
+
class OuterModel(Model):
|
|
1936
|
+
inner: InnerModel = rest_field()
|
|
1937
|
+
|
|
1938
|
+
@overload
|
|
1939
|
+
def __init__(self, *, inner: InnerModel): ...
|
|
1940
|
+
|
|
1941
|
+
@overload
|
|
1942
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1943
|
+
|
|
1944
|
+
def __init__(self, *args, **kwargs):
|
|
1945
|
+
super().__init__(*args, **kwargs)
|
|
1946
|
+
|
|
1947
|
+
outer = OuterModel({"inner": {"prop": "hello"}})
|
|
1948
|
+
assert outer.inner["prop"] == outer["inner"]["prop"] == "hello"
|
|
1949
|
+
assert outer.inner.prop == outer["inner"].prop == "olleh" # cspell: ignore olleh
|
|
1950
|
+
|
|
1951
|
+
|
|
1952
|
+
def test_default_value():
|
|
1953
|
+
class MyModel(Model):
|
|
1954
|
+
prop_default_str: str = rest_field(name="propDefaultStr", default="hello")
|
|
1955
|
+
prop_optional_str: Optional[str] = rest_field(name="propOptionalStr", default=None)
|
|
1956
|
+
prop_default_int: int = rest_field(name="propDefaultInt", default=1)
|
|
1957
|
+
prop_optional_int: Optional[int] = rest_field(name="propOptionalInt", default=None)
|
|
1958
|
+
|
|
1959
|
+
@overload
|
|
1960
|
+
def __init__(
|
|
1961
|
+
self,
|
|
1962
|
+
*,
|
|
1963
|
+
prop_default_str: str = "hello",
|
|
1964
|
+
prop_optional_str: Optional[str] = "propOptionalStr",
|
|
1965
|
+
prop_default_int: int = 1,
|
|
1966
|
+
prop_optional_int: Optional[int] = None,
|
|
1967
|
+
): ...
|
|
1968
|
+
|
|
1969
|
+
@overload
|
|
1970
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
1971
|
+
|
|
1972
|
+
def __init__(self, *args, **kwargs):
|
|
1973
|
+
super().__init__(*args, **kwargs)
|
|
1974
|
+
|
|
1975
|
+
my_model = MyModel()
|
|
1976
|
+
assert my_model.prop_default_str == my_model["propDefaultStr"] == "hello"
|
|
1977
|
+
assert my_model.prop_optional_str is my_model["propOptionalStr"] is None
|
|
1978
|
+
assert my_model.prop_default_int == my_model["propDefaultInt"] == 1
|
|
1979
|
+
assert my_model.prop_optional_int is my_model["propOptionalInt"] is None
|
|
1980
|
+
assert my_model == {
|
|
1981
|
+
"propDefaultStr": "hello",
|
|
1982
|
+
"propOptionalStr": None,
|
|
1983
|
+
"propDefaultInt": 1,
|
|
1984
|
+
"propOptionalInt": None,
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
my_model = MyModel(prop_default_str="goodbye")
|
|
1988
|
+
assert my_model.prop_default_str == my_model["propDefaultStr"] == "goodbye"
|
|
1989
|
+
assert my_model.prop_optional_str is my_model["propOptionalStr"] is None
|
|
1990
|
+
assert my_model.prop_default_int == my_model["propDefaultInt"] == 1
|
|
1991
|
+
assert my_model.prop_optional_int is my_model["propOptionalInt"] is None
|
|
1992
|
+
assert my_model == {
|
|
1993
|
+
"propDefaultStr": "goodbye",
|
|
1994
|
+
"propOptionalStr": None,
|
|
1995
|
+
"propDefaultInt": 1,
|
|
1996
|
+
"propOptionalInt": None,
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
my_model = MyModel(prop_optional_int=4)
|
|
2000
|
+
assert my_model.prop_default_str == my_model["propDefaultStr"] == "hello"
|
|
2001
|
+
assert my_model.prop_optional_str is my_model["propOptionalStr"] is None
|
|
2002
|
+
assert my_model.prop_default_int == my_model["propDefaultInt"] == 1
|
|
2003
|
+
assert my_model.prop_optional_int == my_model["propOptionalInt"] == 4
|
|
2004
|
+
assert my_model == {
|
|
2005
|
+
"propDefaultStr": "hello",
|
|
2006
|
+
"propOptionalStr": None,
|
|
2007
|
+
"propDefaultInt": 1,
|
|
2008
|
+
"propOptionalInt": 4,
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
my_model = MyModel({"propDefaultInt": 5})
|
|
2012
|
+
assert my_model.prop_default_str == my_model["propDefaultStr"] == "hello"
|
|
2013
|
+
assert my_model.prop_optional_str is my_model["propOptionalStr"] is None
|
|
2014
|
+
assert my_model.prop_default_int == my_model["propDefaultInt"] == 5
|
|
2015
|
+
assert my_model.prop_optional_int is my_model["propOptionalInt"] is None
|
|
2016
|
+
assert my_model == {
|
|
2017
|
+
"propDefaultStr": "hello",
|
|
2018
|
+
"propOptionalStr": None,
|
|
2019
|
+
"propDefaultInt": 5,
|
|
2020
|
+
"propOptionalInt": None,
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
|
|
2024
|
+
def test_pass_models_in_dict():
|
|
2025
|
+
class Inner(Model):
|
|
2026
|
+
str_property: str = rest_field(name="strProperty")
|
|
2027
|
+
|
|
2028
|
+
@overload
|
|
2029
|
+
def __init__(
|
|
2030
|
+
self,
|
|
2031
|
+
*,
|
|
2032
|
+
str_property: str,
|
|
2033
|
+
): ...
|
|
2034
|
+
|
|
2035
|
+
@overload
|
|
2036
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2037
|
+
|
|
2038
|
+
def __init__(self, *args, **kwargs):
|
|
2039
|
+
super().__init__(*args, **kwargs)
|
|
2040
|
+
|
|
2041
|
+
class Outer(Model):
|
|
2042
|
+
inner_property: Inner = rest_field(name="innerProperty")
|
|
2043
|
+
|
|
2044
|
+
@overload
|
|
2045
|
+
def __init__(
|
|
2046
|
+
self,
|
|
2047
|
+
*,
|
|
2048
|
+
inner_property: Inner,
|
|
2049
|
+
): ...
|
|
2050
|
+
|
|
2051
|
+
@overload
|
|
2052
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2053
|
+
|
|
2054
|
+
def __init__(self, *args, **kwargs):
|
|
2055
|
+
super().__init__(*args, **kwargs)
|
|
2056
|
+
|
|
2057
|
+
def _tests(model: Outer):
|
|
2058
|
+
assert (
|
|
2059
|
+
{"innerProperty": {"strProperty": "hello"}}
|
|
2060
|
+
== {"innerProperty": Inner(str_property="hello")}
|
|
2061
|
+
== {"innerProperty": Inner({"strProperty": "hello"})}
|
|
2062
|
+
== Outer(inner_property=Inner(str_property="hello"))
|
|
2063
|
+
== Outer(inner_property=Inner({"strProperty": "hello"}))
|
|
2064
|
+
== Outer({"innerProperty": {"strProperty": "hello"}})
|
|
2065
|
+
== Outer({"innerProperty": Inner(str_property="hello")})
|
|
2066
|
+
== Outer({"innerProperty": Inner({"strProperty": "hello"})})
|
|
2067
|
+
== model
|
|
2068
|
+
)
|
|
2069
|
+
|
|
2070
|
+
_tests(Outer(inner_property=Inner(str_property="hello")))
|
|
2071
|
+
_tests(Outer(inner_property=Inner({"strProperty": "hello"})))
|
|
2072
|
+
_tests(Outer({"innerProperty": {"strProperty": "hello"}}))
|
|
2073
|
+
_tests(Outer({"innerProperty": Inner(str_property="hello")}))
|
|
2074
|
+
_tests(Outer({"innerProperty": Inner({"strProperty": "hello"})}))
|
|
2075
|
+
|
|
2076
|
+
|
|
2077
|
+
def test_mutability_list():
|
|
2078
|
+
class Inner(Model):
|
|
2079
|
+
str_property: str = rest_field(name="strProperty")
|
|
2080
|
+
|
|
2081
|
+
@overload
|
|
2082
|
+
def __init__(
|
|
2083
|
+
self,
|
|
2084
|
+
*,
|
|
2085
|
+
str_property: str,
|
|
2086
|
+
): ...
|
|
2087
|
+
|
|
2088
|
+
@overload
|
|
2089
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2090
|
+
|
|
2091
|
+
def __init__(self, *args, **kwargs):
|
|
2092
|
+
super().__init__(*args, **kwargs)
|
|
2093
|
+
|
|
2094
|
+
class Middle(Model):
|
|
2095
|
+
inner_property: List[Inner] = rest_field(name="innerProperty")
|
|
2096
|
+
prop: str = rest_field()
|
|
2097
|
+
|
|
2098
|
+
@overload
|
|
2099
|
+
def __init__(
|
|
2100
|
+
self,
|
|
2101
|
+
*,
|
|
2102
|
+
inner_property: List[Inner],
|
|
2103
|
+
prop: str,
|
|
2104
|
+
): ...
|
|
2105
|
+
|
|
2106
|
+
@overload
|
|
2107
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2108
|
+
|
|
2109
|
+
def __init__(self, *args, **kwargs):
|
|
2110
|
+
super().__init__(*args, **kwargs)
|
|
2111
|
+
|
|
2112
|
+
class Outer(Model):
|
|
2113
|
+
middle_property: Middle = rest_field(name="middleProperty")
|
|
2114
|
+
|
|
2115
|
+
@overload
|
|
2116
|
+
def __init__(
|
|
2117
|
+
self,
|
|
2118
|
+
*,
|
|
2119
|
+
middle_property: Model,
|
|
2120
|
+
): ...
|
|
2121
|
+
|
|
2122
|
+
@overload
|
|
2123
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2124
|
+
|
|
2125
|
+
def __init__(self, *args, **kwargs):
|
|
2126
|
+
super().__init__(*args, **kwargs)
|
|
2127
|
+
|
|
2128
|
+
original_dict = {
|
|
2129
|
+
"middleProperty": {
|
|
2130
|
+
"innerProperty": [{"strProperty": "hello"}],
|
|
2131
|
+
"prop": "original",
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
model = Outer(original_dict)
|
|
2135
|
+
assert model is not original_dict
|
|
2136
|
+
|
|
2137
|
+
# set with dict syntax
|
|
2138
|
+
assert model.middle_property is model["middleProperty"]
|
|
2139
|
+
middle_property = model.middle_property
|
|
2140
|
+
middle_property["prop"] = "new"
|
|
2141
|
+
assert model["middleProperty"] is model.middle_property is middle_property
|
|
2142
|
+
assert model["middleProperty"]["prop"] == model.middle_property.prop == "new"
|
|
2143
|
+
|
|
2144
|
+
# set with attr syntax
|
|
2145
|
+
middle_property.prop = "newest"
|
|
2146
|
+
assert model["middleProperty"] is model.middle_property is middle_property
|
|
2147
|
+
assert model["middleProperty"]["prop"] == model.middle_property.prop == "newest"
|
|
2148
|
+
|
|
2149
|
+
# modify innerproperty list
|
|
2150
|
+
assert model["middleProperty"]["innerProperty"][0] is model.middle_property.inner_property[0]
|
|
2151
|
+
assert (
|
|
2152
|
+
model["middleProperty"]["innerProperty"][0]
|
|
2153
|
+
is model.middle_property["innerProperty"][0]
|
|
2154
|
+
is model["middleProperty"].inner_property[0]
|
|
2155
|
+
is model.middle_property.inner_property[0]
|
|
2156
|
+
)
|
|
2157
|
+
inner_property = model["middleProperty"]["innerProperty"][0]
|
|
2158
|
+
|
|
2159
|
+
# set with dict syntax
|
|
2160
|
+
inner_property["strProperty"] = "nihao"
|
|
2161
|
+
assert (
|
|
2162
|
+
model["middleProperty"]["innerProperty"][0]
|
|
2163
|
+
is model.middle_property["innerProperty"][0]
|
|
2164
|
+
is model["middleProperty"].inner_property[0]
|
|
2165
|
+
is model.middle_property.inner_property[0]
|
|
2166
|
+
)
|
|
2167
|
+
assert (
|
|
2168
|
+
model["middleProperty"]["innerProperty"][0]["strProperty"]
|
|
2169
|
+
== model.middle_property["innerProperty"][0]["strProperty"]
|
|
2170
|
+
== model["middleProperty"].inner_property[0]["strProperty"]
|
|
2171
|
+
== model.middle_property.inner_property[0]["strProperty"]
|
|
2172
|
+
== model["middleProperty"]["innerProperty"][0].str_property
|
|
2173
|
+
== model.middle_property["innerProperty"][0].str_property
|
|
2174
|
+
== model["middleProperty"].inner_property[0].str_property
|
|
2175
|
+
== model.middle_property.inner_property[0].str_property
|
|
2176
|
+
== "nihao"
|
|
2177
|
+
)
|
|
2178
|
+
|
|
2179
|
+
|
|
2180
|
+
def test_mutability_dict():
|
|
2181
|
+
class Inner(Model):
|
|
2182
|
+
str_property: str = rest_field(name="strProperty")
|
|
2183
|
+
|
|
2184
|
+
@overload
|
|
2185
|
+
def __init__(
|
|
2186
|
+
self,
|
|
2187
|
+
*,
|
|
2188
|
+
str_property: str,
|
|
2189
|
+
): ...
|
|
2190
|
+
|
|
2191
|
+
@overload
|
|
2192
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2193
|
+
|
|
2194
|
+
def __init__(self, *args, **kwargs):
|
|
2195
|
+
super().__init__(*args, **kwargs)
|
|
2196
|
+
|
|
2197
|
+
class Middle(Model):
|
|
2198
|
+
inner_property: Dict[str, Inner] = rest_field(name="innerProperty")
|
|
2199
|
+
prop: str = rest_field()
|
|
2200
|
+
|
|
2201
|
+
@overload
|
|
2202
|
+
def __init__(
|
|
2203
|
+
self,
|
|
2204
|
+
*,
|
|
2205
|
+
inner_property: Dict[str, Inner],
|
|
2206
|
+
prop: str,
|
|
2207
|
+
): ...
|
|
2208
|
+
|
|
2209
|
+
@overload
|
|
2210
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2211
|
+
|
|
2212
|
+
def __init__(self, *args, **kwargs):
|
|
2213
|
+
super().__init__(*args, **kwargs)
|
|
2214
|
+
|
|
2215
|
+
class Outer(Model):
|
|
2216
|
+
middle_property: Middle = rest_field(name="middleProperty")
|
|
2217
|
+
|
|
2218
|
+
@overload
|
|
2219
|
+
def __init__(
|
|
2220
|
+
self,
|
|
2221
|
+
*,
|
|
2222
|
+
middle_property: Model,
|
|
2223
|
+
): ...
|
|
2224
|
+
|
|
2225
|
+
@overload
|
|
2226
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2227
|
+
|
|
2228
|
+
def __init__(self, *args, **kwargs):
|
|
2229
|
+
super().__init__(*args, **kwargs)
|
|
2230
|
+
|
|
2231
|
+
original_dict = {
|
|
2232
|
+
"middleProperty": {
|
|
2233
|
+
"innerProperty": {"inner": {"strProperty": "hello"}},
|
|
2234
|
+
"prop": "original",
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
model = Outer(original_dict)
|
|
2238
|
+
assert model is not original_dict
|
|
2239
|
+
|
|
2240
|
+
# set with dict syntax
|
|
2241
|
+
assert model.middle_property is model["middleProperty"]
|
|
2242
|
+
middle_property = model.middle_property
|
|
2243
|
+
middle_property["prop"] = "new"
|
|
2244
|
+
assert model["middleProperty"] is model.middle_property is middle_property
|
|
2245
|
+
assert (
|
|
2246
|
+
model["middleProperty"]["prop"]
|
|
2247
|
+
== model["middleProperty"].prop
|
|
2248
|
+
== model.middle_property.prop
|
|
2249
|
+
== model.middle_property["prop"]
|
|
2250
|
+
== "new"
|
|
2251
|
+
)
|
|
2252
|
+
|
|
2253
|
+
# set with attr syntax
|
|
2254
|
+
middle_property.prop = "newest"
|
|
2255
|
+
assert model["middleProperty"] is model.middle_property is middle_property
|
|
2256
|
+
assert model["middleProperty"]["prop"] == model.middle_property.prop == "newest"
|
|
2257
|
+
|
|
2258
|
+
# modify innerproperty list
|
|
2259
|
+
assert model["middleProperty"]["innerProperty"]["inner"] is model.middle_property.inner_property["inner"]
|
|
2260
|
+
assert (
|
|
2261
|
+
model["middleProperty"]["innerProperty"]["inner"]
|
|
2262
|
+
is model.middle_property["innerProperty"]["inner"]
|
|
2263
|
+
is model["middleProperty"].inner_property["inner"]
|
|
2264
|
+
is model.middle_property.inner_property["inner"]
|
|
2265
|
+
)
|
|
2266
|
+
inner_property = model["middleProperty"]["innerProperty"]["inner"]
|
|
2267
|
+
|
|
2268
|
+
# set with dict syntax
|
|
2269
|
+
inner_property["strProperty"] = "nihao"
|
|
2270
|
+
assert (
|
|
2271
|
+
model["middleProperty"]["innerProperty"]["inner"]
|
|
2272
|
+
is model.middle_property["innerProperty"]["inner"]
|
|
2273
|
+
is model["middleProperty"].inner_property["inner"]
|
|
2274
|
+
is model.middle_property.inner_property["inner"]
|
|
2275
|
+
)
|
|
2276
|
+
assert (
|
|
2277
|
+
model["middleProperty"]["innerProperty"]["inner"]["strProperty"]
|
|
2278
|
+
== model.middle_property["innerProperty"]["inner"]["strProperty"]
|
|
2279
|
+
== model["middleProperty"].inner_property["inner"]["strProperty"]
|
|
2280
|
+
== model.middle_property.inner_property["inner"]["strProperty"]
|
|
2281
|
+
== model["middleProperty"]["innerProperty"]["inner"].str_property
|
|
2282
|
+
== model.middle_property["innerProperty"]["inner"].str_property
|
|
2283
|
+
== model["middleProperty"].inner_property["inner"].str_property
|
|
2284
|
+
== model.middle_property.inner_property["inner"].str_property
|
|
2285
|
+
== "nihao"
|
|
2286
|
+
)
|
|
2287
|
+
|
|
2288
|
+
|
|
2289
|
+
def test_del_model():
|
|
2290
|
+
class TestModel(Model):
|
|
2291
|
+
x: Optional[int] = rest_field()
|
|
2292
|
+
|
|
2293
|
+
my_dict = {}
|
|
2294
|
+
my_dict["x"] = None
|
|
2295
|
+
|
|
2296
|
+
assert my_dict["x"] is None
|
|
2297
|
+
|
|
2298
|
+
my_model = TestModel({})
|
|
2299
|
+
my_model["x"] = None
|
|
2300
|
+
|
|
2301
|
+
assert my_model["x"] is my_model.x is None
|
|
2302
|
+
|
|
2303
|
+
my_model = TestModel({"x": 7})
|
|
2304
|
+
my_model.x = None
|
|
2305
|
+
|
|
2306
|
+
assert "x" not in my_model
|
|
2307
|
+
assert my_model.x is None
|
|
2308
|
+
|
|
2309
|
+
with pytest.raises(KeyError):
|
|
2310
|
+
del my_model["x"]
|
|
2311
|
+
my_model.x = 8
|
|
2312
|
+
|
|
2313
|
+
del my_model["x"]
|
|
2314
|
+
assert "x" not in my_model
|
|
2315
|
+
assert my_model.x is my_model.get("x") is None
|
|
2316
|
+
|
|
2317
|
+
with pytest.raises(AttributeError):
|
|
2318
|
+
del my_model.x
|
|
2319
|
+
my_model.x = None
|
|
2320
|
+
assert "x" not in my_model
|
|
2321
|
+
assert my_model.x is my_model.get("x") is None
|
|
2322
|
+
|
|
2323
|
+
|
|
2324
|
+
def test_pop_model():
|
|
2325
|
+
class Inner(Model):
|
|
2326
|
+
str_property: str = rest_field(name="strProperty")
|
|
2327
|
+
|
|
2328
|
+
@overload
|
|
2329
|
+
def __init__(
|
|
2330
|
+
self,
|
|
2331
|
+
*,
|
|
2332
|
+
str_property: str,
|
|
2333
|
+
): ...
|
|
2334
|
+
|
|
2335
|
+
@overload
|
|
2336
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2337
|
+
|
|
2338
|
+
def __init__(self, *args, **kwargs):
|
|
2339
|
+
super().__init__(*args, **kwargs)
|
|
2340
|
+
|
|
2341
|
+
class Middle(Model):
|
|
2342
|
+
inner_property: Dict[str, Inner] = rest_field(name="innerProperty")
|
|
2343
|
+
prop: str = rest_field()
|
|
2344
|
+
|
|
2345
|
+
@overload
|
|
2346
|
+
def __init__(
|
|
2347
|
+
self,
|
|
2348
|
+
*,
|
|
2349
|
+
inner_property: Dict[str, Inner],
|
|
2350
|
+
prop: str,
|
|
2351
|
+
): ...
|
|
2352
|
+
|
|
2353
|
+
@overload
|
|
2354
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2355
|
+
|
|
2356
|
+
def __init__(self, *args, **kwargs):
|
|
2357
|
+
super().__init__(*args, **kwargs)
|
|
2358
|
+
|
|
2359
|
+
class Outer(Model):
|
|
2360
|
+
middle_property: Middle = rest_field(name="middleProperty")
|
|
2361
|
+
|
|
2362
|
+
@overload
|
|
2363
|
+
def __init__(
|
|
2364
|
+
self,
|
|
2365
|
+
*,
|
|
2366
|
+
middle_property: Model,
|
|
2367
|
+
): ...
|
|
2368
|
+
|
|
2369
|
+
@overload
|
|
2370
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2371
|
+
|
|
2372
|
+
def __init__(self, *args, **kwargs):
|
|
2373
|
+
super().__init__(*args, **kwargs)
|
|
2374
|
+
|
|
2375
|
+
original_dict = {
|
|
2376
|
+
"middleProperty": {
|
|
2377
|
+
"innerProperty": {"inner": {"strProperty": "hello"}},
|
|
2378
|
+
"prop": "original",
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
model_dict = Outer(original_dict) # model we will access with dict syntax
|
|
2382
|
+
model_attr = Outer(original_dict) # model we will access with attr syntax
|
|
2383
|
+
|
|
2384
|
+
assert model_dict is not original_dict is not model_attr
|
|
2385
|
+
assert (
|
|
2386
|
+
original_dict["middleProperty"]["innerProperty"]["inner"].pop("strProperty")
|
|
2387
|
+
== model_dict["middleProperty"]["innerProperty"]["inner"].pop("strProperty")
|
|
2388
|
+
== model_attr.middle_property.inner_property["inner"].pop("strProperty")
|
|
2389
|
+
== "hello"
|
|
2390
|
+
)
|
|
2391
|
+
|
|
2392
|
+
with pytest.raises(KeyError):
|
|
2393
|
+
original_dict["middleProperty"]["innerProperty"]["inner"].pop("strProperty")
|
|
2394
|
+
with pytest.raises(KeyError):
|
|
2395
|
+
model_dict["middleProperty"]["innerProperty"]["inner"].pop("strProperty")
|
|
2396
|
+
with pytest.raises(KeyError):
|
|
2397
|
+
model_attr.middle_property.inner_property["inner"].pop("strProperty")
|
|
2398
|
+
|
|
2399
|
+
|
|
2400
|
+
def test_contains():
|
|
2401
|
+
class ParentA(Model):
|
|
2402
|
+
a_prop: str = rest_field(name="aProp")
|
|
2403
|
+
|
|
2404
|
+
@overload
|
|
2405
|
+
def __init__(
|
|
2406
|
+
self,
|
|
2407
|
+
*,
|
|
2408
|
+
a_prop: str,
|
|
2409
|
+
): ...
|
|
2410
|
+
|
|
2411
|
+
@overload
|
|
2412
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2413
|
+
|
|
2414
|
+
def __init__(self, *args, **kwargs):
|
|
2415
|
+
super().__init__(*args, **kwargs)
|
|
2416
|
+
|
|
2417
|
+
class ParentB(Model):
|
|
2418
|
+
b_prop: str = rest_field(name="bProp")
|
|
2419
|
+
|
|
2420
|
+
@overload
|
|
2421
|
+
def __init__(
|
|
2422
|
+
self,
|
|
2423
|
+
*,
|
|
2424
|
+
b_prop: str,
|
|
2425
|
+
): ...
|
|
2426
|
+
|
|
2427
|
+
@overload
|
|
2428
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2429
|
+
|
|
2430
|
+
def __init__(self, *args, **kwargs):
|
|
2431
|
+
super().__init__(*args, **kwargs)
|
|
2432
|
+
|
|
2433
|
+
class ChildC(ParentA, ParentB):
|
|
2434
|
+
c_prop: str = rest_field(name="cProp")
|
|
2435
|
+
|
|
2436
|
+
@overload
|
|
2437
|
+
def __init__(
|
|
2438
|
+
self,
|
|
2439
|
+
*,
|
|
2440
|
+
a_prop: str,
|
|
2441
|
+
b_prop: str,
|
|
2442
|
+
c_prop: str,
|
|
2443
|
+
): ...
|
|
2444
|
+
|
|
2445
|
+
@overload
|
|
2446
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2447
|
+
|
|
2448
|
+
def __init__(self, *args, **kwargs):
|
|
2449
|
+
super().__init__(*args, **kwargs)
|
|
2450
|
+
|
|
2451
|
+
parent_a_dict = {"aProp": "a"}
|
|
2452
|
+
assert "aProp" in parent_a_dict
|
|
2453
|
+
|
|
2454
|
+
parent_a = ParentA(parent_a_dict)
|
|
2455
|
+
assert "aProp" in parent_a
|
|
2456
|
+
assert not "a_prop" in parent_a
|
|
2457
|
+
|
|
2458
|
+
parent_a.a_prop = None # clear it out
|
|
2459
|
+
assert "aProp" not in parent_a
|
|
2460
|
+
|
|
2461
|
+
parent_b_dict = {"bProp": "b"}
|
|
2462
|
+
assert "bProp" in parent_b_dict
|
|
2463
|
+
|
|
2464
|
+
parent_b = ParentB(parent_b_dict)
|
|
2465
|
+
assert "bProp" in parent_b
|
|
2466
|
+
assert "b_prop" not in parent_b
|
|
2467
|
+
|
|
2468
|
+
parent_b.b_prop = None # clear it out
|
|
2469
|
+
assert "bProp" not in parent_b
|
|
2470
|
+
|
|
2471
|
+
props = ["aProp", "bProp", "cProp"]
|
|
2472
|
+
child_c_dict = {"aProp": "a", "bProp": "b", "cProp": "c"}
|
|
2473
|
+
assert all(p for p in props if p in child_c_dict)
|
|
2474
|
+
|
|
2475
|
+
child_c = ChildC(child_c_dict)
|
|
2476
|
+
assert all(p for p in props if p in child_c)
|
|
2477
|
+
assert not any(p for p in ["a_prop", "b_prop", "c_prop"] if p in child_c)
|
|
2478
|
+
|
|
2479
|
+
child_c.a_prop = None
|
|
2480
|
+
child_c.b_prop = None
|
|
2481
|
+
child_c.c_prop = None
|
|
2482
|
+
|
|
2483
|
+
assert not any(p for p in props if p in child_c)
|
|
2484
|
+
|
|
2485
|
+
|
|
2486
|
+
def test_iter():
|
|
2487
|
+
dict_response = {
|
|
2488
|
+
"platformUpdateDomainCount": 5,
|
|
2489
|
+
"platformFaultDomainCount": 3,
|
|
2490
|
+
"virtualMachines": [],
|
|
2491
|
+
}
|
|
2492
|
+
assert isinstance(iter(dict_response), Iterable)
|
|
2493
|
+
model = BasicResource(dict_response)
|
|
2494
|
+
assert isinstance(iter(model), Iterable)
|
|
2495
|
+
|
|
2496
|
+
assert (
|
|
2497
|
+
list(iter(dict_response))
|
|
2498
|
+
== list(iter(model))
|
|
2499
|
+
== ["platformUpdateDomainCount", "platformFaultDomainCount", "virtualMachines"]
|
|
2500
|
+
)
|
|
2501
|
+
|
|
2502
|
+
|
|
2503
|
+
def test_len():
|
|
2504
|
+
dict_response = {
|
|
2505
|
+
"platformUpdateDomainCount": 5,
|
|
2506
|
+
"platformFaultDomainCount": 3,
|
|
2507
|
+
"virtualMachines": [],
|
|
2508
|
+
}
|
|
2509
|
+
model = BasicResource(dict_response)
|
|
2510
|
+
assert len(dict_response) == len(model) == 3
|
|
2511
|
+
|
|
2512
|
+
dict_response.pop("platformUpdateDomainCount")
|
|
2513
|
+
model.pop("platformUpdateDomainCount")
|
|
2514
|
+
assert len(dict_response) == len(model) == 2
|
|
2515
|
+
|
|
2516
|
+
|
|
2517
|
+
def test_keys():
|
|
2518
|
+
class Inner(Model):
|
|
2519
|
+
str_prop: str = rest_field(name="strProp")
|
|
2520
|
+
|
|
2521
|
+
class Outer(Model):
|
|
2522
|
+
inner_prop: Inner = rest_field(name="innerProp")
|
|
2523
|
+
|
|
2524
|
+
outer_dict = {"innerProp": {"strProp": "hello"}}
|
|
2525
|
+
outer = Outer(outer_dict)
|
|
2526
|
+
assert outer.keys() == outer_dict.keys()
|
|
2527
|
+
outer_dict["newProp"] = "hello"
|
|
2528
|
+
outer["newProp"] = "hello"
|
|
2529
|
+
|
|
2530
|
+
assert outer.keys() == outer_dict.keys()
|
|
2531
|
+
|
|
2532
|
+
outer_dict.pop("newProp")
|
|
2533
|
+
outer.pop("newProp")
|
|
2534
|
+
assert outer_dict.keys() == outer.keys()
|
|
2535
|
+
|
|
2536
|
+
|
|
2537
|
+
def test_values():
|
|
2538
|
+
class Inner(Model):
|
|
2539
|
+
str_prop: str = rest_field(name="strProp")
|
|
2540
|
+
|
|
2541
|
+
class Outer(Model):
|
|
2542
|
+
inner_prop: Inner = rest_field(name="innerProp")
|
|
2543
|
+
|
|
2544
|
+
outer_dict = {"innerProp": {"strProp": "hello"}}
|
|
2545
|
+
outer = Outer(outer_dict)
|
|
2546
|
+
|
|
2547
|
+
assert list(outer.values()) == list(outer_dict.values())
|
|
2548
|
+
assert len(outer.values()) == len(outer_dict.values()) == 1
|
|
2549
|
+
assert list(outer.values())[0]["strProp"] == list(outer_dict.values())[0]["strProp"] == "hello"
|
|
2550
|
+
|
|
2551
|
+
outer_dict["innerProp"]["strProp"] = "goodbye"
|
|
2552
|
+
outer.inner_prop.str_prop = "goodbye"
|
|
2553
|
+
|
|
2554
|
+
assert list(outer.inner_prop.values()) == list(outer_dict["innerProp"].values())
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
def test_items():
|
|
2558
|
+
class Inner(Model):
|
|
2559
|
+
str_prop: str = rest_field(name="strProp")
|
|
2560
|
+
|
|
2561
|
+
class Outer(Model):
|
|
2562
|
+
inner_prop: Inner = rest_field(name="innerProp")
|
|
2563
|
+
|
|
2564
|
+
outer_dict = {"innerProp": {"strProp": "hello"}}
|
|
2565
|
+
outer = Outer(outer_dict)
|
|
2566
|
+
|
|
2567
|
+
assert list(outer.items()) == list(outer_dict.items())
|
|
2568
|
+
|
|
2569
|
+
outer_dict["innerProp"]["strProp"] = "goodbye"
|
|
2570
|
+
outer.inner_prop.str_prop = "goodbye"
|
|
2571
|
+
|
|
2572
|
+
assert list(outer.inner_prop.items()) == list(outer_dict["innerProp"].items())
|
|
2573
|
+
|
|
2574
|
+
outer_dict["newProp"] = "bonjour"
|
|
2575
|
+
outer["newProp"] = "bonjour"
|
|
2576
|
+
|
|
2577
|
+
assert list(outer.items()) == list(outer_dict.items())
|
|
2578
|
+
|
|
2579
|
+
|
|
2580
|
+
def test_get():
|
|
2581
|
+
class MyModel(Model):
|
|
2582
|
+
prop: str = rest_field()
|
|
2583
|
+
rest_prop: str = rest_field(name="restProp")
|
|
2584
|
+
|
|
2585
|
+
my_dict = {"prop": "hello", "restProp": "bonjour"}
|
|
2586
|
+
my_model = MyModel(my_dict)
|
|
2587
|
+
|
|
2588
|
+
assert my_dict.get("prop") == my_model.get("prop") == "hello"
|
|
2589
|
+
my_dict["prop"] = "nihao"
|
|
2590
|
+
my_model.prop = "nihao"
|
|
2591
|
+
|
|
2592
|
+
assert my_dict.get("prop") == my_model.get("prop") == "nihao"
|
|
2593
|
+
|
|
2594
|
+
my_dict["restProp"] = "buongiorno"
|
|
2595
|
+
my_model.rest_prop = "buongiorno"
|
|
2596
|
+
|
|
2597
|
+
assert my_dict.get("restProp") == my_model.get("restProp") == "buongiorno"
|
|
2598
|
+
assert my_dict.get("rest_prop") is None # attr case should not work here
|
|
2599
|
+
|
|
2600
|
+
my_dict["newProp"] = "i'm new"
|
|
2601
|
+
my_model["newProp"] = "i'm new"
|
|
2602
|
+
|
|
2603
|
+
assert my_dict.get("newProp") == my_model.get("newProp") == "i'm new"
|
|
2604
|
+
assert my_dict.get("nonexistent") is my_model.get("nonexistent") is None
|
|
2605
|
+
|
|
2606
|
+
assert my_dict.get("nonexistent", 0) == my_model.get("nonexistent", 0) == 0
|
|
2607
|
+
|
|
2608
|
+
|
|
2609
|
+
def test_pop():
|
|
2610
|
+
class MyModel(Model):
|
|
2611
|
+
prop: str = rest_field()
|
|
2612
|
+
rest_prop: str = rest_field(name="restProp")
|
|
2613
|
+
|
|
2614
|
+
my_dict = {"prop": "hello", "restProp": "bonjour"}
|
|
2615
|
+
my_model = MyModel(my_dict)
|
|
2616
|
+
|
|
2617
|
+
assert my_dict.pop("prop") == my_model.pop("prop") == "hello"
|
|
2618
|
+
with pytest.raises(KeyError):
|
|
2619
|
+
my_dict.pop("prop")
|
|
2620
|
+
with pytest.raises(KeyError):
|
|
2621
|
+
my_model.pop("prop")
|
|
2622
|
+
|
|
2623
|
+
my_dict["prop"] = "nihao"
|
|
2624
|
+
my_model.prop = "nihao"
|
|
2625
|
+
|
|
2626
|
+
assert my_dict.pop("prop") == my_model.pop("prop") == "nihao"
|
|
2627
|
+
|
|
2628
|
+
with pytest.raises(KeyError):
|
|
2629
|
+
my_dict.pop("prop")
|
|
2630
|
+
with pytest.raises(KeyError):
|
|
2631
|
+
my_model.pop("prop")
|
|
2632
|
+
|
|
2633
|
+
my_dict["restProp"] = "buongiorno"
|
|
2634
|
+
my_model.rest_prop = "buongiorno"
|
|
2635
|
+
|
|
2636
|
+
assert my_dict.pop("restProp") == my_model.pop("restProp") == "buongiorno"
|
|
2637
|
+
with pytest.raises(KeyError):
|
|
2638
|
+
my_dict.pop("rest_prop") # attr case should not work here
|
|
2639
|
+
|
|
2640
|
+
my_dict["newProp"] = "i'm new"
|
|
2641
|
+
my_model["newProp"] = "i'm new"
|
|
2642
|
+
|
|
2643
|
+
assert my_dict.pop("newProp") == my_model.pop("newProp") == "i'm new"
|
|
2644
|
+
assert my_dict.pop("nonexistent", 0) == my_model.pop("nonexistent", 0) == 0
|
|
2645
|
+
|
|
2646
|
+
|
|
2647
|
+
def test_popitem():
|
|
2648
|
+
class ModelA(Model):
|
|
2649
|
+
a_str_prop: str = rest_field(name="aStrProp")
|
|
2650
|
+
|
|
2651
|
+
class ModelB(Model):
|
|
2652
|
+
b_str_prop: str = rest_field(name="bStrProp")
|
|
2653
|
+
|
|
2654
|
+
class ModelC(Model):
|
|
2655
|
+
c_str_prop: str = rest_field(name="cStrProp")
|
|
2656
|
+
|
|
2657
|
+
class MainModel(Model):
|
|
2658
|
+
a_prop: ModelA = rest_field(name="aProp")
|
|
2659
|
+
b_prop: ModelB = rest_field(name="bProp")
|
|
2660
|
+
c_prop: ModelC = rest_field(name="cProp")
|
|
2661
|
+
|
|
2662
|
+
my_dict = {
|
|
2663
|
+
"aProp": {"aStrProp": "a"},
|
|
2664
|
+
"bProp": {"bStrProp": "b"},
|
|
2665
|
+
"cProp": {"cStrProp": "c"},
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
def _tests(my_dict: Dict[str, Any], my_model: MainModel):
|
|
2669
|
+
my_dict = copy.deepcopy(my_dict) # so we don't get rid of the dict each time we run tests
|
|
2670
|
+
|
|
2671
|
+
# pop c prop
|
|
2672
|
+
dict_popitem = my_dict.popitem()
|
|
2673
|
+
model_popitem = my_model.popitem()
|
|
2674
|
+
assert dict_popitem[0] == model_popitem[0] == "cProp"
|
|
2675
|
+
assert dict_popitem[1]["cStrProp"] == model_popitem[1]["cStrProp"] == model_popitem[1].c_str_prop == "c"
|
|
2676
|
+
|
|
2677
|
+
# pop b prop
|
|
2678
|
+
dict_popitem = my_dict.popitem()
|
|
2679
|
+
model_popitem = my_model.popitem()
|
|
2680
|
+
assert dict_popitem[0] == model_popitem[0] == "bProp"
|
|
2681
|
+
assert dict_popitem[1]["bStrProp"] == model_popitem[1]["bStrProp"] == model_popitem[1].b_str_prop == "b"
|
|
2682
|
+
|
|
2683
|
+
# pop a prop
|
|
2684
|
+
dict_popitem = my_dict.popitem()
|
|
2685
|
+
model_popitem = my_model.popitem()
|
|
2686
|
+
assert dict_popitem[0] == model_popitem[0] == "aProp"
|
|
2687
|
+
assert dict_popitem[1]["aStrProp"] == model_popitem[1]["aStrProp"] == model_popitem[1].a_str_prop == "a"
|
|
2688
|
+
|
|
2689
|
+
with pytest.raises(KeyError):
|
|
2690
|
+
my_dict.popitem()
|
|
2691
|
+
|
|
2692
|
+
with pytest.raises(KeyError):
|
|
2693
|
+
my_model.popitem()
|
|
2694
|
+
|
|
2695
|
+
_tests(my_dict, MainModel(my_dict))
|
|
2696
|
+
_tests(
|
|
2697
|
+
my_dict,
|
|
2698
|
+
MainModel(
|
|
2699
|
+
a_prop=ModelA(a_str_prop="a"),
|
|
2700
|
+
b_prop=ModelB(b_str_prop="b"),
|
|
2701
|
+
c_prop=ModelC(c_str_prop="c"),
|
|
2702
|
+
),
|
|
2703
|
+
)
|
|
2704
|
+
|
|
2705
|
+
|
|
2706
|
+
def test_clear():
|
|
2707
|
+
class ModelA(Model):
|
|
2708
|
+
a_str_prop: str = rest_field(name="aStrProp")
|
|
2709
|
+
|
|
2710
|
+
class ModelB(Model):
|
|
2711
|
+
b_str_prop: str = rest_field(name="bStrProp")
|
|
2712
|
+
|
|
2713
|
+
class ModelC(Model):
|
|
2714
|
+
c_str_prop: str = rest_field(name="cStrProp")
|
|
2715
|
+
|
|
2716
|
+
class MainModel(Model):
|
|
2717
|
+
a_prop: ModelA = rest_field(name="aProp")
|
|
2718
|
+
b_prop: ModelB = rest_field(name="bProp")
|
|
2719
|
+
c_prop: ModelC = rest_field(name="cProp")
|
|
2720
|
+
|
|
2721
|
+
my_dict = {
|
|
2722
|
+
"aProp": {"aStrProp": "a"},
|
|
2723
|
+
"bProp": {"bStrProp": "b"},
|
|
2724
|
+
"cProp": {"cStrProp": "c"},
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
def _tests(my_dict: Dict[str, Any], my_model: MainModel):
|
|
2728
|
+
my_dict = copy.deepcopy(my_dict) # so we don't get rid of the dict each time we run tests
|
|
2729
|
+
|
|
2730
|
+
assert my_dict["aProp"] == my_model.a_prop == my_model["aProp"] == {"aStrProp": "a"}
|
|
2731
|
+
my_dict.clear()
|
|
2732
|
+
my_model.clear()
|
|
2733
|
+
assert my_dict == my_model == {}
|
|
2734
|
+
|
|
2735
|
+
assert my_model.a_prop is None
|
|
2736
|
+
assert my_model.b_prop is None
|
|
2737
|
+
assert my_model.c_prop is None
|
|
2738
|
+
|
|
2739
|
+
my_dict.clear()
|
|
2740
|
+
my_model.clear()
|
|
2741
|
+
assert my_dict == my_model == {}
|
|
2742
|
+
|
|
2743
|
+
_tests(my_dict, MainModel(my_dict))
|
|
2744
|
+
_tests(
|
|
2745
|
+
my_dict,
|
|
2746
|
+
MainModel(
|
|
2747
|
+
a_prop=ModelA(a_str_prop="a"),
|
|
2748
|
+
b_prop=ModelB(b_str_prop="b"),
|
|
2749
|
+
c_prop=ModelC(c_str_prop="c"),
|
|
2750
|
+
),
|
|
2751
|
+
)
|
|
2752
|
+
|
|
2753
|
+
|
|
2754
|
+
def test_update():
|
|
2755
|
+
class ModelA(Model):
|
|
2756
|
+
a_str_prop: str = rest_field(name="aStrProp")
|
|
2757
|
+
|
|
2758
|
+
class ModelB(Model):
|
|
2759
|
+
b_str_prop: str = rest_field(name="bStrProp")
|
|
2760
|
+
|
|
2761
|
+
class ModelC(Model):
|
|
2762
|
+
c_str_prop: str = rest_field(name="cStrProp")
|
|
2763
|
+
|
|
2764
|
+
class MainModel(Model):
|
|
2765
|
+
a_prop: ModelA = rest_field(name="aProp")
|
|
2766
|
+
b_prop: ModelB = rest_field(name="bProp")
|
|
2767
|
+
c_prop: ModelC = rest_field(name="cProp")
|
|
2768
|
+
|
|
2769
|
+
my_dict = {
|
|
2770
|
+
"aProp": {"aStrProp": "a"},
|
|
2771
|
+
"bProp": {"bStrProp": "b"},
|
|
2772
|
+
"cProp": {"cStrProp": "c"},
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
def _tests(my_dict: Dict[str, Any], my_model: MainModel):
|
|
2776
|
+
my_dict = copy.deepcopy(my_dict) # so we don't get rid of the dict each time we run tests
|
|
2777
|
+
|
|
2778
|
+
assert my_dict["aProp"] == my_model.a_prop == my_model["aProp"] == {"aStrProp": "a"}
|
|
2779
|
+
my_dict.update({"aProp": {"aStrProp": "newA"}})
|
|
2780
|
+
my_model.a_prop.update({"aStrProp": "newA"})
|
|
2781
|
+
assert my_dict["aProp"] == my_model.a_prop == my_model["aProp"] == {"aStrProp": "newA"}
|
|
2782
|
+
|
|
2783
|
+
my_dict["bProp"].update({"newBProp": "hello"})
|
|
2784
|
+
my_model.b_prop.update({"newBProp": "hello"})
|
|
2785
|
+
|
|
2786
|
+
assert my_dict["bProp"] == my_model.b_prop == my_model["bProp"] == {"bStrProp": "b", "newBProp": "hello"}
|
|
2787
|
+
|
|
2788
|
+
my_dict.update({"dProp": "hello"})
|
|
2789
|
+
my_model.update({"dProp": "hello"})
|
|
2790
|
+
|
|
2791
|
+
assert my_dict["dProp"] == my_model["dProp"] == "hello"
|
|
2792
|
+
|
|
2793
|
+
_tests(my_dict, MainModel(my_dict))
|
|
2794
|
+
_tests(
|
|
2795
|
+
my_dict,
|
|
2796
|
+
MainModel(
|
|
2797
|
+
a_prop=ModelA(a_str_prop="a"),
|
|
2798
|
+
b_prop=ModelB(b_str_prop="b"),
|
|
2799
|
+
c_prop=ModelC(c_str_prop="c"),
|
|
2800
|
+
),
|
|
2801
|
+
)
|
|
2802
|
+
|
|
2803
|
+
|
|
2804
|
+
def test_setdefault():
|
|
2805
|
+
class Inner(Model):
|
|
2806
|
+
str_prop: str = rest_field(name="strProp", default="modelDefault")
|
|
2807
|
+
|
|
2808
|
+
class Outer(Model):
|
|
2809
|
+
inner_prop: Inner = rest_field(name="innerProp")
|
|
2810
|
+
|
|
2811
|
+
og_dict = {"innerProp": {}}
|
|
2812
|
+
og_dict["innerProp"].setdefault("strProp", "actualDefault")
|
|
2813
|
+
og_model = Outer(og_dict)
|
|
2814
|
+
og_model.inner_prop.setdefault("strProp", "actualDefault")
|
|
2815
|
+
|
|
2816
|
+
assert og_dict["innerProp"] == og_model["innerProp"] == og_model.inner_prop == {"strProp": "actualDefault"}
|
|
2817
|
+
|
|
2818
|
+
assert (
|
|
2819
|
+
og_dict["innerProp"].setdefault("strProp")
|
|
2820
|
+
== og_model["innerProp"].setdefault("strProp")
|
|
2821
|
+
== og_model.inner_prop.setdefault("strProp")
|
|
2822
|
+
== "actualDefault"
|
|
2823
|
+
)
|
|
2824
|
+
|
|
2825
|
+
assert og_dict.setdefault("newProp") is og_model.setdefault("newProp") is None
|
|
2826
|
+
assert og_dict["newProp"] is og_model["newProp"] is None
|
|
2827
|
+
|
|
2828
|
+
|
|
2829
|
+
def test_repr():
|
|
2830
|
+
class ModelA(Model):
|
|
2831
|
+
a_str_prop: str = rest_field(name="aStrProp")
|
|
2832
|
+
|
|
2833
|
+
class ModelB(Model):
|
|
2834
|
+
b_str_prop: str = rest_field(name="bStrProp")
|
|
2835
|
+
|
|
2836
|
+
class ModelC(Model):
|
|
2837
|
+
c_str_prop: str = rest_field(name="cStrProp")
|
|
2838
|
+
|
|
2839
|
+
class MainModel(Model):
|
|
2840
|
+
a_prop: ModelA = rest_field(name="aProp")
|
|
2841
|
+
b_prop: ModelB = rest_field(name="bProp")
|
|
2842
|
+
c_prop: ModelC = rest_field(name="cProp")
|
|
2843
|
+
|
|
2844
|
+
my_dict = {
|
|
2845
|
+
"aProp": {"aStrProp": "a"},
|
|
2846
|
+
"bProp": {"bStrProp": "b"},
|
|
2847
|
+
"cProp": {"cStrProp": "c"},
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
assert repr(my_dict) == repr(MainModel(my_dict))
|
|
2851
|
+
|
|
2852
|
+
|
|
2853
|
+
##### REWRITE BODY COMPLEX INTO THIS FILE #####
|
|
2854
|
+
|
|
2855
|
+
|
|
2856
|
+
def test_complex_basic():
|
|
2857
|
+
class Basic(Model):
|
|
2858
|
+
id: Optional[int] = rest_field(default=None)
|
|
2859
|
+
name: Optional[str] = rest_field(default=None)
|
|
2860
|
+
color: Optional[Literal["cyan", "Magenta", "YELLOW", "blacK"]] = rest_field(default=None)
|
|
2861
|
+
|
|
2862
|
+
@overload
|
|
2863
|
+
def __init__(
|
|
2864
|
+
self,
|
|
2865
|
+
*,
|
|
2866
|
+
id: Optional[int] = None,
|
|
2867
|
+
name: Optional[str] = None,
|
|
2868
|
+
color: Optional[Literal["cyan", "Magenta", "YELLOW", "blacK"]] = None,
|
|
2869
|
+
): ...
|
|
2870
|
+
|
|
2871
|
+
@overload
|
|
2872
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2873
|
+
|
|
2874
|
+
def __init__(self, *args, **kwargs):
|
|
2875
|
+
super().__init__(*args, **kwargs)
|
|
2876
|
+
|
|
2877
|
+
basic = Basic(id=2, name="abc", color="Magenta")
|
|
2878
|
+
assert basic == {"id": 2, "name": "abc", "color": "Magenta"}
|
|
2879
|
+
|
|
2880
|
+
basic.id = 3
|
|
2881
|
+
basic.name = "new_name"
|
|
2882
|
+
basic.color = "blacK"
|
|
2883
|
+
|
|
2884
|
+
assert basic == {"id": 3, "name": "new_name", "color": "blacK"}
|
|
2885
|
+
|
|
2886
|
+
basic["id"] = 4
|
|
2887
|
+
basic["name"] = "newest_name"
|
|
2888
|
+
basic["color"] = "YELLOW"
|
|
2889
|
+
|
|
2890
|
+
assert basic == {"id": 4, "name": "newest_name", "color": "YELLOW"}
|
|
2891
|
+
|
|
2892
|
+
|
|
2893
|
+
def test_complex_boolean_wrapper():
|
|
2894
|
+
class BooleanWrapper(Model):
|
|
2895
|
+
field_true: Optional[bool] = rest_field(default=None)
|
|
2896
|
+
field_false: Optional[bool] = rest_field(default=None)
|
|
2897
|
+
|
|
2898
|
+
@overload
|
|
2899
|
+
def __init__(
|
|
2900
|
+
self,
|
|
2901
|
+
*,
|
|
2902
|
+
field_true: Optional[bool] = None,
|
|
2903
|
+
field_false: Optional[bool] = None,
|
|
2904
|
+
): ...
|
|
2905
|
+
|
|
2906
|
+
@overload
|
|
2907
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2908
|
+
|
|
2909
|
+
def __init__(self, *args, **kwargs):
|
|
2910
|
+
super().__init__(*args, **kwargs)
|
|
2911
|
+
|
|
2912
|
+
bool_model = BooleanWrapper(field_true=True, field_false=False)
|
|
2913
|
+
assert bool_model == {"field_true": True, "field_false": False}
|
|
2914
|
+
bool_model.field_true = False
|
|
2915
|
+
bool_model.field_false = True
|
|
2916
|
+
assert bool_model == {"field_true": False, "field_false": True}
|
|
2917
|
+
|
|
2918
|
+
bool_model["field_true"] = True
|
|
2919
|
+
bool_model["field_false"] = False
|
|
2920
|
+
assert bool_model == {"field_true": True, "field_false": False}
|
|
2921
|
+
|
|
2922
|
+
|
|
2923
|
+
def test_complex_byte_wrapper():
|
|
2924
|
+
class ByteWrapper(Model):
|
|
2925
|
+
default: Optional[bytes] = rest_field(default=None)
|
|
2926
|
+
base64: Optional[bytes] = rest_field(default=None, format="base64")
|
|
2927
|
+
base64url: Optional[bytes] = rest_field(default=None, format="base64url")
|
|
2928
|
+
list_base64: Optional[List[bytes]] = rest_field(default=None, format="base64")
|
|
2929
|
+
map_base64url: Optional[Dict[str, bytes]] = rest_field(default=None, format="base64url")
|
|
2930
|
+
|
|
2931
|
+
@overload
|
|
2932
|
+
def __init__(
|
|
2933
|
+
self,
|
|
2934
|
+
*,
|
|
2935
|
+
default: Optional[bytes] = None,
|
|
2936
|
+
base64: Optional[bytes] = None,
|
|
2937
|
+
base64url: Optional[bytes] = None,
|
|
2938
|
+
list_base64: Optional[List[bytes]] = None,
|
|
2939
|
+
map_base64url: Optional[Dict[str, bytes]] = None,
|
|
2940
|
+
): ...
|
|
2941
|
+
|
|
2942
|
+
@overload
|
|
2943
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
2944
|
+
|
|
2945
|
+
def __init__(self, *args, **kwargs):
|
|
2946
|
+
super().__init__(*args, **kwargs)
|
|
2947
|
+
|
|
2948
|
+
byte_string = bytes("test", "utf-8")
|
|
2949
|
+
mod = ByteWrapper(
|
|
2950
|
+
default=byte_string,
|
|
2951
|
+
base64=byte_string,
|
|
2952
|
+
base64url=byte_string,
|
|
2953
|
+
list_base64=[byte_string, byte_string],
|
|
2954
|
+
map_base64url={"key1": byte_string, "key2": byte_string},
|
|
2955
|
+
)
|
|
2956
|
+
decoded = "dGVzdA=="
|
|
2957
|
+
decoded_urlsafe = "dGVzdA"
|
|
2958
|
+
|
|
2959
|
+
def _tests(mod: ByteWrapper):
|
|
2960
|
+
assert mod == {
|
|
2961
|
+
"default": decoded,
|
|
2962
|
+
"base64": decoded,
|
|
2963
|
+
"base64url": decoded_urlsafe,
|
|
2964
|
+
"list_base64": [decoded, decoded],
|
|
2965
|
+
"map_base64url": {"key1": decoded_urlsafe, "key2": decoded_urlsafe},
|
|
2966
|
+
}
|
|
2967
|
+
assert mod.default == byte_string
|
|
2968
|
+
assert mod.base64 == byte_string
|
|
2969
|
+
assert mod.base64url == byte_string
|
|
2970
|
+
assert mod.list_base64 == [byte_string, byte_string]
|
|
2971
|
+
assert mod.map_base64url == {"key1": byte_string, "key2": byte_string}
|
|
2972
|
+
assert mod["default"] == decoded
|
|
2973
|
+
assert mod["base64"] == decoded
|
|
2974
|
+
assert mod["base64url"] == decoded_urlsafe
|
|
2975
|
+
assert mod["list_base64"] == [decoded, decoded]
|
|
2976
|
+
assert mod["map_base64url"] == {
|
|
2977
|
+
"key1": decoded_urlsafe,
|
|
2978
|
+
"key2": decoded_urlsafe,
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
_tests(mod)
|
|
2982
|
+
mod.default = byte_string
|
|
2983
|
+
mod.base64 = byte_string
|
|
2984
|
+
mod.base64url = byte_string
|
|
2985
|
+
mod.list_base64 = [byte_string, byte_string]
|
|
2986
|
+
mod.map_base64url = {"key1": byte_string, "key2": byte_string}
|
|
2987
|
+
_tests(mod)
|
|
2988
|
+
mod["default"] = decoded
|
|
2989
|
+
mod["base64"] = decoded
|
|
2990
|
+
mod["base64url"] = decoded_urlsafe
|
|
2991
|
+
mod["list_base64"] = [decoded, decoded]
|
|
2992
|
+
mod["map_base64url"] = {"key1": decoded_urlsafe, "key2": decoded_urlsafe}
|
|
2993
|
+
_tests(mod)
|
|
2994
|
+
|
|
2995
|
+
|
|
2996
|
+
def test_complex_byte_array_wrapper():
|
|
2997
|
+
class ByteArrayWrapper(Model):
|
|
2998
|
+
default: Optional[bytearray] = rest_field(default=None)
|
|
2999
|
+
base64: Optional[bytearray] = rest_field(default=None, format="base64")
|
|
3000
|
+
base64url: Optional[bytearray] = rest_field(default=None, format="base64url")
|
|
3001
|
+
list_base64: Optional[List[bytearray]] = rest_field(default=None, format="base64")
|
|
3002
|
+
map_base64url: Optional[Dict[str, bytearray]] = rest_field(default=None, format="base64url")
|
|
3003
|
+
|
|
3004
|
+
@overload
|
|
3005
|
+
def __init__(
|
|
3006
|
+
self,
|
|
3007
|
+
*,
|
|
3008
|
+
default: Optional[bytearray] = None,
|
|
3009
|
+
base64: Optional[bytearray] = None,
|
|
3010
|
+
base64url: Optional[bytearray] = None,
|
|
3011
|
+
list_base64: Optional[List[bytearray]] = None,
|
|
3012
|
+
map_base64url: Optional[Dict[str, bytearray]] = None,
|
|
3013
|
+
): ...
|
|
3014
|
+
|
|
3015
|
+
@overload
|
|
3016
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3017
|
+
|
|
3018
|
+
def __init__(self, *args, **kwargs):
|
|
3019
|
+
super().__init__(*args, **kwargs)
|
|
3020
|
+
|
|
3021
|
+
byte_array = bytearray("test".encode("utf-8"))
|
|
3022
|
+
decoded = "dGVzdA=="
|
|
3023
|
+
decoded_urlsafe = "dGVzdA"
|
|
3024
|
+
|
|
3025
|
+
def _tests(model: ByteArrayWrapper):
|
|
3026
|
+
assert model == {
|
|
3027
|
+
"default": decoded,
|
|
3028
|
+
"base64": decoded,
|
|
3029
|
+
"base64url": decoded_urlsafe,
|
|
3030
|
+
"list_base64": [decoded, decoded],
|
|
3031
|
+
"map_base64url": {"key1": decoded_urlsafe, "key2": decoded_urlsafe},
|
|
3032
|
+
}
|
|
3033
|
+
assert model.default == byte_array
|
|
3034
|
+
assert model.base64 == byte_array
|
|
3035
|
+
assert model.base64url == byte_array
|
|
3036
|
+
assert model.list_base64 == [byte_array, byte_array]
|
|
3037
|
+
assert model.map_base64url == {"key1": byte_array, "key2": byte_array}
|
|
3038
|
+
assert model["default"] == decoded
|
|
3039
|
+
assert model["base64"] == decoded
|
|
3040
|
+
assert model["base64url"] == decoded_urlsafe
|
|
3041
|
+
assert model["list_base64"] == [decoded, decoded]
|
|
3042
|
+
assert model["map_base64url"] == {
|
|
3043
|
+
"key1": decoded_urlsafe,
|
|
3044
|
+
"key2": decoded_urlsafe,
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
_tests(
|
|
3048
|
+
ByteArrayWrapper(
|
|
3049
|
+
default=byte_array,
|
|
3050
|
+
base64=byte_array,
|
|
3051
|
+
base64url=byte_array,
|
|
3052
|
+
list_base64=[byte_array, byte_array],
|
|
3053
|
+
map_base64url={"key1": byte_array, "key2": byte_array},
|
|
3054
|
+
)
|
|
3055
|
+
)
|
|
3056
|
+
_tests(
|
|
3057
|
+
ByteArrayWrapper(
|
|
3058
|
+
{
|
|
3059
|
+
"default": decoded,
|
|
3060
|
+
"base64": decoded,
|
|
3061
|
+
"base64url": decoded_urlsafe,
|
|
3062
|
+
"list_base64": [decoded, decoded],
|
|
3063
|
+
"map_base64url": {"key1": decoded_urlsafe, "key2": decoded_urlsafe},
|
|
3064
|
+
}
|
|
3065
|
+
)
|
|
3066
|
+
)
|
|
3067
|
+
|
|
3068
|
+
|
|
3069
|
+
def test_complex_datetime_wrapper():
|
|
3070
|
+
class DatetimeWrapper(Model):
|
|
3071
|
+
default: datetime.datetime = rest_field(default=None)
|
|
3072
|
+
rfc3339: datetime.datetime = rest_field(default=None, format="rfc3339")
|
|
3073
|
+
rfc7231: datetime.datetime = rest_field(default=None, format="rfc7231")
|
|
3074
|
+
unix: datetime.datetime = rest_field(default=None, format="unix-timestamp")
|
|
3075
|
+
list_rfc3339: List[datetime.datetime] = rest_field(default=None, format="rfc3339")
|
|
3076
|
+
dict_rfc7231: Dict[str, datetime.datetime] = rest_field(default=None, format="rfc7231")
|
|
3077
|
+
|
|
3078
|
+
@overload
|
|
3079
|
+
def __init__(
|
|
3080
|
+
self,
|
|
3081
|
+
*,
|
|
3082
|
+
default: Optional[datetime.datetime] = None,
|
|
3083
|
+
rfc3339: Optional[datetime.datetime] = None,
|
|
3084
|
+
rfc7231: Optional[datetime.datetime] = None,
|
|
3085
|
+
unix: Optional[datetime.datetime] = None,
|
|
3086
|
+
list_rfc3339: Optional[List[datetime.datetime]] = None,
|
|
3087
|
+
dict_rfc7231: Optional[Dict[str, datetime.datetime]] = None,
|
|
3088
|
+
): ...
|
|
3089
|
+
|
|
3090
|
+
@overload
|
|
3091
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3092
|
+
|
|
3093
|
+
def __init__(self, *args, **kwargs):
|
|
3094
|
+
super().__init__(*args, **kwargs)
|
|
3095
|
+
|
|
3096
|
+
rfc3339 = "2023-06-27T06:11:09Z"
|
|
3097
|
+
rfc7231 = "Tue, 27 Jun 2023 06:11:09 GMT"
|
|
3098
|
+
unix = 1687846269
|
|
3099
|
+
dt = datetime.datetime(2023, 6, 27, 6, 11, 9, tzinfo=datetime.timezone.utc)
|
|
3100
|
+
|
|
3101
|
+
def _tests(model: DatetimeWrapper):
|
|
3102
|
+
assert model["default"] == rfc3339
|
|
3103
|
+
assert model["rfc3339"] == rfc3339
|
|
3104
|
+
assert model["rfc7231"] == rfc7231
|
|
3105
|
+
assert model["unix"] == unix
|
|
3106
|
+
assert model["list_rfc3339"] == [rfc3339, rfc3339]
|
|
3107
|
+
assert model["dict_rfc7231"] == {"key1": rfc7231, "key2": rfc7231}
|
|
3108
|
+
assert model.default == model.rfc3339 == model.rfc7231 == model.unix == dt
|
|
3109
|
+
assert model.list_rfc3339 == [dt, dt]
|
|
3110
|
+
assert model.dict_rfc7231 == {"key1": dt, "key2": dt}
|
|
3111
|
+
|
|
3112
|
+
_tests(
|
|
3113
|
+
DatetimeWrapper(
|
|
3114
|
+
default=dt,
|
|
3115
|
+
rfc3339=dt,
|
|
3116
|
+
rfc7231=dt,
|
|
3117
|
+
unix=dt,
|
|
3118
|
+
list_rfc3339=[dt, dt],
|
|
3119
|
+
dict_rfc7231={"key1": dt, "key2": dt},
|
|
3120
|
+
)
|
|
3121
|
+
)
|
|
3122
|
+
_tests(
|
|
3123
|
+
DatetimeWrapper(
|
|
3124
|
+
{
|
|
3125
|
+
"default": rfc3339,
|
|
3126
|
+
"rfc3339": rfc3339,
|
|
3127
|
+
"rfc7231": rfc7231,
|
|
3128
|
+
"unix": unix,
|
|
3129
|
+
"list_rfc3339": [rfc3339, rfc3339],
|
|
3130
|
+
"dict_rfc7231": {"key1": rfc7231, "key2": rfc7231},
|
|
3131
|
+
}
|
|
3132
|
+
)
|
|
3133
|
+
)
|
|
3134
|
+
|
|
3135
|
+
|
|
3136
|
+
def test_complex_date_wrapper():
|
|
3137
|
+
class DateWrapper(Model):
|
|
3138
|
+
field: datetime.date = rest_field(default=None)
|
|
3139
|
+
leap: datetime.date = rest_field(default=None)
|
|
3140
|
+
|
|
3141
|
+
@overload
|
|
3142
|
+
def __init__(
|
|
3143
|
+
self,
|
|
3144
|
+
*,
|
|
3145
|
+
field: Optional[datetime.date] = None,
|
|
3146
|
+
leap: Optional[datetime.date] = None,
|
|
3147
|
+
): ...
|
|
3148
|
+
|
|
3149
|
+
@overload
|
|
3150
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3151
|
+
|
|
3152
|
+
def __init__(self, *args, **kwargs):
|
|
3153
|
+
super().__init__(*args, **kwargs)
|
|
3154
|
+
|
|
3155
|
+
field = "0001-01-01"
|
|
3156
|
+
leap = "2016-02-29"
|
|
3157
|
+
|
|
3158
|
+
def _tests(model: DateWrapper):
|
|
3159
|
+
assert model.field == isodate.parse_date(field)
|
|
3160
|
+
assert model["field"] == field
|
|
3161
|
+
|
|
3162
|
+
assert model.leap == isodate.parse_date(leap)
|
|
3163
|
+
assert model["leap"] == leap
|
|
3164
|
+
|
|
3165
|
+
model.field = isodate.parse_date(leap)
|
|
3166
|
+
assert model.field == isodate.parse_date(leap)
|
|
3167
|
+
assert model["field"] == leap
|
|
3168
|
+
|
|
3169
|
+
model["field"] = field
|
|
3170
|
+
assert model.field == isodate.parse_date(field)
|
|
3171
|
+
assert model["field"] == field
|
|
3172
|
+
|
|
3173
|
+
_tests(DateWrapper({"field": field, "leap": leap}))
|
|
3174
|
+
_tests(DateWrapper(field=isodate.parse_date(field), leap=isodate.parse_date(leap)))
|
|
3175
|
+
|
|
3176
|
+
|
|
3177
|
+
class DictionaryWrapper(Model):
|
|
3178
|
+
default_program: Dict[str, str] = rest_field(name="defaultProgram", default=None)
|
|
3179
|
+
|
|
3180
|
+
@overload
|
|
3181
|
+
def __init__(
|
|
3182
|
+
self,
|
|
3183
|
+
*,
|
|
3184
|
+
default_program: Optional[Dict[str, str]] = None,
|
|
3185
|
+
): ...
|
|
3186
|
+
|
|
3187
|
+
@overload
|
|
3188
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3189
|
+
|
|
3190
|
+
def __init__(self, *args, **kwargs):
|
|
3191
|
+
super().__init__(*args, **kwargs)
|
|
3192
|
+
|
|
3193
|
+
|
|
3194
|
+
default_program = {
|
|
3195
|
+
"txt": "notepad",
|
|
3196
|
+
"bmp": "mspaint",
|
|
3197
|
+
"xls": "excel",
|
|
3198
|
+
"exe": "",
|
|
3199
|
+
"": None,
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
|
|
3203
|
+
@pytest.mark.parametrize(
|
|
3204
|
+
"model",
|
|
3205
|
+
[
|
|
3206
|
+
DictionaryWrapper({"defaultProgram": default_program}),
|
|
3207
|
+
DictionaryWrapper(default_program=default_program),
|
|
3208
|
+
],
|
|
3209
|
+
)
|
|
3210
|
+
def test_complex_dictionary_wrapper(model: DictionaryWrapper):
|
|
3211
|
+
assert model == {"defaultProgram": default_program}
|
|
3212
|
+
assert model.default_program == model["defaultProgram"] == default_program
|
|
3213
|
+
|
|
3214
|
+
|
|
3215
|
+
@pytest.mark.parametrize(
|
|
3216
|
+
"model",
|
|
3217
|
+
[DictionaryWrapper({"defaultProgram": {}}), DictionaryWrapper(default_program={})],
|
|
3218
|
+
)
|
|
3219
|
+
def test_complex_dictionary_wrapper_empty(model: DictionaryWrapper):
|
|
3220
|
+
assert model == {"defaultProgram": {}}
|
|
3221
|
+
assert model.default_program == model["defaultProgram"] == {}
|
|
3222
|
+
|
|
3223
|
+
|
|
3224
|
+
@pytest.mark.parametrize(
|
|
3225
|
+
"model",
|
|
3226
|
+
[
|
|
3227
|
+
DictionaryWrapper({"defaultProgram": None}),
|
|
3228
|
+
DictionaryWrapper(default_program=None),
|
|
3229
|
+
],
|
|
3230
|
+
)
|
|
3231
|
+
def test_complex_dictionary_wrapper_none(model: DictionaryWrapper):
|
|
3232
|
+
assert model == {"defaultProgram": None}
|
|
3233
|
+
assert model.default_program is None
|
|
3234
|
+
|
|
3235
|
+
|
|
3236
|
+
class ArrayWrapper(Model):
|
|
3237
|
+
array: Optional[List[str]] = rest_field(default=None)
|
|
3238
|
+
|
|
3239
|
+
@overload
|
|
3240
|
+
def __init__(
|
|
3241
|
+
self,
|
|
3242
|
+
*,
|
|
3243
|
+
array: Optional[List[str]] = None,
|
|
3244
|
+
): ...
|
|
3245
|
+
|
|
3246
|
+
@overload
|
|
3247
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3248
|
+
|
|
3249
|
+
def __init__(self, *args, **kwargs):
|
|
3250
|
+
super().__init__(*args, **kwargs)
|
|
3251
|
+
|
|
3252
|
+
|
|
3253
|
+
array_value = [
|
|
3254
|
+
"1, 2, 3, 4",
|
|
3255
|
+
"",
|
|
3256
|
+
None,
|
|
3257
|
+
"&S#$(*Y",
|
|
3258
|
+
"The quick brown fox jumps over the lazy dog",
|
|
3259
|
+
]
|
|
3260
|
+
|
|
3261
|
+
|
|
3262
|
+
@pytest.mark.parametrize("model", [ArrayWrapper(array=array_value), ArrayWrapper({"array": array_value})])
|
|
3263
|
+
def test_complex_array_wrapper(model: ArrayWrapper):
|
|
3264
|
+
assert model == {"array": array_value}
|
|
3265
|
+
assert model.array == model["array"] == array_value
|
|
3266
|
+
|
|
3267
|
+
model.array = None
|
|
3268
|
+
with pytest.raises(KeyError):
|
|
3269
|
+
model["array"]
|
|
3270
|
+
assert model.array is None
|
|
3271
|
+
|
|
3272
|
+
model["array"] = [1, 2, 3, 4, 5]
|
|
3273
|
+
assert model.array == ["1", "2", "3", "4", "5"]
|
|
3274
|
+
assert model["array"] == [1, 2, 3, 4, 5]
|
|
3275
|
+
|
|
3276
|
+
|
|
3277
|
+
@pytest.mark.parametrize("model", [ArrayWrapper(array=[]), ArrayWrapper({"array": []})])
|
|
3278
|
+
def test_complex_array_wrapper_empty(model: ArrayWrapper):
|
|
3279
|
+
assert model == {"array": []}
|
|
3280
|
+
assert model.array == model["array"] == []
|
|
3281
|
+
|
|
3282
|
+
model.array = ["bonjour"]
|
|
3283
|
+
assert model.array == model["array"] == ["bonjour"]
|
|
3284
|
+
|
|
3285
|
+
|
|
3286
|
+
@pytest.mark.parametrize("model", [ArrayWrapper(array=None), ArrayWrapper({"array": None})])
|
|
3287
|
+
def test_complex_array_wrapper_none(model: ArrayWrapper):
|
|
3288
|
+
assert model == {"array": None}
|
|
3289
|
+
assert model.array is model["array"] is None
|
|
3290
|
+
|
|
3291
|
+
model.array = ["bonjour"]
|
|
3292
|
+
assert model.array == model["array"] == ["bonjour"]
|
|
3293
|
+
|
|
3294
|
+
|
|
3295
|
+
class PetComplex(Model):
|
|
3296
|
+
id: Optional[int] = rest_field(default=None)
|
|
3297
|
+
name: Optional[str] = rest_field(default=None)
|
|
3298
|
+
|
|
3299
|
+
@overload
|
|
3300
|
+
def __init__(
|
|
3301
|
+
self,
|
|
3302
|
+
*,
|
|
3303
|
+
id: Optional[int] = None,
|
|
3304
|
+
name: Optional[str] = None,
|
|
3305
|
+
): ...
|
|
3306
|
+
|
|
3307
|
+
@overload
|
|
3308
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3309
|
+
|
|
3310
|
+
def __init__(self, *args, **kwargs):
|
|
3311
|
+
super().__init__(*args, **kwargs)
|
|
3312
|
+
|
|
3313
|
+
|
|
3314
|
+
class DogComplex(PetComplex):
|
|
3315
|
+
food: Optional[str] = rest_field(default=None)
|
|
3316
|
+
|
|
3317
|
+
@overload
|
|
3318
|
+
def __init__(
|
|
3319
|
+
self,
|
|
3320
|
+
*,
|
|
3321
|
+
id: Optional[int] = None,
|
|
3322
|
+
name: Optional[str] = None,
|
|
3323
|
+
food: Optional[str] = None,
|
|
3324
|
+
): ...
|
|
3325
|
+
|
|
3326
|
+
@overload
|
|
3327
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3328
|
+
|
|
3329
|
+
def __init__(self, *args, **kwargs):
|
|
3330
|
+
super().__init__(*args, **kwargs)
|
|
3331
|
+
|
|
3332
|
+
|
|
3333
|
+
class CatComplex(PetComplex):
|
|
3334
|
+
color: Optional[str] = rest_field(default=None)
|
|
3335
|
+
hates: Optional[List[DogComplex]] = rest_field(default=None)
|
|
3336
|
+
|
|
3337
|
+
@overload
|
|
3338
|
+
def __init__(
|
|
3339
|
+
self,
|
|
3340
|
+
*,
|
|
3341
|
+
id: Optional[int] = None,
|
|
3342
|
+
name: Optional[str] = None,
|
|
3343
|
+
food: Optional[str] = None,
|
|
3344
|
+
color: Optional[str] = None,
|
|
3345
|
+
hates: Optional[List[DogComplex]] = None,
|
|
3346
|
+
): ...
|
|
3347
|
+
|
|
3348
|
+
@overload
|
|
3349
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3350
|
+
|
|
3351
|
+
def __init__(self, *args, **kwargs):
|
|
3352
|
+
super().__init__(*args, **kwargs)
|
|
3353
|
+
|
|
3354
|
+
|
|
3355
|
+
@pytest.mark.parametrize(
|
|
3356
|
+
"model",
|
|
3357
|
+
[
|
|
3358
|
+
CatComplex(
|
|
3359
|
+
id=2,
|
|
3360
|
+
name="Siamese",
|
|
3361
|
+
hates=[
|
|
3362
|
+
DogComplex(id=1, name="Potato", food="tomato"),
|
|
3363
|
+
DogComplex(id=-1, name="Tomato", food="french fries"),
|
|
3364
|
+
],
|
|
3365
|
+
),
|
|
3366
|
+
CatComplex(
|
|
3367
|
+
id=2,
|
|
3368
|
+
name="Siamese",
|
|
3369
|
+
hates=[
|
|
3370
|
+
DogComplex(id=1, name="Potato", food="tomato"),
|
|
3371
|
+
{"id": -1, "name": "Tomato", "food": "french fries"},
|
|
3372
|
+
],
|
|
3373
|
+
),
|
|
3374
|
+
CatComplex(
|
|
3375
|
+
id=2,
|
|
3376
|
+
name="Siamese",
|
|
3377
|
+
hates=[
|
|
3378
|
+
{"id": 1, "name": "Potato", "food": "tomato"},
|
|
3379
|
+
{"id": -1, "name": "Tomato", "food": "french fries"},
|
|
3380
|
+
],
|
|
3381
|
+
),
|
|
3382
|
+
],
|
|
3383
|
+
)
|
|
3384
|
+
def test_complex_inheritance(model):
|
|
3385
|
+
assert model.id == model["id"] == 2
|
|
3386
|
+
assert model.name == model["name"] == "Siamese"
|
|
3387
|
+
assert model.hates
|
|
3388
|
+
assert model.hates[1] == model["hates"][1] == {"id": -1, "name": "Tomato", "food": "french fries"}
|
|
3389
|
+
model["breed"] = "persian"
|
|
3390
|
+
model["color"] = "green"
|
|
3391
|
+
with pytest.raises(AttributeError):
|
|
3392
|
+
model.breed
|
|
3393
|
+
assert model == {
|
|
3394
|
+
"id": 2,
|
|
3395
|
+
"name": "Siamese",
|
|
3396
|
+
"color": "green",
|
|
3397
|
+
"breed": "persian",
|
|
3398
|
+
"hates": [
|
|
3399
|
+
DogComplex(id=1, name="Potato", food="tomato"),
|
|
3400
|
+
DogComplex(id=-1, name="Tomato", food="french fries"),
|
|
3401
|
+
],
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
|
|
3405
|
+
def test_required_prop_not_passed():
|
|
3406
|
+
class ModelWithRequiredProperty(Model):
|
|
3407
|
+
required_property: int = rest_field(name="requiredProperty")
|
|
3408
|
+
|
|
3409
|
+
@overload
|
|
3410
|
+
def __init__(
|
|
3411
|
+
self,
|
|
3412
|
+
*,
|
|
3413
|
+
required_property: int,
|
|
3414
|
+
): ...
|
|
3415
|
+
|
|
3416
|
+
@overload
|
|
3417
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3418
|
+
|
|
3419
|
+
def __init__(self, *args, **kwargs):
|
|
3420
|
+
super().__init__(*args, **kwargs)
|
|
3421
|
+
|
|
3422
|
+
model = ModelWithRequiredProperty()
|
|
3423
|
+
assert model.required_property is None
|
|
3424
|
+
with pytest.raises(KeyError):
|
|
3425
|
+
model["requiredProperty"]
|
|
3426
|
+
|
|
3427
|
+
model = ModelWithRequiredProperty({})
|
|
3428
|
+
assert model.required_property is None
|
|
3429
|
+
with pytest.raises(KeyError):
|
|
3430
|
+
model["requiredProperty"]
|
|
3431
|
+
|
|
3432
|
+
|
|
3433
|
+
def test_null_serialization(core_library):
|
|
3434
|
+
dict_response = {
|
|
3435
|
+
"name": "it's me!",
|
|
3436
|
+
"listOfMe": [
|
|
3437
|
+
{
|
|
3438
|
+
"name": "it's me!",
|
|
3439
|
+
}
|
|
3440
|
+
],
|
|
3441
|
+
"dictOfMe": {
|
|
3442
|
+
"me": {
|
|
3443
|
+
"name": "it's me!",
|
|
3444
|
+
}
|
|
3445
|
+
},
|
|
3446
|
+
"dictOfListOfMe": {
|
|
3447
|
+
"many mes": [
|
|
3448
|
+
{
|
|
3449
|
+
"name": "it's me!",
|
|
3450
|
+
}
|
|
3451
|
+
]
|
|
3452
|
+
},
|
|
3453
|
+
"listOfDictOfMe": None,
|
|
3454
|
+
}
|
|
3455
|
+
model = RecursiveModel(dict_response)
|
|
3456
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == dict_response
|
|
3457
|
+
|
|
3458
|
+
assert model.as_dict() == dict_response
|
|
3459
|
+
|
|
3460
|
+
model.list_of_me = core_library.serialization.NULL
|
|
3461
|
+
model.dict_of_me = None
|
|
3462
|
+
model.list_of_dict_of_me = [
|
|
3463
|
+
{
|
|
3464
|
+
"me": {
|
|
3465
|
+
"name": "it's me!",
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
]
|
|
3469
|
+
model.dict_of_list_of_me["many mes"][0].list_of_me = core_library.serialization.NULL
|
|
3470
|
+
model.dict_of_list_of_me["many mes"][0].dict_of_me = None
|
|
3471
|
+
model.list_of_dict_of_me[0]["me"].list_of_me = core_library.serialization.NULL
|
|
3472
|
+
model.list_of_dict_of_me[0]["me"].dict_of_me = None
|
|
3473
|
+
|
|
3474
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == {
|
|
3475
|
+
"name": "it's me!",
|
|
3476
|
+
"listOfMe": None,
|
|
3477
|
+
"dictOfListOfMe": {
|
|
3478
|
+
"many mes": [
|
|
3479
|
+
{
|
|
3480
|
+
"name": "it's me!",
|
|
3481
|
+
"listOfMe": None,
|
|
3482
|
+
}
|
|
3483
|
+
]
|
|
3484
|
+
},
|
|
3485
|
+
"listOfDictOfMe": [
|
|
3486
|
+
{
|
|
3487
|
+
"me": {
|
|
3488
|
+
"name": "it's me!",
|
|
3489
|
+
"listOfMe": None,
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
],
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
assert model.as_dict() == {
|
|
3496
|
+
"name": "it's me!",
|
|
3497
|
+
"listOfMe": None,
|
|
3498
|
+
"dictOfListOfMe": {
|
|
3499
|
+
"many mes": [
|
|
3500
|
+
{
|
|
3501
|
+
"name": "it's me!",
|
|
3502
|
+
"listOfMe": None,
|
|
3503
|
+
}
|
|
3504
|
+
]
|
|
3505
|
+
},
|
|
3506
|
+
"listOfDictOfMe": [
|
|
3507
|
+
{
|
|
3508
|
+
"me": {
|
|
3509
|
+
"name": "it's me!",
|
|
3510
|
+
"listOfMe": None,
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
],
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
|
|
3517
|
+
class UnionBaseModel(Model):
|
|
3518
|
+
name: str = rest_field()
|
|
3519
|
+
|
|
3520
|
+
@overload
|
|
3521
|
+
def __init__(self, *, name: str): ...
|
|
3522
|
+
|
|
3523
|
+
@overload
|
|
3524
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3525
|
+
|
|
3526
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3527
|
+
super().__init__(*args, **kwargs)
|
|
3528
|
+
|
|
3529
|
+
|
|
3530
|
+
class UnionModel1(UnionBaseModel):
|
|
3531
|
+
prop1: int = rest_field()
|
|
3532
|
+
|
|
3533
|
+
@overload
|
|
3534
|
+
def __init__(self, *, name: str, prop1: int): ...
|
|
3535
|
+
|
|
3536
|
+
@overload
|
|
3537
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3538
|
+
|
|
3539
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3540
|
+
super().__init__(*args, **kwargs)
|
|
3541
|
+
|
|
3542
|
+
|
|
3543
|
+
class UnionModel2(UnionBaseModel):
|
|
3544
|
+
prop2: int = rest_field()
|
|
3545
|
+
|
|
3546
|
+
@overload
|
|
3547
|
+
def __init__(self, *, name: str, prop2: int): ...
|
|
3548
|
+
|
|
3549
|
+
@overload
|
|
3550
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3551
|
+
|
|
3552
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3553
|
+
super().__init__(*args, **kwargs)
|
|
3554
|
+
|
|
3555
|
+
|
|
3556
|
+
MyNamedUnion = Union["UnionModel1", "UnionModel2"]
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
class ModelWithNamedUnionProperty(Model):
|
|
3560
|
+
named_union: "MyNamedUnion" = rest_field(name="namedUnion")
|
|
3561
|
+
|
|
3562
|
+
@overload
|
|
3563
|
+
def __init__(self, *, named_union: "MyNamedUnion"): ...
|
|
3564
|
+
|
|
3565
|
+
@overload
|
|
3566
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3567
|
+
|
|
3568
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3569
|
+
super().__init__(*args, **kwargs)
|
|
3570
|
+
|
|
3571
|
+
|
|
3572
|
+
class ModelWithSimpleUnionProperty(Model):
|
|
3573
|
+
simple_union: Union[int, List[int]] = rest_field(name="simpleUnion")
|
|
3574
|
+
|
|
3575
|
+
@overload
|
|
3576
|
+
def __init__(self, *, simple_union: Union[int, List[int]]): ...
|
|
3577
|
+
|
|
3578
|
+
@overload
|
|
3579
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3580
|
+
|
|
3581
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3582
|
+
super().__init__(*args, **kwargs)
|
|
3583
|
+
|
|
3584
|
+
|
|
3585
|
+
def test_union():
|
|
3586
|
+
simple = ModelWithSimpleUnionProperty(simple_union=1)
|
|
3587
|
+
assert simple.simple_union == simple["simpleUnion"] == 1
|
|
3588
|
+
simple = ModelWithSimpleUnionProperty(simple_union=[1, 2])
|
|
3589
|
+
assert simple.simple_union == simple["simpleUnion"] == [1, 2]
|
|
3590
|
+
named = ModelWithNamedUnionProperty()
|
|
3591
|
+
assert not _is_model(named.named_union)
|
|
3592
|
+
named.named_union = UnionModel1(name="model1", prop1=1)
|
|
3593
|
+
assert _is_model(named.named_union)
|
|
3594
|
+
assert named.named_union == named["namedUnion"] == {"name": "model1", "prop1": 1}
|
|
3595
|
+
named = ModelWithNamedUnionProperty(named_union=UnionModel2(name="model2", prop2=2))
|
|
3596
|
+
assert named.named_union == named["namedUnion"] == {"name": "model2", "prop2": 2}
|
|
3597
|
+
named = ModelWithNamedUnionProperty({"namedUnion": {"name": "model2", "prop2": 2}})
|
|
3598
|
+
assert named.named_union == named["namedUnion"] == {"name": "model2", "prop2": 2}
|
|
3599
|
+
|
|
3600
|
+
|
|
3601
|
+
def test_as_dict():
|
|
3602
|
+
class CatComplex(PetComplex):
|
|
3603
|
+
color: Optional[str] = rest_field(default=None)
|
|
3604
|
+
hates: Optional[List[DogComplex]] = rest_field(default=None, visibility=["read"])
|
|
3605
|
+
|
|
3606
|
+
@overload
|
|
3607
|
+
def __init__(
|
|
3608
|
+
self,
|
|
3609
|
+
*,
|
|
3610
|
+
id: Optional[int] = None,
|
|
3611
|
+
name: Optional[str] = None,
|
|
3612
|
+
food: Optional[str] = None,
|
|
3613
|
+
color: Optional[str] = None,
|
|
3614
|
+
hates: Optional[List[DogComplex]] = None,
|
|
3615
|
+
): ...
|
|
3616
|
+
|
|
3617
|
+
@overload
|
|
3618
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3619
|
+
|
|
3620
|
+
def __init__(self, *args, **kwargs):
|
|
3621
|
+
super().__init__(*args, **kwargs)
|
|
3622
|
+
|
|
3623
|
+
model = CatComplex(
|
|
3624
|
+
id=2,
|
|
3625
|
+
name="Siamese",
|
|
3626
|
+
hates=[
|
|
3627
|
+
DogComplex(id=1, name="Potato", food="tomato"),
|
|
3628
|
+
DogComplex(id=-1, name="Tomato", food="french fries"),
|
|
3629
|
+
],
|
|
3630
|
+
)
|
|
3631
|
+
assert model.as_dict(exclude_readonly=True) == {
|
|
3632
|
+
"id": 2,
|
|
3633
|
+
"name": "Siamese",
|
|
3634
|
+
"color": None,
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
|
|
3638
|
+
class Fish(Model):
|
|
3639
|
+
__mapping__: Dict[str, Model] = {}
|
|
3640
|
+
age: int = rest_field()
|
|
3641
|
+
kind: Literal[None] = rest_discriminator(name="kind")
|
|
3642
|
+
|
|
3643
|
+
@overload
|
|
3644
|
+
def __init__(
|
|
3645
|
+
self,
|
|
3646
|
+
*,
|
|
3647
|
+
age: int,
|
|
3648
|
+
): ...
|
|
3649
|
+
|
|
3650
|
+
@overload
|
|
3651
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3652
|
+
|
|
3653
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3654
|
+
super().__init__(*args, **kwargs)
|
|
3655
|
+
self.kind: Literal[None] = None
|
|
3656
|
+
|
|
3657
|
+
|
|
3658
|
+
class Shark(Fish, discriminator="shark"):
|
|
3659
|
+
__mapping__: Dict[str, Model] = {}
|
|
3660
|
+
kind: Literal["shark"] = rest_discriminator(name="kind")
|
|
3661
|
+
sharktype: Literal[None] = rest_discriminator(name="sharktype")
|
|
3662
|
+
|
|
3663
|
+
@overload
|
|
3664
|
+
def __init__(
|
|
3665
|
+
self,
|
|
3666
|
+
*,
|
|
3667
|
+
age: int,
|
|
3668
|
+
): ...
|
|
3669
|
+
|
|
3670
|
+
@overload
|
|
3671
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3672
|
+
|
|
3673
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3674
|
+
super().__init__(*args, **kwargs)
|
|
3675
|
+
self.kind: Literal["shark"] = "shark"
|
|
3676
|
+
self.sharktype: Literal[None] = None
|
|
3677
|
+
|
|
3678
|
+
|
|
3679
|
+
class GoblinShark(Shark, discriminator="goblin"):
|
|
3680
|
+
sharktype: Literal["goblin"] = rest_discriminator(name="sharktype")
|
|
3681
|
+
|
|
3682
|
+
@overload
|
|
3683
|
+
def __init__(
|
|
3684
|
+
self,
|
|
3685
|
+
*,
|
|
3686
|
+
age: int,
|
|
3687
|
+
): ...
|
|
3688
|
+
|
|
3689
|
+
@overload
|
|
3690
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3691
|
+
|
|
3692
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3693
|
+
super().__init__(*args, **kwargs)
|
|
3694
|
+
self.sharktype: Literal["goblin"] = "goblin"
|
|
3695
|
+
|
|
3696
|
+
|
|
3697
|
+
class Salmon(Fish, discriminator="salmon"):
|
|
3698
|
+
kind: Literal["salmon"] = rest_discriminator(name="kind")
|
|
3699
|
+
friends: Optional[List["Fish"]] = rest_field()
|
|
3700
|
+
hate: Optional[Dict[str, "Fish"]] = rest_field()
|
|
3701
|
+
partner: Optional["Fish"] = rest_field()
|
|
3702
|
+
|
|
3703
|
+
@overload
|
|
3704
|
+
def __init__(
|
|
3705
|
+
self,
|
|
3706
|
+
*,
|
|
3707
|
+
age: int,
|
|
3708
|
+
friends: Optional[List["Fish"]] = None,
|
|
3709
|
+
hate: Optional[Dict[str, "Fish"]] = None,
|
|
3710
|
+
partner: Optional["Fish"] = None,
|
|
3711
|
+
): ...
|
|
3712
|
+
|
|
3713
|
+
@overload
|
|
3714
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3715
|
+
|
|
3716
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3717
|
+
super().__init__(*args, **kwargs)
|
|
3718
|
+
self.kind: Literal["salmon"] = "salmon"
|
|
3719
|
+
|
|
3720
|
+
|
|
3721
|
+
class SawShark(Shark, discriminator="saw"):
|
|
3722
|
+
sharktype: Literal["saw"] = rest_discriminator(name="sharktype")
|
|
3723
|
+
|
|
3724
|
+
@overload
|
|
3725
|
+
def __init__(
|
|
3726
|
+
self,
|
|
3727
|
+
*,
|
|
3728
|
+
age: int,
|
|
3729
|
+
): ...
|
|
3730
|
+
|
|
3731
|
+
@overload
|
|
3732
|
+
def __init__(self, mapping: Mapping[str, Any]): ...
|
|
3733
|
+
|
|
3734
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
3735
|
+
super().__init__(*args, **kwargs)
|
|
3736
|
+
self.sharktype: Literal["saw"] = "saw"
|
|
3737
|
+
|
|
3738
|
+
|
|
3739
|
+
def test_discriminator():
|
|
3740
|
+
input = {
|
|
3741
|
+
"age": 1,
|
|
3742
|
+
"kind": "salmon",
|
|
3743
|
+
"partner": {
|
|
3744
|
+
"age": 2,
|
|
3745
|
+
"kind": "shark",
|
|
3746
|
+
"sharktype": "saw",
|
|
3747
|
+
},
|
|
3748
|
+
"friends": [
|
|
3749
|
+
{
|
|
3750
|
+
"age": 2,
|
|
3751
|
+
"kind": "salmon",
|
|
3752
|
+
"partner": {
|
|
3753
|
+
"age": 3,
|
|
3754
|
+
"kind": "salmon",
|
|
3755
|
+
},
|
|
3756
|
+
"hate": {
|
|
3757
|
+
"key1": {
|
|
3758
|
+
"age": 4,
|
|
3759
|
+
"kind": "salmon",
|
|
3760
|
+
},
|
|
3761
|
+
"key2": {
|
|
3762
|
+
"age": 2,
|
|
3763
|
+
"kind": "shark",
|
|
3764
|
+
"sharktype": "goblin",
|
|
3765
|
+
},
|
|
3766
|
+
},
|
|
3767
|
+
},
|
|
3768
|
+
{
|
|
3769
|
+
"age": 3,
|
|
3770
|
+
"kind": "shark",
|
|
3771
|
+
"sharktype": "goblin",
|
|
3772
|
+
},
|
|
3773
|
+
],
|
|
3774
|
+
"hate": {
|
|
3775
|
+
"key3": {
|
|
3776
|
+
"age": 3,
|
|
3777
|
+
"kind": "shark",
|
|
3778
|
+
"sharktype": "saw",
|
|
3779
|
+
},
|
|
3780
|
+
"key4": {
|
|
3781
|
+
"age": 2,
|
|
3782
|
+
"kind": "salmon",
|
|
3783
|
+
"friends": [
|
|
3784
|
+
{
|
|
3785
|
+
"age": 1,
|
|
3786
|
+
"kind": "salmon",
|
|
3787
|
+
},
|
|
3788
|
+
{
|
|
3789
|
+
"age": 4,
|
|
3790
|
+
"kind": "shark",
|
|
3791
|
+
"sharktype": "goblin",
|
|
3792
|
+
},
|
|
3793
|
+
],
|
|
3794
|
+
},
|
|
3795
|
+
},
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
model = Salmon(input)
|
|
3799
|
+
assert model == input
|
|
3800
|
+
assert model.partner.age == 2
|
|
3801
|
+
assert model.partner == SawShark(age=2)
|
|
3802
|
+
assert model.friends[0].hate["key2"] == GoblinShark(age=2)
|
|
3803
|
+
|
|
3804
|
+
|
|
3805
|
+
def test_body_bytes_format():
|
|
3806
|
+
assert json.dumps(bytes("test", "utf-8"), cls=SdkJSONEncoder) == '"dGVzdA=="'
|
|
3807
|
+
assert json.dumps(bytearray("test", "utf-8"), cls=SdkJSONEncoder) == '"dGVzdA=="'
|
|
3808
|
+
assert json.dumps(bytes("test", "utf-8"), cls=SdkJSONEncoder, format="base64") == '"dGVzdA=="'
|
|
3809
|
+
assert json.dumps(bytes("test", "utf-8"), cls=SdkJSONEncoder, format="base64url") == '"dGVzdA"'
|
|
3810
|
+
assert json.dumps(bytearray("test", "utf-8"), cls=SdkJSONEncoder, format="base64") == '"dGVzdA=="'
|
|
3811
|
+
assert json.dumps(bytearray("test", "utf-8"), cls=SdkJSONEncoder, format="base64url") == '"dGVzdA"'
|
|
3812
|
+
|
|
3813
|
+
assert (
|
|
3814
|
+
json.dumps([bytes("test", "utf-8"), bytes("test", "utf-8")], cls=SdkJSONEncoder) == '["dGVzdA==", "dGVzdA=="]'
|
|
3815
|
+
)
|
|
3816
|
+
assert (
|
|
3817
|
+
json.dumps([bytearray("test", "utf-8"), bytearray("test", "utf-8")], cls=SdkJSONEncoder)
|
|
3818
|
+
== '["dGVzdA==", "dGVzdA=="]'
|
|
3819
|
+
)
|
|
3820
|
+
assert (
|
|
3821
|
+
json.dumps(
|
|
3822
|
+
[bytes("test", "utf-8"), bytes("test", "utf-8")],
|
|
3823
|
+
cls=SdkJSONEncoder,
|
|
3824
|
+
format="base64",
|
|
3825
|
+
)
|
|
3826
|
+
== '["dGVzdA==", "dGVzdA=="]'
|
|
3827
|
+
)
|
|
3828
|
+
assert (
|
|
3829
|
+
json.dumps(
|
|
3830
|
+
[bytes("test", "utf-8"), bytes("test", "utf-8")],
|
|
3831
|
+
cls=SdkJSONEncoder,
|
|
3832
|
+
format="base64url",
|
|
3833
|
+
)
|
|
3834
|
+
== '["dGVzdA", "dGVzdA"]'
|
|
3835
|
+
)
|
|
3836
|
+
assert (
|
|
3837
|
+
json.dumps(
|
|
3838
|
+
[bytearray("test", "utf-8"), bytearray("test", "utf-8")],
|
|
3839
|
+
cls=SdkJSONEncoder,
|
|
3840
|
+
format="base64",
|
|
3841
|
+
)
|
|
3842
|
+
== '["dGVzdA==", "dGVzdA=="]'
|
|
3843
|
+
)
|
|
3844
|
+
assert (
|
|
3845
|
+
json.dumps(
|
|
3846
|
+
[bytearray("test", "utf-8"), bytearray("test", "utf-8")],
|
|
3847
|
+
cls=SdkJSONEncoder,
|
|
3848
|
+
format="base64url",
|
|
3849
|
+
)
|
|
3850
|
+
== '["dGVzdA", "dGVzdA"]'
|
|
3851
|
+
)
|
|
3852
|
+
|
|
3853
|
+
assert (
|
|
3854
|
+
json.dumps(
|
|
3855
|
+
{"a": bytes("test", "utf-8"), "b": bytes("test", "utf-8")},
|
|
3856
|
+
cls=SdkJSONEncoder,
|
|
3857
|
+
)
|
|
3858
|
+
== '{"a": "dGVzdA==", "b": "dGVzdA=="}'
|
|
3859
|
+
)
|
|
3860
|
+
assert (
|
|
3861
|
+
json.dumps(
|
|
3862
|
+
{"a": bytearray("test", "utf-8"), "b": bytearray("test", "utf-8")},
|
|
3863
|
+
cls=SdkJSONEncoder,
|
|
3864
|
+
)
|
|
3865
|
+
== '{"a": "dGVzdA==", "b": "dGVzdA=="}'
|
|
3866
|
+
)
|
|
3867
|
+
assert (
|
|
3868
|
+
json.dumps(
|
|
3869
|
+
{"a": bytes("test", "utf-8"), "b": bytes("test", "utf-8")},
|
|
3870
|
+
cls=SdkJSONEncoder,
|
|
3871
|
+
format="base64",
|
|
3872
|
+
)
|
|
3873
|
+
== '{"a": "dGVzdA==", "b": "dGVzdA=="}'
|
|
3874
|
+
)
|
|
3875
|
+
assert (
|
|
3876
|
+
json.dumps(
|
|
3877
|
+
{"a": bytes("test", "utf-8"), "b": bytes("test", "utf-8")},
|
|
3878
|
+
cls=SdkJSONEncoder,
|
|
3879
|
+
format="base64url",
|
|
3880
|
+
)
|
|
3881
|
+
== '{"a": "dGVzdA", "b": "dGVzdA"}'
|
|
3882
|
+
)
|
|
3883
|
+
assert (
|
|
3884
|
+
json.dumps(
|
|
3885
|
+
{"a": bytearray("test", "utf-8"), "b": bytearray("test", "utf-8")},
|
|
3886
|
+
cls=SdkJSONEncoder,
|
|
3887
|
+
format="base64",
|
|
3888
|
+
)
|
|
3889
|
+
== '{"a": "dGVzdA==", "b": "dGVzdA=="}'
|
|
3890
|
+
)
|
|
3891
|
+
assert (
|
|
3892
|
+
json.dumps(
|
|
3893
|
+
{"a": bytearray("test", "utf-8"), "b": bytearray("test", "utf-8")},
|
|
3894
|
+
cls=SdkJSONEncoder,
|
|
3895
|
+
format="base64url",
|
|
3896
|
+
)
|
|
3897
|
+
== '{"a": "dGVzdA", "b": "dGVzdA"}'
|
|
3898
|
+
)
|
|
3899
|
+
|
|
3900
|
+
|
|
3901
|
+
def test_decimal_deserialization():
|
|
3902
|
+
class DecimalModel(Model):
|
|
3903
|
+
decimal_value: decimal.Decimal = rest_field(name="decimalValue")
|
|
3904
|
+
|
|
3905
|
+
@overload
|
|
3906
|
+
def __init__(self, *, decimal_value: decimal.Decimal): ...
|
|
3907
|
+
|
|
3908
|
+
@overload
|
|
3909
|
+
def __init__(self, mapping: Mapping[str, Any], /): ...
|
|
3910
|
+
|
|
3911
|
+
def __init__(self, *args, **kwargs):
|
|
3912
|
+
super().__init__(*args, **kwargs)
|
|
3913
|
+
|
|
3914
|
+
model = DecimalModel({"decimalValue": 0.33333})
|
|
3915
|
+
assert model["decimalValue"] == 0.33333
|
|
3916
|
+
assert model.decimal_value == decimal.Decimal("0.33333")
|
|
3917
|
+
|
|
3918
|
+
class BaseModel(Model):
|
|
3919
|
+
my_prop: DecimalModel = rest_field(name="myProp")
|
|
3920
|
+
|
|
3921
|
+
model = BaseModel({"myProp": {"decimalValue": 0.33333}})
|
|
3922
|
+
assert isinstance(model.my_prop, DecimalModel)
|
|
3923
|
+
assert model.my_prop["decimalValue"] == model["myProp"]["decimalValue"] == 0.33333
|
|
3924
|
+
assert model.my_prop.decimal_value == decimal.Decimal("0.33333")
|
|
3925
|
+
|
|
3926
|
+
|
|
3927
|
+
def test_decimal_serialization():
|
|
3928
|
+
assert json.dumps(decimal.Decimal("0.33333"), cls=SdkJSONEncoder) == "0.33333"
|
|
3929
|
+
assert (
|
|
3930
|
+
json.dumps([decimal.Decimal("0.33333"), decimal.Decimal("0.33333")], cls=SdkJSONEncoder) == "[0.33333, 0.33333]"
|
|
3931
|
+
)
|
|
3932
|
+
assert (
|
|
3933
|
+
json.dumps(
|
|
3934
|
+
{"a": decimal.Decimal("0.33333"), "b": decimal.Decimal("0.33333")},
|
|
3935
|
+
cls=SdkJSONEncoder,
|
|
3936
|
+
)
|
|
3937
|
+
== '{"a": 0.33333, "b": 0.33333}'
|
|
3938
|
+
)
|
|
3939
|
+
|
|
3940
|
+
|
|
3941
|
+
def test_int_as_str_deserialization():
|
|
3942
|
+
class IntAsStrModel(Model):
|
|
3943
|
+
int_as_str_value: int = rest_field(name="intAsStrValue", format="str")
|
|
3944
|
+
|
|
3945
|
+
model = IntAsStrModel({"intAsStrValue": "123"})
|
|
3946
|
+
assert model["intAsStrValue"] == "123"
|
|
3947
|
+
assert model.int_as_str_value == 123
|
|
3948
|
+
|
|
3949
|
+
class BaseModel(Model):
|
|
3950
|
+
my_prop: IntAsStrModel = rest_field(name="myProp")
|
|
3951
|
+
|
|
3952
|
+
model = BaseModel({"myProp": {"intAsStrValue": "123"}})
|
|
3953
|
+
assert isinstance(model.my_prop, IntAsStrModel)
|
|
3954
|
+
assert model.my_prop["intAsStrValue"] == model["myProp"]["intAsStrValue"] == "123"
|
|
3955
|
+
assert model.my_prop.int_as_str_value == 123
|
|
3956
|
+
|
|
3957
|
+
|
|
3958
|
+
def test_deserialize():
|
|
3959
|
+
expected = {"name": "name", "role": "role"}
|
|
3960
|
+
result = _deserialize(JSON, expected)
|
|
3961
|
+
assert result == expected
|
|
3962
|
+
|
|
3963
|
+
|
|
3964
|
+
def test_enum_deserialization():
|
|
3965
|
+
class MyEnum(Enum):
|
|
3966
|
+
A = "a"
|
|
3967
|
+
B = "b"
|
|
3968
|
+
|
|
3969
|
+
class ModelWithEnumProperty(Model):
|
|
3970
|
+
enum_property: Union[str, MyEnum] = rest_field(name="enumProperty")
|
|
3971
|
+
enum_property_optional: Optional[Union[str, MyEnum]] = rest_field(name="enumPropertyOptional")
|
|
3972
|
+
enum_property_optional_none: Optional[Union[str, MyEnum]] = rest_field(name="enumPropertyOptionalNone")
|
|
3973
|
+
|
|
3974
|
+
raw_input = {
|
|
3975
|
+
"enumProperty": "a",
|
|
3976
|
+
"enumPropertyOptional": "b",
|
|
3977
|
+
"enumPropertyOptionalNone": None,
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
def check_func(target: ModelWithEnumProperty):
|
|
3981
|
+
assert target.enum_property == MyEnum.A
|
|
3982
|
+
assert target["enumProperty"] == "a"
|
|
3983
|
+
assert isinstance(target.enum_property, Enum)
|
|
3984
|
+
assert isinstance(target["enumProperty"], str)
|
|
3985
|
+
|
|
3986
|
+
assert target.enum_property_optional == MyEnum.B
|
|
3987
|
+
assert target["enumPropertyOptional"] == "b"
|
|
3988
|
+
assert isinstance(target.enum_property_optional, Enum)
|
|
3989
|
+
assert isinstance(target["enumPropertyOptional"], str)
|
|
3990
|
+
|
|
3991
|
+
assert target.enum_property_optional_none is None
|
|
3992
|
+
assert target["enumPropertyOptionalNone"] is None
|
|
3993
|
+
|
|
3994
|
+
model = ModelWithEnumProperty(raw_input)
|
|
3995
|
+
check_func(model)
|
|
3996
|
+
|
|
3997
|
+
result = _deserialize(List[ModelWithEnumProperty], [raw_input])
|
|
3998
|
+
for item in result:
|
|
3999
|
+
check_func(item)
|
|
4000
|
+
|
|
4001
|
+
|
|
4002
|
+
def test_not_mutating_original_dict():
|
|
4003
|
+
class MyInnerModel(Model):
|
|
4004
|
+
property: str = rest_field()
|
|
4005
|
+
|
|
4006
|
+
class MyModel(Model):
|
|
4007
|
+
property: MyInnerModel = rest_field()
|
|
4008
|
+
|
|
4009
|
+
origin = {"property": {"property": "hello"}}
|
|
4010
|
+
|
|
4011
|
+
dpg_model = MyModel(origin)
|
|
4012
|
+
assert dpg_model["property"]["property"] == "hello"
|
|
4013
|
+
|
|
4014
|
+
origin["property"]["property"] = "world"
|
|
4015
|
+
assert dpg_model["property"]["property"] == "hello"
|
|
4016
|
+
|
|
4017
|
+
|
|
4018
|
+
def test_model_init_io():
|
|
4019
|
+
class BytesModel(Model):
|
|
4020
|
+
property: bytes = rest_field()
|
|
4021
|
+
|
|
4022
|
+
JPG = Path(__file__).parent.parent / "data/image.jpg"
|
|
4023
|
+
with open(JPG, "rb") as f:
|
|
4024
|
+
b = BytesModel({"property": f})
|
|
4025
|
+
assert b.property == f
|
|
4026
|
+
assert b["property"] == f
|
|
4027
|
+
with open(JPG, "rb") as f:
|
|
4028
|
+
b = BytesModel(property=f)
|
|
4029
|
+
assert b.property == f
|
|
4030
|
+
assert b["property"] == f
|
|
4031
|
+
|
|
4032
|
+
|
|
4033
|
+
def test_additional_properties_serialization():
|
|
4034
|
+
value = {
|
|
4035
|
+
"name": "test",
|
|
4036
|
+
"modelProp": {"name": "test"},
|
|
4037
|
+
"stringProp": "string",
|
|
4038
|
+
"intProp": 1,
|
|
4039
|
+
"floatProp": 1.0,
|
|
4040
|
+
"boolProp": True,
|
|
4041
|
+
"listProp": [1, 2, 3],
|
|
4042
|
+
"dictProp": {"key": "value"},
|
|
4043
|
+
"noneProp": None,
|
|
4044
|
+
"datetimeProp": "2023-06-27T06:11:09Z",
|
|
4045
|
+
"durationProp": "P1D",
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
class NormalModel(Model):
|
|
4049
|
+
prop: str = rest_field(name="name")
|
|
4050
|
+
|
|
4051
|
+
class AdditionalPropertiesModel(Model):
|
|
4052
|
+
name: str = rest_field(name="name")
|
|
4053
|
+
|
|
4054
|
+
model = AdditionalPropertiesModel(name="test")
|
|
4055
|
+
prop = NormalModel(prop="test")
|
|
4056
|
+
model["modelProp"] = prop
|
|
4057
|
+
model["stringProp"] = "string"
|
|
4058
|
+
model["intProp"] = 1
|
|
4059
|
+
model["floatProp"] = 1.0
|
|
4060
|
+
model["boolProp"] = True
|
|
4061
|
+
model["listProp"] = [1, 2, 3]
|
|
4062
|
+
model["dictProp"] = {"key": "value"}
|
|
4063
|
+
model["noneProp"] = None
|
|
4064
|
+
model["datetimeProp"] = datetime.datetime(2023, 6, 27, 6, 11, 9, tzinfo=datetime.timezone.utc)
|
|
4065
|
+
model["durationProp"] = datetime.timedelta(days=1)
|
|
4066
|
+
|
|
4067
|
+
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == value
|