diff --git a/CHANGELOG.md b/CHANGELOG.md index b64b1bbaf8..fe3b933635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Added support for no-content responses in python abstractions and http packages. [#1630](https://github.com/microsoft/kiota/issues/1459) +- Added support for vendor-specific content types in python. [#1631](https://github.com/microsoft/kiota/issues/1463) +- Simplified field deserializers for json in Python. [#1632](https://github.com/microsoft/kiota/issues/1492) ### Changed @@ -141,6 +144,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug with special characters in query parameters names. [#1445](https://github.com/microsoft/kiota/issues/1445) - Fixed a bug where complex types path parameters would fail to generate. - Fixed a bug where Go serialization/deserialization method would generate invalid accessor names. +- Added discriminator support in the python abstractions serialization and http packages. [#1500](https://github.com/microsoft/kiota/issues/1256) ## [0.0.22] - 2022-04-08 diff --git a/abstractions/python/.pylintrc b/abstractions/python/.pylintrc index d051dfd4ab..eace9500b6 100644 --- a/abstractions/python/.pylintrc +++ b/abstractions/python/.pylintrc @@ -60,15 +60,9 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, +disable=long-suffix, old-ne-operator, old-octal-literal, - import-star-module-level, non-ascii-bytes-literal, raw-checker-failed, bad-inline-option, @@ -78,74 +72,12 @@ disable=print-statement, useless-suppression, deprecated-pragma, use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, too-few-public-methods, - no-self-use, missing-module-docstring, missing-class-docstring, missing-function-docstring, C0103, - C0330, R0801, R0904, @@ -324,13 +256,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no diff --git a/abstractions/python/Pipfile b/abstractions/python/Pipfile index 5c79830e4f..2ac76c2b07 100644 --- a/abstractions/python/Pipfile +++ b/abstractions/python/Pipfile @@ -12,4 +12,5 @@ pylint = "==2.14.4" mypy = "==0.961" yapf = "==0.32.0" isort = "==5.10.1" +toml = "==0.10.2" diff --git a/abstractions/python/Pipfile.lock b/abstractions/python/Pipfile.lock index bfa3baf0ab..3c11fb7e6a 100644 --- a/abstractions/python/Pipfile.lock +++ b/abstractions/python/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "cd4f99429037643b5f6b04782c12a98136b746cbf528c0f48e413841f80ae2f1" + "sha256": "ed0d2cfa923bdc14822d172e925a69379cbe7d94aef3bd95f9371a5b78800952" }, "pipfile-spec": 6, "requires": {}, @@ -26,18 +26,18 @@ "develop": { "astroid": { "hashes": [ - "sha256:4f933d0bf5e408b03a6feb5d23793740c27e07340605f236496cd6ce552043d6", - "sha256:ba33a82a9a9c06a5ceed98180c5aab16e29c285b828d94696bf32d6015ea82a9" + "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b", + "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946" ], "markers": "python_full_version >= '3.6.2'", - "version": "==2.11.6" + "version": "==2.11.7" }, "certifi": { "hashes": [ "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==2022.6.15" }, "charset-normalizer": { @@ -45,9 +45,17 @@ "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==2.1.0" }, + "colorama": { + "hashes": [ + "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", + "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.5" + }, "dill": { "hashes": [ "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", @@ -58,11 +66,11 @@ }, "docutils": { "hashes": [ - "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", - "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", + "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.18.1" + "markers": "python_version >= '3.7'", + "version": "==0.19" }, "flit": { "hashes": [ @@ -77,7 +85,7 @@ "sha256:14955af340c43035dbfa96b5ee47407e377ee337f69e70f73064940d27d0a44f", "sha256:e454fdbf68c7036e1c7435ec7479383f9d9a1650ca5b304feb184eba1efcdcef" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==3.7.1" }, "idna": { @@ -136,7 +144,7 @@ "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==1.7.1" }, "mccabe": { @@ -144,7 +152,7 @@ "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" ], - "markers": "python_version >= '3.6'", + "markers": "python_full_version >= '3.6.0'", "version": "==0.7.0" }, "mypy": { @@ -204,16 +212,16 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_version < '4.0'", "version": "==2.28.1" }, - "setuptools": { + "toml": { "hashes": [ - "sha256:990a4f7861b31532871ab72331e755b5f14efbe52d336ea7f6118144dd478741", - "sha256:c1848f654aea2e3526d17fc3ce6aeaa5e7e24e66e645b5be2171f3f6b4e5a178" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '3.7'", - "version": "==62.6.0" + "index": "pypi", + "version": "==0.10.2" }, "tomli": { "hashes": [ @@ -233,27 +241,27 @@ }, "tomlkit": { "hashes": [ - "sha256:0f4050db66fd445b885778900ce4dd9aea8c90c4721141fde0d6ade893820ef1", - "sha256:71ceb10c0eefd8b8f11fe34e8a51ad07812cb1dc3de23247425fbc9ddc47b9dd" + "sha256:1c5bebdf19d5051e2e1de6cf70adfc5948d47221f097fcff7a3ffc91e953eaf5", + "sha256:61901f81ff4017951119cd0d1ed9b7af31c821d6845c8c477587bbdcd5e5854e" ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==0.11.0" + "markers": "python_version < '4.0' and python_full_version >= '3.6.0'", + "version": "==0.11.1" }, "typing-extensions": { "hashes": [ - "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", - "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version >= '3.7'", - "version": "==4.2.0" + "markers": "python_version < '3.10'", + "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.9" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", + "version": "==1.26.10" }, "wrapt": { "hashes": [ diff --git a/abstractions/python/kiota/abstractions/_version.py b/abstractions/python/kiota/abstractions/_version.py index 6bf6b2c362..8dd7053bf0 100644 --- a/abstractions/python/kiota/abstractions/_version.py +++ b/abstractions/python/kiota/abstractions/_version.py @@ -1 +1 @@ -VERSION: str = '0.3.0' +VERSION: str = '0.4.0' diff --git a/abstractions/python/kiota/abstractions/api_error.py b/abstractions/python/kiota/abstractions/api_error.py index cbefc53fa8..9237a5441d 100644 --- a/abstractions/python/kiota/abstractions/api_error.py +++ b/abstractions/python/kiota/abstractions/api_error.py @@ -1,3 +1,4 @@ class APIError(Exception): + def __init__(self, message: str) -> None: super().__init__(message) diff --git a/abstractions/python/kiota/abstractions/authentication/access_token_provider.py b/abstractions/python/kiota/abstractions/authentication/access_token_provider.py index 19de2d456f..16f2826882 100644 --- a/abstractions/python/kiota/abstractions/authentication/access_token_provider.py +++ b/abstractions/python/kiota/abstractions/authentication/access_token_provider.py @@ -6,6 +6,7 @@ class AccessTokenProvider(ABC): """Defines a contract for obtaining access tokens for a given url. """ + @abstractmethod async def get_authorization_token(self, uri: str) -> str: """This method is called by the BaseBearerTokenAuthenticationProvider class to get the diff --git a/abstractions/python/kiota/abstractions/authentication/allowed_hosts_validator.py b/abstractions/python/kiota/abstractions/authentication/allowed_hosts_validator.py index 0223909809..7d72fbeda0 100644 --- a/abstractions/python/kiota/abstractions/authentication/allowed_hosts_validator.py +++ b/abstractions/python/kiota/abstractions/authentication/allowed_hosts_validator.py @@ -6,6 +6,7 @@ class AllowedHostsValidator: """Maintains a list of valid hosts and allows authentication providers to check whether a host is valid before authenticating a request """ + def __init__(self, allowed_hosts: List[str]) -> None: """Creates a new AllowedHostsValidator object with provided values. diff --git a/abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py b/abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py index d63e8d9642..554cf3ebfc 100644 --- a/abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py +++ b/abstractions/python/kiota/abstractions/authentication/anonymous_authentication_provider.py @@ -8,6 +8,7 @@ class AnonymousAuthenticationProvider(AuthenticationProvider): Args: AuthenticationProvider (ABC): The abstract base class that this class implements """ + async def authenticate_request(self, request: RequestInformation) -> None: """Authenticates the provided request information diff --git a/abstractions/python/kiota/abstractions/authentication/authentication_provider.py b/abstractions/python/kiota/abstractions/authentication/authentication_provider.py index 86724da4a4..ee386251aa 100644 --- a/abstractions/python/kiota/abstractions/authentication/authentication_provider.py +++ b/abstractions/python/kiota/abstractions/authentication/authentication_provider.py @@ -7,6 +7,7 @@ class AuthenticationProvider(ABC): """ Base class for providing authentication information for a request. """ + @abstractmethod async def authenticate_request(self, request: RequestInformation) -> None: """Authenticates the application request diff --git a/abstractions/python/kiota/abstractions/authentication/base_bearer_token_authentication_provider.py b/abstractions/python/kiota/abstractions/authentication/base_bearer_token_authentication_provider.py index d0bbf22754..6525f81123 100644 --- a/abstractions/python/kiota/abstractions/authentication/base_bearer_token_authentication_provider.py +++ b/abstractions/python/kiota/abstractions/authentication/base_bearer_token_authentication_provider.py @@ -20,10 +20,10 @@ async def authenticate_request(self, request: RequestInformation) -> None: """ if not request: raise Exception("Request cannot be null") - if not request.headers: + if not request.get_request_headers(): request.headers = {} if not self.AUTHORIZATION_HEADER in request.headers: token = await self.access_token_provider.get_authorization_token(request.get_url()) if token: - request.headers.update({f'{self.AUTHORIZATION_HEADER}': f'Bearer {token}'}) + request.add_request_headers({f'{self.AUTHORIZATION_HEADER}': f'Bearer {token}'}) diff --git a/abstractions/python/kiota/abstractions/native_response_handler.py b/abstractions/python/kiota/abstractions/native_response_handler.py index 1492796aeb..e00d4ff2a9 100644 --- a/abstractions/python/kiota/abstractions/native_response_handler.py +++ b/abstractions/python/kiota/abstractions/native_response_handler.py @@ -1,7 +1,7 @@ from typing import Any, Callable, Dict, Optional, TypeVar, cast from .response_handler import ResponseHandler -from .serialization import Parsable +from .serialization import Parsable, ParsableFactory NativeResponseType = TypeVar("NativeResponseType") ModelType = TypeVar("ModelType") @@ -16,10 +16,10 @@ class NativeResponseHandler(ResponseHandler): # The error mappings for the response to use when deserializing failed responses bodies. # Where an error code like 401 applies specifically to that status code, a class code like # 4XX applies to all status codes within the range if a specific error code is not present. - error_map: Dict[str, Optional[Callable[[], Parsable]]] + error_map: Dict[str, Optional[ParsableFactory]] async def handle_response_async( - self, response: NativeResponseType, error_map: Dict[str, Optional[Callable[[], Parsable]]] + self, response: NativeResponseType, error_map: Dict[str, Optional[ParsableFactory]] ) -> ModelType: self.value = response self.error_map = error_map diff --git a/abstractions/python/kiota/abstractions/native_response_wrapper.py b/abstractions/python/kiota/abstractions/native_response_wrapper.py index 2dd981e7aa..cca88d26e6 100644 --- a/abstractions/python/kiota/abstractions/native_response_wrapper.py +++ b/abstractions/python/kiota/abstractions/native_response_wrapper.py @@ -25,6 +25,7 @@ class NativeResponseWrapper: """This class can be used to wrap a request using the fluent API and get the native response object in return. """ + async def call_and_get_native( self, original_call: OriginalCallType, q: Optional[QueryParametersType], h: Optional[HeadersType], o: Optional[RequestOption] diff --git a/abstractions/python/kiota/abstractions/request_adapter.py b/abstractions/python/kiota/abstractions/request_adapter.py index ac0b5d161a..1c661a955e 100644 --- a/abstractions/python/kiota/abstractions/request_adapter.py +++ b/abstractions/python/kiota/abstractions/request_adapter.py @@ -5,7 +5,7 @@ from .request_information import RequestInformation from .response_handler import ResponseHandler -from .serialization import Parsable, SerializationWriterFactory +from .serialization import Parsable, ParsableFactory, SerializationWriterFactory from .store import BackingStoreFactory ResponseType = TypeVar("ResponseType", str, int, float, bool, datetime, BytesIO) @@ -30,19 +30,18 @@ def get_serialization_writer_factory(self) -> SerializationWriterFactory: @abstractmethod async def send_async( - self, request_info: RequestInformation, type: ModelType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> ModelType: + self, request_info: RequestInformation, type: ParsableFactory, + response_handler: Optional[ResponseHandler], error_map: Dict[str, Optional[ParsableFactory]] + ) -> Optional[ModelType]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model. Args: request_info (RequestInformation): the request info to execute. - type (ModelType): the class of the response model to deserialize the response into. + type (ParsableFactory): the class of response model to deserialize the response into. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in case + error_map (Dict[str, Optional[ParsableFactory]]): the error dict to use in case of a failed request. Returns: @@ -52,19 +51,21 @@ async def send_async( @abstractmethod async def send_collection_async( - self, request_info: RequestInformation, type: ModelType, + self, + request_info: RequestInformation, + type: ParsableFactory, response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> List[ModelType]: + error_map: Dict[str, Optional[ParsableFactory]], + ) -> Optional[List[ModelType]]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. Args: request_info (RequestInformation): the request info to execute. - type (ModelType): the class of the response model to deserialize the response into. + type (ParsableFactory): the class of response model to deserialize the response into. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, Optional[ParsableFactory]]): the error dict to use in case of a failed request. Returns: @@ -74,9 +75,11 @@ async def send_collection_async( @abstractmethod async def send_collection_of_primitive_async( - self, request_info: RequestInformation, response_type: ResponseType, + self, + request_info: RequestInformation, + response_type: ResponseType, response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] + error_map: Dict[str, Optional[ParsableFactory]], ) -> Optional[List[ResponseType]]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. @@ -87,20 +90,19 @@ async def send_collection_of_primitive_async( response into. response_handler (Optional[ResponseType]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, Optional[ParsableFactory]]): the error dict to use in case of a failed request. Returns: - Optional[List[ModelType]]: he deserialized response model collection. + Optional[List[ModelType]]: The deserialized response model collection. """ pass @abstractmethod async def send_primitive_async( self, request_info: RequestInformation, response_type: ResponseType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> ResponseType: + response_handler: Optional[ResponseHandler], error_map: Dict[str, Optional[ParsableFactory]] + ) -> Optional[ResponseType]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. @@ -110,7 +112,7 @@ async def send_primitive_async( response into. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, Optional[ParsableFactory]]): the error dict to use in case of a failed request. Returns: @@ -121,7 +123,7 @@ async def send_primitive_async( @abstractmethod async def send_no_response_content_async( self, request_info: RequestInformation, response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] + error_map: Dict[str, Optional[ParsableFactory]] ) -> None: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. @@ -130,7 +132,7 @@ async def send_no_response_content_async( request_info (RequestInformation):the request info to execute. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, Optional[Optional[ParsableFactory]]): the error dict to use in case of a failed request. """ pass diff --git a/abstractions/python/kiota/abstractions/request_information.py b/abstractions/python/kiota/abstractions/request_information.py index 48b277e6e8..94b4ed4c8b 100644 --- a/abstractions/python/kiota/abstractions/request_information.py +++ b/abstractions/python/kiota/abstractions/request_information.py @@ -1,3 +1,4 @@ +from dataclasses import fields from io import BytesIO from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Tuple, TypeVar @@ -12,7 +13,7 @@ Url = str T = TypeVar("T", bound=Parsable) -QueryParams = TypeVar('QueryParams', int, float, str, bool, None) +QueryParams = TypeVar('QueryParams') class RequestInformation(Generic[QueryParams]): @@ -22,28 +23,30 @@ class RequestInformation(Generic[QueryParams]): BINARY_CONTENT_TYPE = 'application/octet-stream' CONTENT_TYPE_HEADER = 'Content-Type' - # The uri of the request - __uri: Optional[Url] + def __init__(self) -> None: - __request_options: Dict[str, RequestOption] = {} + # The uri of the request + self.__uri: Optional[Url] = None - # The path parameters for the current request - path_parameters: Dict[str, Any] = {} + self.__request_options: Dict[str, RequestOption] = {} - # The URL template for the request - url_template: Optional[str] + # The path parameters for the current request + self.path_parameters: Dict[str, Any] = {} - # The HTTP Method for the request - http_method: Method + # The URL template for the request + self.url_template: Optional[str] - # The query parameters for the request - query_parameters: Dict[str, QueryParams] = {} + # The HTTP Method for the request + self.http_method: Method - # The Request Headers - headers: Dict[str, str] = {} + # The query parameters for the request + self.query_parameters: Dict[str, QueryParams] = {} - # The Request Body - content: BytesIO + # The Request Headers + self.headers: Dict[str, str] = {} + + # The Request Body + self.content: BytesIO def get_url(self) -> Url: """ Gets the URL of the request @@ -79,6 +82,25 @@ def set_url(self, url: Url) -> None: self.query_parameters.clear() self.path_parameters.clear() + def get_request_headers(self) -> Optional[Dict]: + return self.headers + + def add_request_headers(self, headers_to_add: Optional[Dict[str, str]]) -> None: + """Adds headers to the request + """ + if headers_to_add: + for key in headers_to_add: + self.headers[key.lower()] = headers_to_add[key] + + def remove_request_headers(self, key: str) -> None: + """Removes a request header from the current request + + Args: + key (str): The key of the header to remove + """ + if key and key.lower() in self.headers: + del self.headers[key.lower()] + def get_request_options(self) -> List[Tuple[str, RequestOption]]: """Gets the request options for the request. """ @@ -133,3 +155,13 @@ def set_stream_content(self, value: BytesIO) -> None: """ self.headers[self.CONTENT_TYPE_HEADER] = self.BINARY_CONTENT_TYPE self.content = value + + def set_query_string_parameters_from_raw_object(self, q: Optional[QueryParams]) -> None: + if q: + for field in fields(q): + key = field.name + if hasattr(q, 'get_query_parameter'): + serialization_key = q.get_query_parameter(key) #type: ignore + if serialization_key: + key = serialization_key + self.query_parameters[key] = getattr(q, field.name) diff --git a/abstractions/python/kiota/abstractions/request_option.py b/abstractions/python/kiota/abstractions/request_option.py index adfc042b0e..a9e974d42b 100644 --- a/abstractions/python/kiota/abstractions/request_option.py +++ b/abstractions/python/kiota/abstractions/request_option.py @@ -4,6 +4,7 @@ class RequestOption(ABC): """Represents a request option """ + @abstractmethod def get_key(self) -> str: """Gets the option key for when adding it to a request. Must be unique diff --git a/abstractions/python/kiota/abstractions/response_handler.py b/abstractions/python/kiota/abstractions/response_handler.py index 4933f71be2..c8563cb3db 100644 --- a/abstractions/python/kiota/abstractions/response_handler.py +++ b/abstractions/python/kiota/abstractions/response_handler.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Callable, Dict, Optional, TypeVar -from .serialization import Parsable +from .serialization import Parsable, ParsableFactory NativeResponseType = TypeVar("NativeResponseType") ModelType = TypeVar("ModelType") @@ -10,15 +10,16 @@ class ResponseHandler(ABC): """Abstract class that defines the contract for a response handler """ + @abstractmethod async def handle_response_async( - self, response: NativeResponseType, error_map: Dict[str, Optional[Callable[[], Parsable]]] + self, response: NativeResponseType, error_map: Dict[str, Optional[ParsableFactory]] ) -> ModelType: """Callback method that is invoked when a response is received. Args: response (NativeResponseType): The type of the native response object. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use + error_map (Dict[str, Optional[ParsableFactory]]): the error dict to use in case of a failed request. Returns: diff --git a/abstractions/python/kiota/abstractions/serialization/__init__.py b/abstractions/python/kiota/abstractions/serialization/__init__.py index 017f4476bd..9dfb2175d5 100644 --- a/abstractions/python/kiota/abstractions/serialization/__init__.py +++ b/abstractions/python/kiota/abstractions/serialization/__init__.py @@ -1,5 +1,6 @@ from .additional_data_holder import AdditionalDataHolder from .parsable import Parsable +from .parsable_factory import ParsableFactory from .parse_node import ParseNode from .parse_node_factory import ParseNodeFactory from .parse_node_factory_registry import ParseNodeFactoryRegistry diff --git a/abstractions/python/kiota/abstractions/serialization/additional_data_holder.py b/abstractions/python/kiota/abstractions/serialization/additional_data_holder.py index 27cf3eea7e..362de7eab1 100644 --- a/abstractions/python/kiota/abstractions/serialization/additional_data_holder.py +++ b/abstractions/python/kiota/abstractions/serialization/additional_data_holder.py @@ -5,6 +5,7 @@ class AdditionalDataHolder(ABC): """Defines a contract for models that can hold additional data besides the described properties. """ + @abstractmethod def get_additional_data(self) -> Dict[str, Any]: """Stores the additional data for this object that did not belong to the properties. diff --git a/abstractions/python/kiota/abstractions/serialization/parsable.py b/abstractions/python/kiota/abstractions/serialization/parsable.py index 7769b44166..d5d749a0d8 100644 --- a/abstractions/python/kiota/abstractions/serialization/parsable.py +++ b/abstractions/python/kiota/abstractions/serialization/parsable.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Callable, Dict, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Dict, TypeVar -T = TypeVar('T') +T = TypeVar("T") if TYPE_CHECKING: from .parse_node import ParseNode @@ -12,12 +12,13 @@ class Parsable(ABC): """ Defines a serializable model object. """ + @abstractmethod - def get_field_deserializers(self) -> Dict[str, Callable[[T, 'ParseNode'], None]]: + def get_field_deserializers(self) -> Dict[str, Callable[['ParseNode'], None]]: """Gets the deserialization information for this object. Returns: - Dict[str, Callable[[T, ParseNode], None]]: The deserialization information for this + Dict[str, Callable[[ParseNode], None]]: The deserialization information for this object where each entry is a property key with its deserialization callback. """ pass diff --git a/abstractions/python/kiota/abstractions/serialization/parsable_factory.py b/abstractions/python/kiota/abstractions/serialization/parsable_factory.py new file mode 100644 index 0000000000..d06a311683 --- /dev/null +++ b/abstractions/python/kiota/abstractions/serialization/parsable_factory.py @@ -0,0 +1,22 @@ +from typing import Optional + +from .parsable import Parsable +from .parse_node import ParseNode, U + + +class ParsableFactory(Parsable): + """Defines the factory for creating parsable objects. + """ + + @staticmethod + def create(parse_node: Optional[ParseNode]) -> U: + """Create a new parsable object from the given serialized data. + + Args: + parse_node (Optional[ParseNode]): The node to parse to get the discriminator value + from the payload. + + Returns: + U: The parsable object. + """ + pass diff --git a/abstractions/python/kiota/abstractions/serialization/parse_node.py b/abstractions/python/kiota/abstractions/serialization/parse_node.py index 3080c8a427..bd39acb3d3 100644 --- a/abstractions/python/kiota/abstractions/serialization/parse_node.py +++ b/abstractions/python/kiota/abstractions/serialization/parse_node.py @@ -4,7 +4,7 @@ from datetime import date, datetime, time, timedelta from enum import Enum from io import BytesIO -from typing import Callable, List, Optional, TypeVar +from typing import TYPE_CHECKING, Callable, List, Optional, TypeVar from uuid import UUID from .parsable import Parsable @@ -15,12 +15,16 @@ K = TypeVar("K", bound=Enum) +if TYPE_CHECKING: + from .parsable_factory import ParsableFactory + class ParseNode(ABC): """ Interface for a deserialization node in a parse tree. This interace provides an abstraction layer over serialization formats, libraries and implementations. """ + @abstractmethod def get_string_value(self) -> str: """Gets the string value of the node @@ -124,9 +128,10 @@ def get_collection_of_primitive_values(self) -> List[T]: pass @abstractmethod - def get_collection_of_object_values(self) -> List[U]: + def get_collection_of_object_values(self, factory: ParsableFactory) -> List[U]: """Gets the collection of model object values of the node - + Args: + factory (ParsableFactory): The factory to use to create the model object. Returns: List[U]: The collection of model object values of the node """ @@ -151,9 +156,10 @@ def get_enum_value(self) -> Enum: pass @abstractmethod - def get_object_value(self, class_type: Callable[[], U]) -> U: + def get_object_value(self, factory: ParsableFactory) -> U: """Gets the model object value of the node - + Args: + factory (ParsableFactory): The factory to use to create the model object. Returns: Parsable: The model object value of the node """ diff --git a/abstractions/python/kiota/abstractions/serialization/parse_node_factory.py b/abstractions/python/kiota/abstractions/serialization/parse_node_factory.py index 95d584d111..5f24060cab 100644 --- a/abstractions/python/kiota/abstractions/serialization/parse_node_factory.py +++ b/abstractions/python/kiota/abstractions/serialization/parse_node_factory.py @@ -7,6 +7,7 @@ class ParseNodeFactory(ABC): """Defines the protocol for a factory that is used to create ParseNodes. """ + @abstractmethod def get_valid_content_type(self) -> str: """Returns the content type this factory's parse nodes can deserialize diff --git a/abstractions/python/kiota/abstractions/serialization/parse_node_factory_registry.py b/abstractions/python/kiota/abstractions/serialization/parse_node_factory_registry.py index 1bebb0a7df..f626dbe28e 100644 --- a/abstractions/python/kiota/abstractions/serialization/parse_node_factory_registry.py +++ b/abstractions/python/kiota/abstractions/serialization/parse_node_factory_registry.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from io import BytesIO from typing import Dict @@ -36,9 +37,16 @@ def get_root_parse_node(self, content_type: str, content: BytesIO) -> ParseNode: if not content: raise Exception("Content cannot be null") - factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(content_type) + vendor_specific_content_type = content_type.split(';')[0] + factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(vendor_specific_content_type) if factory: - return factory.get_root_parse_node(content_type, content) + return factory.get_root_parse_node(vendor_specific_content_type, content) + + cleaned_content_type = re.sub(r'[^/]+\+', '', vendor_specific_content_type) + factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(cleaned_content_type) + if factory: + return factory.get_root_parse_node(cleaned_content_type, content) + raise Exception( - f"Content type {content_type} does not have a factory registered to be parsed" + f"Content type {cleaned_content_type} does not have a factory registered to be parsed" ) diff --git a/abstractions/python/kiota/abstractions/serialization/parse_node_proxy_factory.py b/abstractions/python/kiota/abstractions/serialization/parse_node_proxy_factory.py index 5c7ff19bec..1f34662b01 100644 --- a/abstractions/python/kiota/abstractions/serialization/parse_node_proxy_factory.py +++ b/abstractions/python/kiota/abstractions/serialization/parse_node_proxy_factory.py @@ -9,6 +9,7 @@ class ParseNodeProxyFactory(ParseNodeFactory): """Proxy factory that allows the composition of before and after callbacks on existing factories """ + def __init__( self, concrete: ParseNodeFactory, on_before: Callable[[Parsable], None], on_after: Callable[[Parsable], None] diff --git a/abstractions/python/kiota/abstractions/serialization/serialization_writer.py b/abstractions/python/kiota/abstractions/serialization/serialization_writer.py index 6e6693bca1..835da53d9e 100644 --- a/abstractions/python/kiota/abstractions/serialization/serialization_writer.py +++ b/abstractions/python/kiota/abstractions/serialization/serialization_writer.py @@ -16,6 +16,7 @@ class SerializationWriter(ABC): """Defines an interface for serialization of objects to a stream """ + @abstractmethod def write_string_value(self, key: Optional[str], value: Optional[str]) -> None: """Writes the specified string value to the stream with an optional given key. diff --git a/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory.py b/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory.py index e9e24e70b3..1b16a5aab2 100644 --- a/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory.py +++ b/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory.py @@ -6,6 +6,7 @@ class SerializationWriterFactory(ABC): """Defines the contract for a factory that creates SerializationWriter instances. """ + @abstractmethod def get_valid_content_type(self) -> str: """Gets the content type this factory creates serialization writers for. diff --git a/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory_registry.py b/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory_registry.py index 2fd5c2e848..f2fa4e4a12 100644 --- a/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory_registry.py +++ b/abstractions/python/kiota/abstractions/serialization/serialization_writer_factory_registry.py @@ -1,3 +1,4 @@ +import re from typing import Dict from .serialization_writer import SerializationWriter @@ -32,9 +33,16 @@ def get_serialization_writer(self, content_type: str) -> SerializationWriter: if not content_type: raise Exception("Content type cannot be null") - factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(content_type) + vendor_specific_content_type = content_type.split(';')[0] + factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(vendor_specific_content_type) if factory: - return factory.get_serialization_writer(content_type) + return factory.get_serialization_writer(vendor_specific_content_type) + cleaned_content_type = re.sub(r'[^/]+\+', '', vendor_specific_content_type) + + factory = self.CONTENT_TYPE_ASSOCIATED_FACTORIES.get(cleaned_content_type) + if factory: + return factory.get_serialization_writer(cleaned_content_type) raise Exception( - f"Content type {content_type} does not have a factory registered to be serialized" + f"Content type {cleaned_content_type} does not have a factory registered" + "to be serialized" ) diff --git a/abstractions/python/kiota/abstractions/serialization/serialization_writer_proxy_factory.py b/abstractions/python/kiota/abstractions/serialization/serialization_writer_proxy_factory.py index d60e080689..b9ee0b3f5e 100644 --- a/abstractions/python/kiota/abstractions/serialization/serialization_writer_proxy_factory.py +++ b/abstractions/python/kiota/abstractions/serialization/serialization_writer_proxy_factory.py @@ -8,6 +8,7 @@ class SerializationWriterProxyFactory(SerializationWriterFactory): """Proxy factory that allows the composition of before and after callbacks on existing factories """ + def __init__( self, concrete: SerializationWriterFactory, on_before: Optional[Callable[[Parsable], None]], on_after: Optional[Callable[[Parsable], None]], diff --git a/abstractions/python/kiota/abstractions/store/__init__.py b/abstractions/python/kiota/abstractions/store/__init__.py index a82c7c15ed..2d4602719a 100644 --- a/abstractions/python/kiota/abstractions/store/__init__.py +++ b/abstractions/python/kiota/abstractions/store/__init__.py @@ -1,5 +1,3 @@ -import imp - from .backed_model import BackingStore from .backing_store import BackingStore from .backing_store_factory import BackingStoreFactory diff --git a/abstractions/python/kiota/abstractions/store/backed_model.py b/abstractions/python/kiota/abstractions/store/backed_model.py index 1afe81ecbf..68aaaa1745 100644 --- a/abstractions/python/kiota/abstractions/store/backed_model.py +++ b/abstractions/python/kiota/abstractions/store/backed_model.py @@ -6,6 +6,7 @@ class BackedModel(ABC): """Defines the contracts for a model that is backed by a store. """ + @abstractmethod def get_backing_store(self) -> BackingStore: """Gets the store that is backing the model diff --git a/abstractions/python/kiota/abstractions/store/backing_store.py b/abstractions/python/kiota/abstractions/store/backing_store.py index a1c1e8f642..f08b5983e1 100644 --- a/abstractions/python/kiota/abstractions/store/backing_store.py +++ b/abstractions/python/kiota/abstractions/store/backing_store.py @@ -9,6 +9,7 @@ class BackingStore(ABC): Implementations can provide dirty tracking capabilities, caching capabilities or integration with 3rd party stores """ + @abstractmethod def get(self, key: str) -> Optional[T]: """Gets a value from the backing store based on its key. Returns null if the value hasn't diff --git a/abstractions/python/kiota/abstractions/store/backing_store_factory.py b/abstractions/python/kiota/abstractions/store/backing_store_factory.py index 2a1e11bb1c..b23e588b3f 100644 --- a/abstractions/python/kiota/abstractions/store/backing_store_factory.py +++ b/abstractions/python/kiota/abstractions/store/backing_store_factory.py @@ -6,6 +6,7 @@ class BackingStoreFactory(ABC): """Defines the contract for a factory that creates backing stores. """ + def create_backing_store(self) -> BackingStore: """Creates a new instance of the backing store. diff --git a/abstractions/python/kiota/abstractions/store/backing_store_parse_node_factory.py b/abstractions/python/kiota/abstractions/store/backing_store_parse_node_factory.py index 27b7bc9677..3e42ae045c 100644 --- a/abstractions/python/kiota/abstractions/store/backing_store_parse_node_factory.py +++ b/abstractions/python/kiota/abstractions/store/backing_store_parse_node_factory.py @@ -8,10 +8,12 @@ class BackingStoreParseNodeFactory(ParseNodeProxyFactory): """Proxy implementation of ParseNodeFactory for the backing store that automatically sets the state of the backing store when deserializing. """ + def __init__(self, concrete: ParseNodeFactory) -> None: """ Initializes a new instance of the BackingStoreParseNodeFactory class given a concrete implementation ParseNodeFactory. """ + def func1(x): if isinstance(x, BackedModel): backed_model = x diff --git a/abstractions/python/kiota/abstractions/store/backing_store_serialization_writer_proxy_factory.py b/abstractions/python/kiota/abstractions/store/backing_store_serialization_writer_proxy_factory.py index e8e2b1e93e..57e81d7116 100644 --- a/abstractions/python/kiota/abstractions/store/backing_store_serialization_writer_proxy_factory.py +++ b/abstractions/python/kiota/abstractions/store/backing_store_serialization_writer_proxy_factory.py @@ -6,6 +6,7 @@ class BackingStoreSerializationWriterProxyFactory(SerializationWriterProxyFactor """Proxy implementation of SerializationWriterFactory for the backing store that automatically sets the state of the backing store when serializing. """ + def __init__(self, concrete: SerializationWriterFactory) -> None: """Initializes a new instance of the BackingStoreSerializationWriterProxyFactory class given a concrete implementation of SerializationWriterFactory. @@ -14,6 +15,7 @@ def __init__(self, concrete: SerializationWriterFactory) -> None: concrete (SerializationWriterFactory): a concrete implementation of SerializationWriterFactory to wrap. """ + def func1(x): if isinstance(x, BackedModel): backed_model = x diff --git a/abstractions/python/kiota/abstractions/store/in_memory_backing_store_factory.py b/abstractions/python/kiota/abstractions/store/in_memory_backing_store_factory.py index 09b1578382..b31652bd5e 100644 --- a/abstractions/python/kiota/abstractions/store/in_memory_backing_store_factory.py +++ b/abstractions/python/kiota/abstractions/store/in_memory_backing_store_factory.py @@ -6,5 +6,6 @@ class InMemoryBackingStoreFactory(BackingStoreFactory): """This class is used to create instances of InMemoryBackingStore """ + def create_backing_store(self) -> BackingStore: return InMemoryBackingStore() diff --git a/http/python/requests/http_requests/_version.py b/http/python/requests/http_requests/_version.py index 6b47c142e5..8e3d668e72 100644 --- a/http/python/requests/http_requests/_version.py +++ b/http/python/requests/http_requests/_version.py @@ -1 +1 @@ -VERSION: str = '0.1.0' +VERSION: str = '0.2.0' diff --git a/http/python/requests/http_requests/kiota_client_factory.py b/http/python/requests/http_requests/kiota_client_factory.py index 6ae3f520b7..67a2a4c853 100644 --- a/http/python/requests/http_requests/kiota_client_factory.py +++ b/http/python/requests/http_requests/kiota_client_factory.py @@ -2,8 +2,7 @@ import requests -from .middleware import MiddlewarePipeline, RetryHandler - +from .middleware import MiddlewarePipeline, ParametersNameDecodingHandler, RetryHandler class KiotaClientFactory: DEFAULT_CONNECTION_TIMEOUT: int = 30 @@ -35,6 +34,7 @@ def _register_default_middleware(self, session: requests.Session) -> requests.Se """ middleware_pipeline = MiddlewarePipeline() middlewares = [ + ParametersNameDecodingHandler(), RetryHandler(), ] diff --git a/http/python/requests/http_requests/middleware/__init__.py b/http/python/requests/http_requests/middleware/__init__.py index 2ca89dee50..536b81f283 100644 --- a/http/python/requests/http_requests/middleware/__init__.py +++ b/http/python/requests/http_requests/middleware/__init__.py @@ -1,2 +1,3 @@ from .middleware import MiddlewarePipeline +from .parameters_name_decoding_handler import ParametersNameDecodingHandler from .retry_handler import RetryHandler diff --git a/http/python/requests/http_requests/middleware/options/__init__.py b/http/python/requests/http_requests/middleware/options/__init__.py index d71b2d1d27..16b887d9d5 100644 --- a/http/python/requests/http_requests/middleware/options/__init__.py +++ b/http/python/requests/http_requests/middleware/options/__init__.py @@ -1 +1,2 @@ +from .parameters_name_decoding_options import ParametersNameDecodingHandlerOption from .retry_handler_option import RetryHandlerOptions diff --git a/http/python/requests/http_requests/middleware/options/parameters_name_decoding_options.py b/http/python/requests/http_requests/middleware/options/parameters_name_decoding_options.py new file mode 100644 index 0000000000..db2d17325d --- /dev/null +++ b/http/python/requests/http_requests/middleware/options/parameters_name_decoding_options.py @@ -0,0 +1,22 @@ +from typing import List +from kiota.abstractions.request_option import RequestOption + +class ParametersNameDecodingHandlerOption(RequestOption): + """The ParametersNameDecodingOptions request class + """ + + parameters_name_decoding_handler_options_key = "ParametersNameDecodingOptionKey" + + def __init__(self, enable: bool = True, characters_to_decode: List[str] = [".", "-", "~", "$"]) -> None: + """To create an instance of ParametersNameDecodingHandlerOptions + + Args: + enable (bool, optional): - Whether to decode the specified characters in the request query parameters names. + Defaults to True. + characters_to_decode (List[str], optional):- The characters to decode. Defaults to [".", "-", "~", "$"]. + """ + self.enable = enable + self.characters_to_decode = characters_to_decode + + def get_key(self) -> str: + return self.parameters_name_decoding_handler_options_key \ No newline at end of file diff --git a/http/python/requests/http_requests/middleware/parameters_name_decoding_handler.py b/http/python/requests/http_requests/middleware/parameters_name_decoding_handler.py new file mode 100644 index 0000000000..858f324cae --- /dev/null +++ b/http/python/requests/http_requests/middleware/parameters_name_decoding_handler.py @@ -0,0 +1,45 @@ +from typing import Dict +from kiota.abstractions.request_option import RequestOption +from requests import PreparedRequest, Response + +from .middleware import BaseMiddleware +from .options import ParametersNameDecodingHandlerOption + +class ParametersNameDecodingHandler(BaseMiddleware): + + def __init__(self, options: ParametersNameDecodingHandlerOption = ParametersNameDecodingHandlerOption(), **kwargs): + """Create an instance of ParametersNameDecodingHandler + + Args: + options (ParametersNameDecodingHandlerOption, optional): The parameters name decoding handler options value. + Defaults to ParametersNameDecodingHandlerOption + """ + if not options: + raise Exception("The options parameter is required.") + + self.options = options + + def send(self, request: PreparedRequest, request_options: Dict[str, RequestOption], **kwargs) -> Response: + """To execute the current middleware + + Args: + request (PreparedRequest): The prepared request object + request_options (Dict[str, RequestOption]): The request options + + Returns: + Response: The response object. + """ + current_options = self.options + options_key = ParametersNameDecodingHandlerOption.parameters_name_decoding_handler_options_key + if request_options and options_key in request_options.keys(): + current_options = request_options[options_key] + + updated_url = request.url + if current_options and current_options.enable and '%' in updated_url and current_options.characters_to_decode: + for char in current_options.characters_to_decode: + encoding = f"{ord(f'{char}:X')}" + updated_url = updated_url.replace(f'%{encoding}', char) + + request.url = updated_url + response = super().send(request, **kwargs) + return response \ No newline at end of file diff --git a/http/python/requests/http_requests/requests_request_adapter.py b/http/python/requests/http_requests/requests_request_adapter.py index cba527fb1f..e134bc2507 100644 --- a/http/python/requests/http_requests/requests_request_adapter.py +++ b/http/python/requests/http_requests/requests_request_adapter.py @@ -12,6 +12,7 @@ from kiota.abstractions.response_handler import ResponseHandler from kiota.abstractions.serialization import ( Parsable, + ParsableFactory, ParseNode, ParseNodeFactory, ParseNodeFactoryRegistry, @@ -91,18 +92,17 @@ def get_response_content_type(self, response: requests.Response) -> Optional[str return segments[0] async def send_async( - self, request_info: RequestInformation, model_type: ModelType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> ModelType: + self, request_info: RequestInformation, model_type: ParsableFactory, + response_handler: Optional[ResponseHandler], error_map: Dict[str, ParsableFactory] + ) -> Optional[ModelType]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model. Args: request_info (RequestInformation): the request info to execute. - type (ModelType): the class of the response model to deserialize the response into. + type (ParsableFactory): the class of the response model to deserialize the response into response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, ParsableFactory]): the error dict to use in case of a failed request. Returns: @@ -117,23 +117,24 @@ async def send_async( return await response_handler.handle_response_async(response, error_map) await self.throw_failed_responses(response, error_map) + if self._should_return_none(response): + return None root_node = await self.get_root_parse_node(response) result = root_node.get_object_value(model_type) return result async def send_collection_async( - self, request_info: RequestInformation, model_type: ModelType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> List[ModelType]: + self, request_info: RequestInformation, model_type: ParsableFactory, + response_handler: Optional[ResponseHandler], error_map: Dict[str, ParsableFactory] + ) -> Optional[List[ModelType]]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. Args: request_info (RequestInformation): the request info to execute. - type (ModelType): the class of the response model to deserialize the response into. + type (ParsableFactory): the class of the response model to deserialize the response into response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, ParsableFactory]): the error dict to use in case of a failed request. Returns: @@ -148,14 +149,15 @@ async def send_collection_async( return await response_handler.handle_response_async(response, error_map) await self.throw_failed_responses(response, error_map) + if self._should_return_none(response): + return None root_node = await self.get_root_parse_node(response) result = root_node.get_collection_of_object_values(model_type) return result async def send_collection_of_primitive_async( self, request_info: RequestInformation, response_type: ResponseType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] + response_handler: Optional[ResponseHandler], error_map: Dict[str, ParsableFactory] ) -> Optional[List[ResponseType]]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. @@ -165,7 +167,7 @@ async def send_collection_of_primitive_async( response into. response_handler (Optional[ResponseType]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in + error_map (Dict[str, ParsableFactory]): the error dict to use in case of a failed request. Returns: @@ -180,14 +182,15 @@ async def send_collection_of_primitive_async( return await response_handler.handle_response_async(response, error_map) await self.throw_failed_responses(response, error_map) + if self._should_return_none(response): + return None root_node = await self.get_root_parse_node(response) return root_node.get_collection_of_primitive_values() async def send_primitive_async( self, request_info: RequestInformation, response_type: ResponseType, - response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] - ) -> ResponseType: + response_handler: Optional[ResponseHandler], error_map: Dict[str, ParsableFactory] + ) -> Optional[ResponseType]: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. Args: @@ -196,7 +199,7 @@ async def send_primitive_async( response into. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in case + error_map (Dict[str, ParsableFactory]): the error dict to use in case of a failed request. Returns: @@ -211,6 +214,8 @@ async def send_primitive_async( return await response_handler.handle_response_async(response, error_map) await self.throw_failed_responses(response, error_map) + if self._should_return_none(response): + return None root_node = await self.get_root_parse_node(response) if response_type == str: return root_node.get_string_value() @@ -228,7 +233,7 @@ async def send_primitive_async( async def send_no_response_content_async( self, request_info: RequestInformation, response_handler: Optional[ResponseHandler], - error_map: Dict[str, Optional[Callable[[], Parsable]]] + error_map: Dict[str, ParsableFactory] ) -> None: """Excutes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model. @@ -236,7 +241,7 @@ async def send_no_response_content_async( request_info (RequestInformation):the request info to execute. response_handler (Optional[ResponseHandler]): The response handler to use for the HTTP request instead of the default handler. - error_map (Dict[str, Optional[Callable[[], Parsable]]]): the error dict to use in case + error_map (Dict[str, ParsableFactory]): the error dict to use in case of a failed request. """ if not request_info: @@ -266,15 +271,18 @@ def enable_backing_store(self, backing_store_factory: Optional[BackingStoreFacto async def get_root_parse_node(self, response: requests.Response) -> ParseNode: payload = response.content - print(payload) response_content_type = self.get_response_content_type(response) + if not response_content_type: raise Exception("No response content type found for deserialization") return self._parse_node_factory.get_root_parse_node(response_content_type, payload) + def _should_return_none(self, response: requests.Response) -> bool: + return response.status_code == 204 + async def throw_failed_responses( - self, response: requests.Response, error_map: Dict[str, Optional[Callable[[], Parsable]]] + self, response: requests.Response, error_map: Dict[str, ParsableFactory] ) -> None: if response.ok: return @@ -325,7 +333,7 @@ def get_request_from_request_information( req = requests.Request( method=str(request_info.http_method), url=request_info.get_url(), - headers=request_info.headers, + headers=request_info.get_request_headers(), data=request_info.content, params=request_info.query_parameters, ) diff --git a/http/python/requests/tests/test_requests_request_adapter.py b/http/python/requests/tests/test_requests_request_adapter.py index bcb438438c..e3bc2ead8a 100644 --- a/http/python/requests/tests/test_requests_request_adapter.py +++ b/http/python/requests/tests/test_requests_request_adapter.py @@ -197,6 +197,30 @@ def mock_primitive_response(mocker): resp = session.send(prepped) return resp +@pytest.fixture +@responses.activate +def mock_no_content_response(mocker): + responses.add( + responses.GET, + url=BASE_URL, + status=204, + match=[ + matchers.header_matcher({"Content-Type": "application/json"}, strict_match=True) + ] + ) + + session = requests.Session() + prepped = session.prepare_request( + requests.Request( + method="GET", + url=BASE_URL, + ) + ) + prepped.headers = {"Content-Type": "application/json"} + + resp = session.send(prepped) + return resp + def test_create_requests_request_adapter(auth_provider, parse_node_factory, serialization_writer_factory): request_adapter = RequestsRequestAdapter(auth_provider, parse_node_factory, serialization_writer_factory) assert request_adapter._authentication_provider is auth_provider @@ -293,3 +317,12 @@ async def test_send_primitive_async(request_adapter, request_info, mock_primitiv assert resp.headers.get("content-type") == 'application/json' final_result = await request_adapter.send_primitive_async(request_info, float, None, {}) assert final_result == 22.3 + +@pytest.mark.asyncio +@responses.activate +async def test_send_primitive_async(request_adapter, request_info, mock_no_content_response): + request_adapter.get_http_response_message = AsyncMock(return_value = mock_no_content_response) + resp = await request_adapter.get_http_response_message(request_info) + assert resp.headers.get("content-type") == 'application/json' + final_result = await request_adapter.send_primitive_async(request_info, float, None, {}) + assert final_result is None diff --git a/serialization/python/json/.pylintrc b/serialization/python/json/.pylintrc index f0fbe81f44..eace9500b6 100644 --- a/serialization/python/json/.pylintrc +++ b/serialization/python/json/.pylintrc @@ -60,15 +60,9 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, +disable=long-suffix, old-ne-operator, old-octal-literal, - import-star-module-level, non-ascii-bytes-literal, raw-checker-failed, bad-inline-option, @@ -78,78 +72,14 @@ disable=print-statement, useless-suppression, deprecated-pragma, use-symbolic-message-instead, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, too-few-public-methods, - no-self-use, missing-module-docstring, missing-class-docstring, missing-function-docstring, C0103, - C0330, R0801, R0904, - R0911, - R0912, # Enable the message, report, category or checker with the given id(s). You can @@ -326,13 +256,6 @@ max-line-length=100 # Maximum number of lines in a module. max-module-lines=1000 -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma, - dict-separator - # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no diff --git a/serialization/python/json/Pipfile b/serialization/python/json/Pipfile index 1c0b304b18..1f15b80567 100644 --- a/serialization/python/json/Pipfile +++ b/serialization/python/json/Pipfile @@ -20,4 +20,5 @@ yapf = {version="0.31.0", index="pypi"} isort = {version="5.10.1", index="pypi"} pytest = {version="7.1.2", index="pypi"} pytest-cov = {version="3.0.0", index="pypi"} -types-python-dateutil = {version="*", index="pypi"} \ No newline at end of file +types-python-dateutil = {version="*", index="pypi"} +toml = "==0.10.2" \ No newline at end of file diff --git a/serialization/python/json/Pipfile.lock b/serialization/python/json/Pipfile.lock index 54b563c7bd..67f1941302 100644 --- a/serialization/python/json/Pipfile.lock +++ b/serialization/python/json/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "99dae0890bc8fe34167af3db1db4faa52b04c267c90af3463bb5618bdb5fc8e4" + "sha256": "34b964d6845f07905feaa867da35d7d6ee9f6f24cf2eb1071817d1525d44ff6e" }, "pipfile-spec": 6, "requires": {}, @@ -49,11 +49,18 @@ "develop": { "astroid": { "hashes": [ - "sha256:4f933d0bf5e408b03a6feb5d23793740c27e07340605f236496cd6ce552043d6", - "sha256:ba33a82a9a9c06a5ceed98180c5aab16e29c285b828d94696bf32d6015ea82a9" + "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b", + "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946" ], "markers": "python_full_version >= '3.6.2'", - "version": "==2.11.6" + "version": "==2.11.7" + }, + "atomicwrites": { + "hashes": [ + "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.4.1" }, "attrs": { "hashes": [ @@ -79,55 +86,63 @@ "markers": "python_version >= '3.6'", "version": "==2.1.0" }, + "colorama": { + "hashes": [ + "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", + "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" + ], + "markers": "sys_platform == 'win32' and sys_platform == 'win32'", + "version": "==0.4.5" + }, "coverage": { "extras": [ "toml" ], "hashes": [ - "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749", - "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982", - "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3", - "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9", - "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428", - "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e", - "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c", - "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9", - "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264", - "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605", - "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397", - "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d", - "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c", - "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815", - "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068", - "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b", - "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4", - "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4", - "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3", - "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84", - "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83", - "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4", - "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8", - "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb", - "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d", - "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df", - "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6", - "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b", - "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72", - "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13", - "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df", - "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc", - "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6", - "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28", - "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b", - "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4", - "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad", - "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46", - "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3", - "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9", - "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54" + "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32", + "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7", + "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996", + "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55", + "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46", + "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de", + "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039", + "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee", + "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1", + "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f", + "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63", + "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083", + "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe", + "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0", + "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6", + "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe", + "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933", + "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0", + "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c", + "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07", + "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8", + "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b", + "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e", + "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120", + "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f", + "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e", + "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd", + "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f", + "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386", + "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8", + "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae", + "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc", + "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783", + "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d", + "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c", + "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97", + "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978", + "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf", + "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29", + "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39", + "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452" ], "markers": "python_version >= '3.7'", - "version": "==6.4.1" + "version": "==6.4.2" }, "dill": { "hashes": [ @@ -139,13 +154,13 @@ }, "docutils": { "hashes": [ - "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", - "sha256:2d58a992bfba95218c0bda128b127da1dfc6c3a1bffc1ce5e309fcc2c6652745", - "sha256:318e45b581ff01158741a399fbd75cd96cf20dffd7e67f73a7d70ace2c2b6bc1", - "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", + "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", + "sha256:5e493091c81994fdf8b1efd177b193ad5961c1c79ba70c49ddcb7f1bff5f2d0b", + "sha256:63f739bab1f40eec651870cc81e34a1b9406d5f80797e67dd2850b2fdd00847b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.18.1" + "markers": "python_version >= '3.7'", + "version": "==0.19" }, "flit": { "hashes": [ @@ -344,16 +359,16 @@ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4'", + "markers": "python_version >= '3.7' and python_version < '4.0'", "version": "==2.28.1" }, - "setuptools": { + "toml": { "hashes": [ - "sha256:990a4f7861b31532871ab72331e755b5f14efbe52d336ea7f6118144dd478741", - "sha256:c1848f654aea2e3526d17fc3ce6aeaa5e7e24e66e645b5be2171f3f6b4e5a178" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '3.7'", - "version": "==62.6.0" + "index": "pypi", + "version": "==0.10.2" }, "tomli": { "hashes": [ @@ -373,11 +388,11 @@ }, "tomlkit": { "hashes": [ - "sha256:0f4050db66fd445b885778900ce4dd9aea8c90c4721141fde0d6ade893820ef1", - "sha256:71ceb10c0eefd8b8f11fe34e8a51ad07812cb1dc3de23247425fbc9ddc47b9dd" + "sha256:1c5bebdf19d5051e2e1de6cf70adfc5948d47221f097fcff7a3ffc91e953eaf5", + "sha256:61901f81ff4017951119cd0d1ed9b7af31c821d6845c8c477587bbdcd5e5854e" ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==0.11.0" + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==0.11.1" }, "types-python-dateutil": { "hashes": [ @@ -389,19 +404,19 @@ }, "typing-extensions": { "hashes": [ - "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", - "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" + "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", + "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version >= '3.7'", - "version": "==4.2.0" + "markers": "python_version < '3.10'", + "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", - "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", + "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.9" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", + "version": "==1.26.10" }, "wrapt": { "hashes": [ diff --git a/serialization/python/json/serialization_json/_version.py b/serialization/python/json/serialization_json/_version.py index 6bf6b2c362..8dd7053bf0 100644 --- a/serialization/python/json/serialization_json/_version.py +++ b/serialization/python/json/serialization_json/_version.py @@ -1 +1 @@ -VERSION: str = '0.3.0' +VERSION: str = '0.4.0' diff --git a/serialization/python/json/serialization_json/json_parse_node.py b/serialization/python/json/serialization_json/json_parse_node.py index 4ffe698263..c926a31529 100644 --- a/serialization/python/json/serialization_json/json_parse_node.py +++ b/serialization/python/json/serialization_json/json_parse_node.py @@ -9,7 +9,12 @@ from uuid import UUID from dateutil import parser -from kiota.abstractions.serialization import AdditionalDataHolder, Parsable, ParseNode +from kiota.abstractions.serialization import ( + AdditionalDataHolder, + Parsable, + ParsableFactory, + ParseNode, +) T = TypeVar("T") @@ -138,7 +143,7 @@ def get_collection_of_primitive_values(self) -> Optional[List[T]]: List[T]: The collection of primitive values """ - def func(item): + def func(item): # pylint: disable=too-many-return-statements generic_type = type(item) current_parse_node = JsonParseNode(item) if generic_type == bool: @@ -165,14 +170,14 @@ def func(item): return list(map(func, json.loads(self._json_node))) return list(map(func, list(self._json_node))) - def get_collection_of_object_values(self, class_type: Type[U]) -> List[U]: + def get_collection_of_object_values(self, factory: ParsableFactory) -> List[U]: """Gets the collection of type U values from the json node Returns: List[U]: The collection of model object values of the node """ return list( map( - lambda x: JsonParseNode(json.dumps(x)).get_object_value(class_type), # type: ignore + lambda x: JsonParseNode(json.dumps(x)).get_object_value(factory), # type: ignore json.loads(self._json_node) ) ) @@ -203,12 +208,12 @@ def get_enum_value(self, enum_class: K) -> Optional[K]: raise Exception(f'Invalid key: {raw_key} for enum {enum_class._name_}.') return None - def get_object_value(self, class_type: Callable[[], U]) -> U: + def get_object_value(self, factory: ParsableFactory) -> U: """Gets the model object value of the node Returns: Parsable: The model object value of the node """ - result = class_type() + result = factory.create(self) if self.on_before_assign_field_values: self.on_before_assign_field_values(result) self._assign_field_values(result) @@ -267,8 +272,7 @@ def _assign_field_values(self, item: U) -> None: snake_case_key = re.sub(r'(? Optio return value.__dict__ return None - def write_any_value(self, key: Optional[str], value: Any) -> Any: + def write_any_value(self, key: Optional[str], value: Any) -> Any: # pylint: disable=too-many-return-statements,too-many-branches """Writes the specified value to the stream with an optional given key. Args: key (Optional[str]): The key to be used for the written value. May be null. diff --git a/serialization/python/json/tests/helpers/user.py b/serialization/python/json/tests/helpers/user.py index 0fc8d4b22a..3f48cb0a67 100644 --- a/serialization/python/json/tests/helpers/user.py +++ b/serialization/python/json/tests/helpers/user.py @@ -87,34 +87,34 @@ def get_additional_data(self) -> Dict[str, Any]: def set_additional_data(self, data: Dict[str, Any]) -> None: self._additional_data = data - def get_field_deserializers(self) -> Dict[str, Callable[[T, ParseNode], None]]: + def get_field_deserializers(self) -> Dict[str, Callable[[ParseNode], None]]: """Gets the deserialization information for this object. Returns: - Dict[str, Callable[[T, ParseNode], None]]: The deserialization information for this + Dict[str, Callable[[ParseNode], None]]: The deserialization information for this object where each entry is a property key with its deserialization callback. """ return { "id": - lambda o, n: o.set_id(n.get_uuid_value()), + lambda n: self.set_id(n.get_uuid_value()), "display_name": - lambda o, n: o.set_display_name(n.get_string_value()), + lambda n: self.set_display_name(n.get_string_value()), "office_location": - lambda o, n: o.set_office_location(n.get_enum_value(OfficeLocation)), + lambda n: self.set_office_location(n.get_enum_value(OfficeLocation)), "updated_at": - lambda o, n: o.set_updated_at(n.get_datetime_offset_value()), + lambda n: self.set_updated_at(n.get_datetime_offset_value()), "birthday": - lambda o, n: o.set_birthday(n.get_date_value()), + lambda n: self.set_birthday(n.get_date_value()), "business_phones": - lambda o, n: o.set_business_phones(n.get_collection_of_primitive_values()), + lambda n: self.set_business_phones(n.get_collection_of_primitive_values()), "mobile_phone": - lambda o, n: o.set_mobile_phone(n.get_string_value()), + lambda n: self.set_mobile_phone(n.get_string_value()), "is_active": - lambda o, n: o.set_is_active(n.get_boolean_value()), + lambda n: self.set_is_active(n.get_boolean_value()), "age": - lambda o, n: o.set_age(n.get_int_value()), + lambda n: self.set_age(n.get_int_value()), "gpa": - lambda o, n: o.set_gpa(n.get_float_value()) + lambda n: self.set_gpa(n.get_float_value()) } def serialize(self, writer: SerializationWriter) -> None: