From 11467f5e15f6d85a8c30f32a4399f88e63d42f8e Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Tue, 2 Mar 2021 22:54:05 -0500 Subject: [PATCH 1/9] create generic core conn str parser --- sdk/core/azure-core/azure/core/__init__.py | 4 ++- .../azure/core/_connection_string_parser.py | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 sdk/core/azure-core/azure/core/_connection_string_parser.py diff --git a/sdk/core/azure-core/azure/core/__init__.py b/sdk/core/azure-core/azure/core/__init__.py index ddd1d8da4b69..22b3d1cacb84 100644 --- a/sdk/core/azure-core/azure/core/__init__.py +++ b/sdk/core/azure-core/azure/core/__init__.py @@ -30,12 +30,14 @@ from ._pipeline_client import PipelineClient from ._match_conditions import MatchConditions from ._enum_meta import CaseInsensitiveEnumMeta +from ._connection_string_parser import parse_connection_string_to_dict __all__ = [ "PipelineClient", "MatchConditions", - "CaseInsensitiveEnumMeta" + "CaseInsensitiveEnumMeta", + "parse_connection_string_to_dict" ] try: diff --git a/sdk/core/azure-core/azure/core/_connection_string_parser.py b/sdk/core/azure-core/azure/core/_connection_string_parser.py new file mode 100644 index 000000000000..edc43245a532 --- /dev/null +++ b/sdk/core/azure-core/azure/core/_connection_string_parser.py @@ -0,0 +1,31 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from typing import Dict + +CS_DELIMITER = ";" +CS_VAL_SEPARATOR = "=" + +def parse_connection_string_to_dict(conn_str): + # type: (str) -> Dict[str, str] + """Parses the connection string into a dict containing its component parts. + each key in the connection string has a provided value. Checks that + :param conn_str: String with connection details provided by Azure services. + :type conn_str: str + :rtype: Dict[str, str] + :raises: + ValueError: if each key in conn_str does not have a corresponding value and + for other bad formatting of connection strings - including duplicate + args, bad syntax, etc. + """ + + cs_args = [s.split("=", 1) for s in conn_str.strip().rstrip(";").split(CS_DELIMITER)] + if any(len(tup) != 2 for tup in cs_args): + raise ValueError("Connection string is either blank or malformed.") + args_dict = dict(cs_args) # type: ignore + + if len(cs_args) != len(args_dict): + raise ValueError("Connection string is either blank or malformed.") + return args_dict From 3bee2b57a673b1010c9163c0c7ab346c8bfdf060 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Thu, 11 Mar 2021 18:39:37 -0500 Subject: [PATCH 2/9] update core version to enable conn str parser feature --- sdk/core/azure-core/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 90e3b12f5520..0b009be9574b 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -2,6 +2,9 @@ ## 1.11.1 (Unreleased) +### Features + +- Added `parse_connection_string_to_dict` function for generic parsing and validation of connection strings across SDKs. ## 1.11.0 (2021-02-08) From 44297e3cf7a2a9c63f506f71bf28df69ef0c3d20 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Wed, 17 Mar 2021 18:55:32 -0400 Subject: [PATCH 3/9] moved conn str parser to utils and added ConnectionStringProperties class --- sdk/core/azure-core/azure/core/__init__.py | 2 - .../azure/core/_connection_string_parser.py | 31 ---- .../azure-core/azure/core/utils/__init__.py | 31 ++++ .../core/utils/_connection_string_parser.py | 153 ++++++++++++++++++ 4 files changed, 184 insertions(+), 33 deletions(-) delete mode 100644 sdk/core/azure-core/azure/core/_connection_string_parser.py create mode 100644 sdk/core/azure-core/azure/core/utils/__init__.py create mode 100644 sdk/core/azure-core/azure/core/utils/_connection_string_parser.py diff --git a/sdk/core/azure-core/azure/core/__init__.py b/sdk/core/azure-core/azure/core/__init__.py index 22b3d1cacb84..f4543b08c571 100644 --- a/sdk/core/azure-core/azure/core/__init__.py +++ b/sdk/core/azure-core/azure/core/__init__.py @@ -30,14 +30,12 @@ from ._pipeline_client import PipelineClient from ._match_conditions import MatchConditions from ._enum_meta import CaseInsensitiveEnumMeta -from ._connection_string_parser import parse_connection_string_to_dict __all__ = [ "PipelineClient", "MatchConditions", "CaseInsensitiveEnumMeta", - "parse_connection_string_to_dict" ] try: diff --git a/sdk/core/azure-core/azure/core/_connection_string_parser.py b/sdk/core/azure-core/azure/core/_connection_string_parser.py deleted file mode 100644 index edc43245a532..000000000000 --- a/sdk/core/azure-core/azure/core/_connection_string_parser.py +++ /dev/null @@ -1,31 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -from typing import Dict - -CS_DELIMITER = ";" -CS_VAL_SEPARATOR = "=" - -def parse_connection_string_to_dict(conn_str): - # type: (str) -> Dict[str, str] - """Parses the connection string into a dict containing its component parts. - each key in the connection string has a provided value. Checks that - :param conn_str: String with connection details provided by Azure services. - :type conn_str: str - :rtype: Dict[str, str] - :raises: - ValueError: if each key in conn_str does not have a corresponding value and - for other bad formatting of connection strings - including duplicate - args, bad syntax, etc. - """ - - cs_args = [s.split("=", 1) for s in conn_str.strip().rstrip(";").split(CS_DELIMITER)] - if any(len(tup) != 2 for tup in cs_args): - raise ValueError("Connection string is either blank or malformed.") - args_dict = dict(cs_args) # type: ignore - - if len(cs_args) != len(args_dict): - raise ValueError("Connection string is either blank or malformed.") - return args_dict diff --git a/sdk/core/azure-core/azure/core/utils/__init__.py b/sdk/core/azure-core/azure/core/utils/__init__.py new file mode 100644 index 000000000000..480ea9c41889 --- /dev/null +++ b/sdk/core/azure-core/azure/core/utils/__init__.py @@ -0,0 +1,31 @@ +# -------------------------------------------------------------------------- +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the ""Software""), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# +# -------------------------------------------------------------------------- +from ._connection_string_parser import ( + parse_connection_string, + ConnectionStringProperties, +) + +__all__ = ["parse_connection_string", "ConnectionStringProperties"] diff --git a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py new file mode 100644 index 000000000000..54fcdc956723 --- /dev/null +++ b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py @@ -0,0 +1,153 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from typing import Dict + +CS_DELIMITER = ";" +CS_VAL_SEPARATOR = "=" + + +class DictMixin(object): + def __setitem__(self, key, item): + # type: (Any, Any) -> None + self.__dict__[key] = item + + def __getitem__(self, key): + # type: (Any) -> Any + return self.__dict__[key] + + def __contains__(self, key): + return key in self.__dict__ + + def __repr__(self): + # type: () -> str + return str(self) + + def __len__(self): + # type: () -> int + return len(self.keys()) + + def __delitem__(self, key): + # type: (Any) -> None + self.__dict__[key] = None + + def __eq__(self, other): + # type: (Any) -> bool + """Compare objects by comparing all attributes.""" + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other): + # type: (Any) -> bool + """Compare objects by comparing all attributes.""" + return not self.__eq__(other) + + def __str__(self): + # type: () -> str + return str({k: v for k, v in self.__dict__.items() if not k.startswith("_")}) + + def has_key(self, k): + # type: (Any) -> bool + return k in self.__dict__ + + def update(self, *args, **kwargs): + # type: (Any, Any) -> None + return self.__dict__.update(*args, **kwargs) + + def keys(self): + # type: () -> list + return [k for k in self.__dict__ if not k.startswith("_")] + + def values(self): + # type: () -> list + return [v for k, v in self.__dict__.items() if not k.startswith("_")] + + def items(self): + # type: () -> list + return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")] + + def get(self, key, default=None): + # type: (Any, Optional[Any]) -> Any + if key in self.__dict__: + return self.__dict__[key] + return default + + +class ConnectionStringProperties(DictMixin): + """ + Properties of a connection string. + """ + + def __init__(self, properties_dict, is_case_sensitive=True): + self.__dict__.update(properties_dict) + self._case_sensitive = is_case_sensitive + self._case_insensitive_props = ( + dict( + (k.lower(), v) + for k, v in self.__dict__.items() + if not k.startswith("_") + ) + if not self._case_sensitive + else None + ) + + def __getitem__(self, key): + # type: (str) -> str + return ( + self.__dict__[key] + if self._case_sensitive + else self._case_insensitive_props[key.lower()] + ) + + def __contains__(self, key): + return ( + key in self.__dict__ + if self._case_sensitive + else key.lower() in self._case_insensitive_props + ) + + def __getattribute__(self, key): + if ( + not key.startswith("_") # avoid recursion error + and not self._case_sensitive # case insensitive + and key.lower() + in self._case_insensitive_props # check case insensitive key exists + ): + return self._case_insensitive_props[key.lower()] + return super(ConnectionStringProperties, self).__getattribute__(key) + + @property + def case_sensitive(self): + return self._case_sensitive + + +def parse_connection_string(conn_str, case_sensitive=True): + # type: (str) -> ConnectionStringProperties + """Parses the connection string into a ConnectionStringProperties object containing its + component parts. Checks that each key in the connection string has a provided value. + :param conn_str: String with connection details provided by Azure services. + :type conn_str: str + :param case_sensitive: Indicates whether returned ConnectionStringProperties + object should retrieve keys with case sensitive checking. + :type case_sensitive: bool + :rtype: ~azure.core.utils.ConnectionStringProperties + :raises: + ValueError: if each key in conn_str does not have a corresponding value and + for other bad formatting of connection strings - including duplicate + args, bad syntax, etc. + """ + + cs_args = [ + s.split("=", 1) for s in conn_str.strip().rstrip(";").split(CS_DELIMITER) + ] + if any(len(tup) != 2 for tup in cs_args): + raise ValueError("Connection string is either blank or malformed.") + args_dict = dict(cs_args) # type: ignore + + if len(cs_args) != len(args_dict): + raise ValueError("Connection string is either blank or malformed.") + + return ConnectionStringProperties(args_dict, case_sensitive) From e732c73eb8638a42931dd060f64cd1d48fea34d6 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Tue, 23 Mar 2021 13:53:21 -0400 Subject: [PATCH 4/9] support case insensitive keys + tests --- sdk/core/azure-core/CHANGELOG.md | 2 +- .../azure-core/azure/core/utils/__init__.py | 5 +- .../core/utils/_connection_string_parser.py | 156 +++--------------- .../tests/test_connection_string_parsing.py | 77 +++++++++ 4 files changed, 104 insertions(+), 136 deletions(-) create mode 100644 sdk/core/azure-core/tests/test_connection_string_parsing.py diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 0b009be9574b..d6778dd307bf 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- Added `parse_connection_string_to_dict` function for generic parsing and validation of connection strings across SDKs. +- Added `azure.core.utils.parse_connection_string` function to parse connection strings across SDKs, with common validation and support for case insensitive keys. ## 1.11.0 (2021-02-08) diff --git a/sdk/core/azure-core/azure/core/utils/__init__.py b/sdk/core/azure-core/azure/core/utils/__init__.py index 480ea9c41889..b9832652cd6a 100644 --- a/sdk/core/azure-core/azure/core/utils/__init__.py +++ b/sdk/core/azure-core/azure/core/utils/__init__.py @@ -24,8 +24,7 @@ # # -------------------------------------------------------------------------- from ._connection_string_parser import ( - parse_connection_string, - ConnectionStringProperties, + parse_connection_string ) -__all__ = ["parse_connection_string", "ConnectionStringProperties"] +__all__ = ["parse_connection_string"] diff --git a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py index 54fcdc956723..dc972e047745 100644 --- a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py +++ b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py @@ -3,151 +3,43 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from typing import Dict +from typing import Mapping, TYPE_CHECKING +from .._utils import _case_insensitive_dict -CS_DELIMITER = ";" -CS_VAL_SEPARATOR = "=" - -class DictMixin(object): - def __setitem__(self, key, item): - # type: (Any, Any) -> None - self.__dict__[key] = item - - def __getitem__(self, key): - # type: (Any) -> Any - return self.__dict__[key] - - def __contains__(self, key): - return key in self.__dict__ - - def __repr__(self): - # type: () -> str - return str(self) - - def __len__(self): - # type: () -> int - return len(self.keys()) - - def __delitem__(self, key): - # type: (Any) -> None - self.__dict__[key] = None - - def __eq__(self, other): - # type: (Any) -> bool - """Compare objects by comparing all attributes.""" - if isinstance(other, self.__class__): - return self.__dict__ == other.__dict__ - return False - - def __ne__(self, other): - # type: (Any) -> bool - """Compare objects by comparing all attributes.""" - return not self.__eq__(other) - - def __str__(self): - # type: () -> str - return str({k: v for k, v in self.__dict__.items() if not k.startswith("_")}) - - def has_key(self, k): - # type: (Any) -> bool - return k in self.__dict__ - - def update(self, *args, **kwargs): - # type: (Any, Any) -> None - return self.__dict__.update(*args, **kwargs) - - def keys(self): - # type: () -> list - return [k for k in self.__dict__ if not k.startswith("_")] - - def values(self): - # type: () -> list - return [v for k, v in self.__dict__.items() if not k.startswith("_")] - - def items(self): - # type: () -> list - return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")] - - def get(self, key, default=None): - # type: (Any, Optional[Any]) -> Any - if key in self.__dict__: - return self.__dict__[key] - return default - - -class ConnectionStringProperties(DictMixin): - """ - Properties of a connection string. - """ - - def __init__(self, properties_dict, is_case_sensitive=True): - self.__dict__.update(properties_dict) - self._case_sensitive = is_case_sensitive - self._case_insensitive_props = ( - dict( - (k.lower(), v) - for k, v in self.__dict__.items() - if not k.startswith("_") - ) - if not self._case_sensitive - else None - ) - - def __getitem__(self, key): - # type: (str) -> str - return ( - self.__dict__[key] - if self._case_sensitive - else self._case_insensitive_props[key.lower()] - ) - - def __contains__(self, key): - return ( - key in self.__dict__ - if self._case_sensitive - else key.lower() in self._case_insensitive_props - ) - - def __getattribute__(self, key): - if ( - not key.startswith("_") # avoid recursion error - and not self._case_sensitive # case insensitive - and key.lower() - in self._case_insensitive_props # check case insensitive key exists - ): - return self._case_insensitive_props[key.lower()] - return super(ConnectionStringProperties, self).__getattribute__(key) - - @property - def case_sensitive(self): - return self._case_sensitive - - -def parse_connection_string(conn_str, case_sensitive=True): - # type: (str) -> ConnectionStringProperties - """Parses the connection string into a ConnectionStringProperties object containing its - component parts. Checks that each key in the connection string has a provided value. +def parse_connection_string(conn_str, case_sensitive_keys=False): + # type: (str, bool) -> Mapping[str, str] + """Parses the connection string into its component parts. Checks that each + key in the connection string has a provided value. :param conn_str: String with connection details provided by Azure services. :type conn_str: str - :param case_sensitive: Indicates whether returned ConnectionStringProperties - object should retrieve keys with case sensitive checking. - :type case_sensitive: bool - :rtype: ~azure.core.utils.ConnectionStringProperties + :param case_sensitive_keys: Indicates whether returned object should retrieve + keys with case sensitive checking. Default is False. + :type case_sensitive_keys: bool + :rtype: Mapping :raises: ValueError: if each key in conn_str does not have a corresponding value and for other bad formatting of connection strings - including duplicate args, bad syntax, etc. """ - cs_args = [ - s.split("=", 1) for s in conn_str.strip().rstrip(";").split(CS_DELIMITER) - ] - if any(len(tup) != 2 for tup in cs_args): + cs_args = [s.split("=", 1) for s in conn_str.strip().rstrip(";").split(";")] + if any(len(tup) != 2 or not all(tup) for tup in cs_args): raise ValueError("Connection string is either blank or malformed.") args_dict = dict(cs_args) # type: ignore if len(cs_args) != len(args_dict): raise ValueError("Connection string is either blank or malformed.") - return ConnectionStringProperties(args_dict, case_sensitive) + if not case_sensitive_keys: + # if duplicate case insensitive keys are passed in, raise error + duplicate_keys = set() + for key in args_dict.keys(): + if key.lower() in duplicate_keys: + raise ValueError( + "Duplicate key in connection string: {}".format(key.lower()) + ) + duplicate_keys.add(key.lower()) + args_dict = _case_insensitive_dict(**args_dict) + + return args_dict diff --git a/sdk/core/azure-core/tests/test_connection_string_parsing.py b/sdk/core/azure-core/tests/test_connection_string_parsing.py new file mode 100644 index 000000000000..50e81d8d3dfd --- /dev/null +++ b/sdk/core/azure-core/tests/test_connection_string_parsing.py @@ -0,0 +1,77 @@ +import os +import pytest +from azure.core.utils import parse_connection_string + +from devtools_testutils import AzureMgmtTestCase + +class CoreConnectionStringParserTests(AzureMgmtTestCase): + + def test_parsing_with_case_sensitive_keys_for_sensitive_conn_str(self, **kwargs): + conn_str = 'Endpoint=XXXXENDPOINTXXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, True) + assert parse_result["Endpoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["SharedAccessKeyName"] == 'XXXXPOLICYXXXX' + assert parse_result["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + with pytest.raises(KeyError): + parse_result["endPoint"] + with pytest.raises(KeyError): + parse_result["sharedAccESSkEynAME"] + with pytest.raises(KeyError): + parse_result["sharedaccesskey"] + + def test_parsing_with_case_insensitive_keys_for_sensitive_conn_str(self, **kwargs): + conn_str = 'Endpoint=XXXXENDPOINTXXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + assert parse_result["Endpoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["endPoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["SharedAccessKeyName"] == 'XXXXPOLICYXXXX' + assert parse_result["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' + assert parse_result["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result["sharedaccesskey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + + def test_parsing_with_case_sensitive_keys_for_insensitive_conn_str(self, **kwargs): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, True) + assert parse_result["enDpoiNT"] == 'XXXXENDPOINTXXXX' + assert parse_result["sharedaccesskeyname"] == 'XXXXPOLICYXXXX' + assert parse_result["SHAREDACCESSKEY"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + with pytest.raises(KeyError): + parse_result["Endpoint"] + with pytest.raises(KeyError): + parse_result["SharedAccessKeyName"] + with pytest.raises(KeyError): + parse_result["SharedAccessKey"] + + def test_parsing_with_case_insensitive_keys_for_insensitive_conn_str(self, **kwargs): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + assert parse_result["Endpoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["endPoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["SharedAccessKeyName"] == 'XXXXPOLICYXXXX' + assert parse_result["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' + assert parse_result["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result["sharedaccesskey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + + def test_error_with_duplicate_case_sensitive_keys_for_sensitive_conn_str(self, **kwargs): + conn_str = 'Endpoint=XXXXENDPOINTXXXX;Endpoint=XXXXENDPOINT2XXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + with pytest.raises(ValueError) as e: + parse_result = parse_connection_string(conn_str, True) + assert str(e.value) == "Connection string is either blank or malformed." + + def test_success_with_duplicate_case_sensitive_keys_for_sensitive_conn_str(self, **kwargs): + conn_str = 'enDpoInt=XXXXENDPOINTXXXX;Endpoint=XXXXENDPOINT2XXXX;' + parse_result = parse_connection_string(conn_str, True) + assert parse_result["enDpoInt"] == 'XXXXENDPOINTXXXX' + assert parse_result["Endpoint"] == 'XXXXENDPOINT2XXXX' + + def test_error_with_duplicate_case_insensitive_keys_for_insensitive_conn_str(self, **kwargs): + conn_str = 'endPoinT=XXXXENDPOINTXXXX;eNdpOint=XXXXENDPOINT2XXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + with pytest.raises(ValueError) as e: + parse_result = parse_connection_string(conn_str, False) + assert str(e.value) == "Duplicate key in connection string: endpoint" + + def test_error_with_malformed_conn_str(self): + for conn_str in ["", "foobar", "foo;bar;baz", ";", "foo=;bar=;", "=", "=;=="]: + with pytest.raises(ValueError) as e: + parse_result = parse_connection_string(conn_str) + self.assertEqual(str(e.value), "Connection string is either blank or malformed.") From 0ff42c379acd73c7600a2c1e695f5138edf721d0 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Wed, 24 Mar 2021 17:31:53 -0400 Subject: [PATCH 5/9] update version + add core tests --- sdk/core/azure-core/CHANGELOG.md | 2 +- sdk/core/azure-core/azure/core/_version.py | 2 +- .../tests/test_connection_string_parsing.py | 104 ++++++++++++++++++ 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index d6778dd307bf..04e45490096b 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 1.11.1 (Unreleased) +## 1.13.0 (Unreleased) ### Features diff --git a/sdk/core/azure-core/azure/core/_version.py b/sdk/core/azure-core/azure/core/_version.py index 14d127f747d9..7a5c38e4c326 100644 --- a/sdk/core/azure-core/azure/core/_version.py +++ b/sdk/core/azure-core/azure/core/_version.py @@ -9,4 +9,4 @@ # regenerated. # -------------------------------------------------------------------------- -VERSION = "1.11.1" +VERSION = "1.13.0" diff --git a/sdk/core/azure-core/tests/test_connection_string_parsing.py b/sdk/core/azure-core/tests/test_connection_string_parsing.py index 50e81d8d3dfd..74a7656c0fa3 100644 --- a/sdk/core/azure-core/tests/test_connection_string_parsing.py +++ b/sdk/core/azure-core/tests/test_connection_string_parsing.py @@ -75,3 +75,107 @@ def test_error_with_malformed_conn_str(self): with pytest.raises(ValueError) as e: parse_result = parse_connection_string(conn_str) self.assertEqual(str(e.value), "Connection string is either blank or malformed.") + + def test_case_insensitive_clear_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + parse_result.clear() + assert len(parse_result) == 0 + + def test_case_insensitive_copy_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + copied = parse_result.copy() + assert copied == parse_result + + def test_case_insensitive_get_method(self): + conn_str = 'Endpoint=XXXXENDPOINTXXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + assert parse_result.get("Endpoint") == 'XXXXENDPOINTXXXX' + assert parse_result.get("endPoint") == 'XXXXENDPOINTXXXX' + assert parse_result.get("SharedAccessKeyName") == 'XXXXPOLICYXXXX' + assert parse_result.get("sharedAccESSkEynAME") == 'XXXXPOLICYXXXX' + assert parse_result.get("SharedAccessKey") == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result.get("sharedaccesskey") == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result.get("accesskey") is None + assert parse_result.get("accesskey", "XXothertestkeyXX=") == "XXothertestkeyXX=" + + def test_case_insensitive_keys_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + keys = parse_result.keys() + assert len(keys) == 3 + assert "enDpoiNT" in keys + assert "endpoint" in keys + + def test_case_insensitive_pop_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + endpoint = parse_result.pop("endpoint") + sharedaccesskey = parse_result.pop("shAredAccESSkey") + assert len(parse_result) == 1 + assert endpoint == "XXXXENDPOINTXXXX" + assert sharedaccesskey == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" + + def test_case_insensitive_update_with_insensitive_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + conn_str2 = 'hostName=XXXXENDPOINTXXXX;ACCessKEy=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=;' + parse_result_insensitive = parse_connection_string(conn_str, False) + parse_result_insensitive2 = parse_connection_string(conn_str2, False) + + parse_result_insensitive.update(parse_result_insensitive2) + assert len(parse_result_insensitive) == 5 + assert parse_result_insensitive["hostname"] == "XXXXENDPOINTXXXX" + assert parse_result_insensitive["ACCESSkey"] == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" + + # check that update replace duplicate case insensitive keys + conn_str_duplicate_key = "endpoint=XXXXENDPOINT2XXXX;ACCessKEy=TestKey" + parse_result_insensitive_dupe = parse_connection_string(conn_str_duplicate_key, False) + parse_result_insensitive.update(parse_result_insensitive_dupe) + assert parse_result_insensitive_dupe["endPoint"] == "XXXXENDPOINT2XXXX" + assert parse_result_insensitive_dupe["ACCESSKEY"] == "TestKey" + assert len(parse_result_insensitive) == 5 + + def test_case_insensitive_update_with_sensitive_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + conn_str2 = 'hostName=XXXXENDPOINTXXXX;ACCessKEy=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=;' + parse_result_insensitive = parse_connection_string(conn_str, False) + parse_result_sensitive = parse_connection_string(conn_str2, True) + + parse_result_insensitive.update(parse_result_sensitive) + assert len(parse_result_insensitive) == 5 + assert parse_result_insensitive["hostname"] == "XXXXENDPOINTXXXX" + assert parse_result_insensitive["ACCESSkey"] == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" + + # check that update with duplicate case sensitive keys makes duplicate keys case sensitive + conn_str_duplicate_key = "endpoint=XXXXENDPOINT2XXXX;ACCessKEy=TestKey" + parse_result_sensitive_dupe = parse_connection_string(conn_str_duplicate_key, True) + parse_result_insensitive.update(parse_result_sensitive_dupe) + with pytest.raises(KeyError): + parse_result_sensitive_dupe["endPoint"] + with pytest.raises(KeyError): + parse_result_sensitive_dupe["ACCESSKEY"] + + assert parse_result_insensitive["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' + assert parse_result_insensitive["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result_sensitive_dupe["endpoint"] == "XXXXENDPOINT2XXXX" + assert parse_result_sensitive_dupe["ACCessKEy"] == "TestKey" + assert len(parse_result_insensitive) == 5 + + def test_case_sensitive_update_with_insensitive_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + conn_str2 = 'hostName=XXXXENDPOINTXXXX;ACCessKEy=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=;' + parse_result_insensitive = parse_connection_string(conn_str, False) + parse_result_sensitive = parse_connection_string(conn_str2, True) + + parse_result_sensitive.update(parse_result_insensitive) + assert len(parse_result_sensitive) == 5 + assert parse_result_sensitive["hostName"] == "XXXXENDPOINTXXXX" + with pytest.raises(KeyError): + parse_result_sensitive["hostname"] + + def test_case_insensitive_values_method(self): + conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + parse_result = parse_connection_string(conn_str, False) + values = parse_result.values() + assert len(values) == 3 \ No newline at end of file From 0ba13a49e9adeb5047ad919b5ba960c45451c038 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Thu, 25 Mar 2021 11:47:57 -0400 Subject: [PATCH 6/9] lint + fix test --- .../azure-core/azure/core/utils/_connection_string_parser.py | 2 +- sdk/core/azure-core/tests/test_connection_string_parsing.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py index dc972e047745..91af38dfbd4d 100644 --- a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py +++ b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -from typing import Mapping, TYPE_CHECKING +from typing import Mapping from .._utils import _case_insensitive_dict diff --git a/sdk/core/azure-core/tests/test_connection_string_parsing.py b/sdk/core/azure-core/tests/test_connection_string_parsing.py index 74a7656c0fa3..ed87067a4dc4 100644 --- a/sdk/core/azure-core/tests/test_connection_string_parsing.py +++ b/sdk/core/azure-core/tests/test_connection_string_parsing.py @@ -1,4 +1,4 @@ -import os +import sys import pytest from azure.core.utils import parse_connection_string @@ -106,7 +106,8 @@ def test_case_insensitive_keys_method(self): keys = parse_result.keys() assert len(keys) == 3 assert "enDpoiNT" in keys - assert "endpoint" in keys + if sys.version_info >= (3, 5): + assert "endpoint" in keys def test_case_insensitive_pop_method(self): conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' From 9afed22bd2ee3d2ca16d2153ebe0e20cbe33c951 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Mon, 29 Mar 2021 16:32:10 -0400 Subject: [PATCH 7/9] fix changelog --- sdk/core/azure-core/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 37caef60f941..dbeecdcc81f9 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -24,7 +24,6 @@ This version will be the last version to officially support Python 3.5, future v ### Bug Fixes - Disable retry in stream downloading. (thanks to @jochen-ott-by @hoffmann for the contribution) #16723 ->>>>>>> master ## 1.11.0 (2021-02-08) From 97da99d319d0d8ee48b677013668645ed8e59898 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Tue, 30 Mar 2021 13:06:43 -0400 Subject: [PATCH 8/9] remove requests dependency --- .../azure-core/azure/core/utils/__init__.py | 6 ++ .../core/utils/_connection_string_parser.py | 20 +++--- .../tests/test_connection_string_parsing.py | 71 +++---------------- 3 files changed, 27 insertions(+), 70 deletions(-) diff --git a/sdk/core/azure-core/azure/core/utils/__init__.py b/sdk/core/azure-core/azure/core/utils/__init__.py index b9832652cd6a..13d98f4b9b68 100644 --- a/sdk/core/azure-core/azure/core/utils/__init__.py +++ b/sdk/core/azure-core/azure/core/utils/__init__.py @@ -23,6 +23,12 @@ # IN THE SOFTWARE. # # -------------------------------------------------------------------------- +""" + +This `utils` module provides functionality that is intended to be used by developers +building on top of `azure-core`. + +""" from ._connection_string_parser import ( parse_connection_string ) diff --git a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py index 91af38dfbd4d..7f8c13d96472 100644 --- a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py +++ b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- from typing import Mapping -from .._utils import _case_insensitive_dict def parse_connection_string(conn_str, case_sensitive_keys=False): # type: (str, bool) -> Mapping[str, str] - """Parses the connection string into its component parts. Checks that each - key in the connection string has a provided value. + """Parses the connection string into a dict of its component parts, with the option of preserving case + of keys, and validates that each key in the connection string has a provided value. If case of keys + is not preserved (ie. `case_sensitive_keys=False`), then a dict with LOWERCASE KEYS will be returned. :param conn_str: String with connection details provided by Azure services. :type conn_str: str :param case_sensitive_keys: Indicates whether returned object should retrieve - keys with case sensitive checking. Default is False. + keys with case sensitive checking. True means that case of keys will be preserved. + Default is False. :type case_sensitive_keys: bool :rtype: Mapping :raises: @@ -33,13 +34,14 @@ def parse_connection_string(conn_str, case_sensitive_keys=False): if not case_sensitive_keys: # if duplicate case insensitive keys are passed in, raise error - duplicate_keys = set() + new_args_dict = {} for key in args_dict.keys(): - if key.lower() in duplicate_keys: + new_key = key.lower() + if new_key in new_args_dict: raise ValueError( - "Duplicate key in connection string: {}".format(key.lower()) + "Duplicate key in connection string: {}".format(new_key) ) - duplicate_keys.add(key.lower()) - args_dict = _case_insensitive_dict(**args_dict) + new_args_dict[new_key] = args_dict[key] + return new_args_dict return args_dict diff --git a/sdk/core/azure-core/tests/test_connection_string_parsing.py b/sdk/core/azure-core/tests/test_connection_string_parsing.py index ed87067a4dc4..1decf6ce45a5 100644 --- a/sdk/core/azure-core/tests/test_connection_string_parsing.py +++ b/sdk/core/azure-core/tests/test_connection_string_parsing.py @@ -22,34 +22,15 @@ def test_parsing_with_case_sensitive_keys_for_sensitive_conn_str(self, **kwargs) def test_parsing_with_case_insensitive_keys_for_sensitive_conn_str(self, **kwargs): conn_str = 'Endpoint=XXXXENDPOINTXXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' parse_result = parse_connection_string(conn_str, False) - assert parse_result["Endpoint"] == 'XXXXENDPOINTXXXX' - assert parse_result["endPoint"] == 'XXXXENDPOINTXXXX' - assert parse_result["SharedAccessKeyName"] == 'XXXXPOLICYXXXX' - assert parse_result["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' - assert parse_result["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - assert parse_result["sharedaccesskey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - - def test_parsing_with_case_sensitive_keys_for_insensitive_conn_str(self, **kwargs): - conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - parse_result = parse_connection_string(conn_str, True) - assert parse_result["enDpoiNT"] == 'XXXXENDPOINTXXXX' + assert parse_result["endpoint"] == 'XXXXENDPOINTXXXX' assert parse_result["sharedaccesskeyname"] == 'XXXXPOLICYXXXX' - assert parse_result["SHAREDACCESSKEY"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - with pytest.raises(KeyError): - parse_result["Endpoint"] - with pytest.raises(KeyError): - parse_result["SharedAccessKeyName"] - with pytest.raises(KeyError): - parse_result["SharedAccessKey"] + assert parse_result["sharedaccesskey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' def test_parsing_with_case_insensitive_keys_for_insensitive_conn_str(self, **kwargs): conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' parse_result = parse_connection_string(conn_str, False) - assert parse_result["Endpoint"] == 'XXXXENDPOINTXXXX' - assert parse_result["endPoint"] == 'XXXXENDPOINTXXXX' - assert parse_result["SharedAccessKeyName"] == 'XXXXPOLICYXXXX' - assert parse_result["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' - assert parse_result["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result["endpoint"] == 'XXXXENDPOINTXXXX' + assert parse_result["sharedaccesskeyname"] == 'XXXXPOLICYXXXX' assert parse_result["sharedaccesskey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' def test_error_with_duplicate_case_sensitive_keys_for_sensitive_conn_str(self, **kwargs): @@ -91,11 +72,7 @@ def test_case_insensitive_copy_method(self): def test_case_insensitive_get_method(self): conn_str = 'Endpoint=XXXXENDPOINTXXXX;SharedAccessKeyName=XXXXPOLICYXXXX;SharedAccessKey=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' parse_result = parse_connection_string(conn_str, False) - assert parse_result.get("Endpoint") == 'XXXXENDPOINTXXXX' - assert parse_result.get("endPoint") == 'XXXXENDPOINTXXXX' - assert parse_result.get("SharedAccessKeyName") == 'XXXXPOLICYXXXX' - assert parse_result.get("sharedAccESSkEynAME") == 'XXXXPOLICYXXXX' - assert parse_result.get("SharedAccessKey") == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' + assert parse_result.get("sharedaccesskeyname") == 'XXXXPOLICYXXXX' assert parse_result.get("sharedaccesskey") == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' assert parse_result.get("accesskey") is None assert parse_result.get("accesskey", "XXothertestkeyXX=") == "XXothertestkeyXX=" @@ -105,15 +82,13 @@ def test_case_insensitive_keys_method(self): parse_result = parse_connection_string(conn_str, False) keys = parse_result.keys() assert len(keys) == 3 - assert "enDpoiNT" in keys - if sys.version_info >= (3, 5): - assert "endpoint" in keys + assert "endpoint" in keys def test_case_insensitive_pop_method(self): conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' parse_result = parse_connection_string(conn_str, False) endpoint = parse_result.pop("endpoint") - sharedaccesskey = parse_result.pop("shAredAccESSkey") + sharedaccesskey = parse_result.pop("sharedaccesskey") assert len(parse_result) == 1 assert endpoint == "XXXXENDPOINTXXXX" assert sharedaccesskey == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" @@ -127,40 +102,14 @@ def test_case_insensitive_update_with_insensitive_method(self): parse_result_insensitive.update(parse_result_insensitive2) assert len(parse_result_insensitive) == 5 assert parse_result_insensitive["hostname"] == "XXXXENDPOINTXXXX" - assert parse_result_insensitive["ACCESSkey"] == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" + assert parse_result_insensitive["accesskey"] == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" # check that update replace duplicate case insensitive keys conn_str_duplicate_key = "endpoint=XXXXENDPOINT2XXXX;ACCessKEy=TestKey" parse_result_insensitive_dupe = parse_connection_string(conn_str_duplicate_key, False) parse_result_insensitive.update(parse_result_insensitive_dupe) - assert parse_result_insensitive_dupe["endPoint"] == "XXXXENDPOINT2XXXX" - assert parse_result_insensitive_dupe["ACCESSKEY"] == "TestKey" - assert len(parse_result_insensitive) == 5 - - def test_case_insensitive_update_with_sensitive_method(self): - conn_str = 'enDpoiNT=XXXXENDPOINTXXXX;sharedaccesskeyname=XXXXPOLICYXXXX;SHAREDACCESSKEY=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - conn_str2 = 'hostName=XXXXENDPOINTXXXX;ACCessKEy=THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=;' - parse_result_insensitive = parse_connection_string(conn_str, False) - parse_result_sensitive = parse_connection_string(conn_str2, True) - - parse_result_insensitive.update(parse_result_sensitive) - assert len(parse_result_insensitive) == 5 - assert parse_result_insensitive["hostname"] == "XXXXENDPOINTXXXX" - assert parse_result_insensitive["ACCESSkey"] == "THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=" - - # check that update with duplicate case sensitive keys makes duplicate keys case sensitive - conn_str_duplicate_key = "endpoint=XXXXENDPOINT2XXXX;ACCessKEy=TestKey" - parse_result_sensitive_dupe = parse_connection_string(conn_str_duplicate_key, True) - parse_result_insensitive.update(parse_result_sensitive_dupe) - with pytest.raises(KeyError): - parse_result_sensitive_dupe["endPoint"] - with pytest.raises(KeyError): - parse_result_sensitive_dupe["ACCESSKEY"] - - assert parse_result_insensitive["sharedAccESSkEynAME"] == 'XXXXPOLICYXXXX' - assert parse_result_insensitive["SharedAccessKey"] == 'THISISATESTKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXX=' - assert parse_result_sensitive_dupe["endpoint"] == "XXXXENDPOINT2XXXX" - assert parse_result_sensitive_dupe["ACCessKEy"] == "TestKey" + assert parse_result_insensitive_dupe["endpoint"] == "XXXXENDPOINT2XXXX" + assert parse_result_insensitive_dupe["accesskey"] == "TestKey" assert len(parse_result_insensitive) == 5 def test_case_sensitive_update_with_insensitive_method(self): From b3c4208937fb3be383fee9c898aaa49028be3a51 Mon Sep 17 00:00:00 2001 From: Swathi Pillalamarri Date: Tue, 30 Mar 2021 20:34:34 -0400 Subject: [PATCH 9/9] updated param description --- .../azure/core/utils/_connection_string_parser.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py index 7f8c13d96472..a074df43b586 100644 --- a/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py +++ b/sdk/core/azure-core/azure/core/utils/_connection_string_parser.py @@ -1,3 +1,4 @@ +# coding=utf-8 # ------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for @@ -11,12 +12,10 @@ def parse_connection_string(conn_str, case_sensitive_keys=False): """Parses the connection string into a dict of its component parts, with the option of preserving case of keys, and validates that each key in the connection string has a provided value. If case of keys is not preserved (ie. `case_sensitive_keys=False`), then a dict with LOWERCASE KEYS will be returned. - :param conn_str: String with connection details provided by Azure services. - :type conn_str: str - :param case_sensitive_keys: Indicates whether returned object should retrieve - keys with case sensitive checking. True means that case of keys will be preserved. - Default is False. - :type case_sensitive_keys: bool + + :param str conn_str: String with connection details provided by Azure services. + :param bool case_sensitive_keys: Indicates whether the casing of the keys will be preserved. When `False`(the + default), all keys will be lower-cased. If set to `True`, the original casing of the keys will be preserved. :rtype: Mapping :raises: ValueError: if each key in conn_str does not have a corresponding value and