diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 441fc3877c1b..4ef32399e2d5 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Added `CaseInsensitiveEnumMeta` class for case-insensitive enums. #16316 - Add `raise_for_status` method onto `HttpResponse`. Calling `response.raise_for_status()` on a response with an error code will raise an `HttpResponseError`. Calling it on a good response will do nothing #16399 diff --git a/sdk/core/azure-core/README.md b/sdk/core/azure-core/README.md index 5d506d660ea5..2e5391ae84ea 100644 --- a/sdk/core/azure-core/README.md +++ b/sdk/core/azure-core/README.md @@ -148,6 +148,20 @@ class MatchConditions(Enum): IfMissing = 5 ``` +#### CaseInsensitiveEnumMeta + +A metaclass to support case-insensitive enums. +```python +from enum import Enum +from six import with_metaclass + +from azure.core import CaseInsensitiveEnumMeta + +class MyCustomEnum(with_metaclass(CaseInsensitiveEnumMeta, str, Enum)): + FOO = 'foo' + BAR = 'bar' +``` + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have diff --git a/sdk/core/azure-core/azure/core/__init__.py b/sdk/core/azure-core/azure/core/__init__.py index 40ed75d540a6..ddd1d8da4b69 100644 --- a/sdk/core/azure-core/azure/core/__init__.py +++ b/sdk/core/azure-core/azure/core/__init__.py @@ -29,11 +29,13 @@ from ._pipeline_client import PipelineClient from ._match_conditions import MatchConditions +from ._enum_meta import CaseInsensitiveEnumMeta __all__ = [ "PipelineClient", - "MatchConditions" + "MatchConditions", + "CaseInsensitiveEnumMeta" ] try: diff --git a/sdk/core/azure-core/azure/core/_enum_meta.py b/sdk/core/azure-core/azure/core/_enum_meta.py new file mode 100644 index 000000000000..8c225f88cc74 --- /dev/null +++ b/sdk/core/azure-core/azure/core/_enum_meta.py @@ -0,0 +1,61 @@ +# -------------------------------------------------------------------------- +# +# 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 enum import EnumMeta + + +class CaseInsensitiveEnumMeta(EnumMeta): + """Enum metaclass to allow for interoperability with case-insensitive strings. + + Consuming this metaclass in an SDK should be done in the following manner: + + .. code-block:: python + + from enum import Enum + from six import with_metaclass + from azure.core import CaseInsensitiveEnumMeta + + class MyCustomEnum(with_metaclass(CaseInsensitiveEnumMeta, str, Enum)): + FOO = 'foo' + BAR = 'bar' + + """ + + def __getitem__(cls, name): + # disabling pylint bc of pylint bug https://github.com/PyCQA/astroid/issues/713 + return super(CaseInsensitiveEnumMeta, cls).__getitem__(name.upper()) # pylint: disable=no-value-for-parameter + + def __getattr__(cls, name): + """Return the enum member matching `name` + We use __getattr__ instead of descriptors or inserting into the enum + class' __dict__ in order to support `name` and `value` being both + properties for enum members (which live in the class' __dict__) and + enum members themselves. + """ + try: + return cls._member_map_[name.upper()] + except KeyError: + raise AttributeError(name) diff --git a/sdk/core/azure-core/tests/test_enums.py b/sdk/core/azure-core/tests/test_enums.py new file mode 100644 index 000000000000..d164568ecdce --- /dev/null +++ b/sdk/core/azure-core/tests/test_enums.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------- +# +# 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 enum import Enum +from six import with_metaclass + +from azure.core import CaseInsensitiveEnumMeta + +class MyCustomEnum(with_metaclass(CaseInsensitiveEnumMeta, str, Enum)): + FOO = 'foo' + BAR = 'bar' + + +def test_case_insensitive_enums(): + assert MyCustomEnum.foo.value == 'foo' + assert MyCustomEnum.FOO.value == 'foo' + assert MyCustomEnum('bar').value == 'bar' + assert 'bar' == MyCustomEnum.BAR + assert 'bar' == MyCustomEnum.bar + assert MyCustomEnum['foo'] == 'foo' + assert MyCustomEnum['FOO'] == 'foo' + assert isinstance(MyCustomEnum.BAR, str)