forked from Azure/azure-sdk-for-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add profile to SDK * Feedback from Derek * Doc update * Update Storage with new Profile * Update Compute with new Profile * Update Resource with new Profile * Improve ResourceGroupPreparer to provide the id * Migrate all Resources tests to new framework * Generic ChangeLog with Profiles * Add RequestUrlNormalizer to SDK processors
- Loading branch information
Showing
39 changed files
with
931 additions
and
611 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
#------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for | ||
# license information. | ||
#-------------------------------------------------------------------------- | ||
from enum import Enum | ||
|
||
class ProfileDefinition(object): | ||
"""Allow to define a custom Profile definition. | ||
Note:: | ||
The dict format taken as input is yet to be confirmed and should | ||
*not* be considered as stable in the current implementation. | ||
:param dict profile_dict: A profile dictionnary | ||
:param str label: A label for pretty printing | ||
""" | ||
def __init__(self, profile_dict, label=None): | ||
self._profile_dict = profile_dict | ||
self._label = label | ||
|
||
@property | ||
def label(self): | ||
"""The label associated to this profile definition. | ||
""" | ||
return self._label | ||
|
||
def __repr__(self): | ||
return self._label if self._label else self._profile_dict.__repr__() | ||
|
||
def get_profile_dict(self): | ||
"""Return the current profile dict. | ||
This is internal information, and content should not be considered stable. | ||
""" | ||
return self._profile_dict | ||
|
||
|
||
class DefaultProfile(object): | ||
"""Store a default profile. | ||
:var ProfileDefinition profile: The default profile as class attribute | ||
""" | ||
profile = None | ||
|
||
def use(self, profile): | ||
"""Define a new default profile.""" | ||
if not isinstance(profile, (KnownProfiles, ProfileDefinition)): | ||
raise ValueError("Can only set as default a ProfileDefinition or a KnownProfiles") | ||
type(self).profile = profile | ||
|
||
def definition(self): | ||
return type(self).profile | ||
|
||
class KnownProfiles(Enum): | ||
"""This defines known Azure Profiles. | ||
There is two meta-profiles: | ||
- latest : will always use latest available api-version on each package | ||
- default : mutable, will define profile automatically for all packages | ||
If you change default, this changes all created packages on the fly to | ||
this profile. This can be used to switch a complete set of API Version | ||
without re-creating all clients. | ||
""" | ||
|
||
# default - This is a meta-profile and point to another profile | ||
default = DefaultProfile() | ||
# latest - This is a meta-profile and does not contain definitions | ||
latest = ProfileDefinition(None, "latest") | ||
v2017_03_09_profile = ProfileDefinition( | ||
{ | ||
"azure.mgmt.compute.ComputeManagementClient": { | ||
None: "2016-03-30" | ||
}, | ||
"azure.mgmt.network.NetworkManagementClient": { | ||
None: "2015-06-15" | ||
}, | ||
"azure.mgmt.storage.StorageManagementClient": { | ||
None: "2016-01-01" | ||
}, | ||
"azure.mgmt.resource.policy.PolicyClient": { | ||
None: "2015-10-01-preview" | ||
}, | ||
"azure.mgmt.resource.locks.ManagementLockClient": { | ||
None: "2015-01-01" | ||
}, | ||
"azure.mgmt.resource.links.ManagementLinkClient": { | ||
None: "2016-09-01" | ||
}, | ||
"azure.mgmt.resource.resources.ResourceManagementClient": { | ||
None: "2016-02-01" | ||
}, | ||
"azure.mgmt.resource.subscriptions.SubscriptionClient": { | ||
None: "2016-06-01" | ||
} | ||
}, | ||
"2017-03-09-profile" | ||
) | ||
|
||
def __init__(self, profile_definition): | ||
self._profile_definition = profile_definition | ||
|
||
def use(self, profile): | ||
if self is not type(self).default: | ||
raise ValueError("use can only be used for `default` profile") | ||
self.value.use(profile) | ||
|
||
def definition(self): | ||
if self is not type(self).default: | ||
raise ValueError("use can only be used for `default` profile") | ||
return self.value.definition() | ||
|
||
@classmethod | ||
def from_name(cls, profile_name): | ||
if profile_name == "default": | ||
return cls.default | ||
for profile in cls: | ||
if isinstance(profile.value, ProfileDefinition) and profile.value.label == profile_name: | ||
return profile | ||
raise ValueError("No profile called {}".format(profile_name)) | ||
|
||
|
||
# Default profile is floating "latest" | ||
KnownProfiles.default.use(KnownProfiles.latest) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for | ||
# license information. | ||
#-------------------------------------------------------------------------- | ||
from . import KnownProfiles, ProfileDefinition | ||
|
||
class InvalidMultiApiClientError(Exception): | ||
"""If the mixin is not used with a compatible class. | ||
""" | ||
pass | ||
|
||
class MultiApiClientMixin(object): | ||
"""Mixin that contains multi-api version profile management. | ||
To use this mixin, a client must define two class attributes: | ||
- LATEST_PROFILE : a ProfileDefinition correspond to latest profile | ||
- _PROFILE_TAG : a tag that filter a full profile for this particular client | ||
This should not be used directly and will only provide private methods. | ||
""" | ||
|
||
def __init__(self, api_version=None, profile=KnownProfiles.default, **kwargs): | ||
|
||
try: | ||
type(self).LATEST_PROFILE | ||
except AttributeError: | ||
raise InvalidMultiApiClientError("To use this mixin, main client MUST define LATEST_PROFILE class attribute") | ||
|
||
try: | ||
type(self)._PROFILE_TAG | ||
except AttributeError: | ||
raise InvalidMultiApiClientError("To use this mixin, main client MUST define _PROFILE_TAG class attribute") | ||
|
||
if api_version and profile is not KnownProfiles.default: | ||
raise ValueError("Cannot use api-version and profile parameters at the same time") | ||
|
||
if api_version: | ||
self.profile = ProfileDefinition({ | ||
self._PROFILE_TAG: { | ||
None: api_version | ||
}}, | ||
self._PROFILE_TAG + " " + api_version | ||
) | ||
elif isinstance(profile, dict): | ||
self.profile = ProfileDefinition({ | ||
self._PROFILE_TAG: profile, | ||
}, | ||
self._PROFILE_TAG + " dict" | ||
) | ||
if api_version: | ||
self.profile._profile_dict[self._PROFILE_TAG][None] = api_version | ||
else: | ||
self.profile = profile | ||
|
||
def _get_api_version(self, operation_group_name): | ||
current_profile = self.profile | ||
if self.profile is KnownProfiles.default: | ||
current_profile = KnownProfiles.default.value.definition() | ||
|
||
if current_profile is KnownProfiles.latest: | ||
current_profile = self.LATEST_PROFILE | ||
elif isinstance(current_profile, KnownProfiles): | ||
current_profile = current_profile.value | ||
elif isinstance(current_profile, ProfileDefinition): | ||
pass # I expect that | ||
else: | ||
raise ValueError("Cannot determine a ProfileDefinition from {}".format(self.profile)) | ||
|
||
local_profile_dict = current_profile.get_profile_dict() | ||
if self._PROFILE_TAG not in local_profile_dict: | ||
raise ValueError("This profile doesn't define {}".format(self._PROFILE_TAG)) | ||
|
||
local_profile = local_profile_dict[self._PROFILE_TAG] | ||
if operation_group_name in local_profile: | ||
return local_profile[operation_group_name] | ||
try: | ||
return local_profile[None] | ||
except KeyError: | ||
raise ValueError("This profile definition does not contain a default API version") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
#------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for | ||
# license information. | ||
#-------------------------------------------------------------------------- | ||
from azure.profiles import ProfileDefinition, KnownProfiles | ||
from azure.profiles.multiapiclient import MultiApiClientMixin | ||
|
||
import pytest | ||
|
||
def test_profile_from_string(): | ||
profile_from_string = KnownProfiles.from_name("2017-03-09-profile") | ||
assert profile_from_string is KnownProfiles.v2017_03_09_profile | ||
|
||
with pytest.raises(ValueError): | ||
KnownProfiles.from_name("blablabla") | ||
|
||
def test_default_profile(): | ||
with pytest.raises(ValueError): | ||
KnownProfiles.default.use("This is not a profile") | ||
|
||
def test_multiapi_client(): | ||
|
||
class TestClient(MultiApiClientMixin): | ||
DEFAULT_API_VERSION = "2216-08-09" | ||
_PROFILE_TAG = "azure.mgmt.compute.ComputeManagementClient" | ||
LATEST_PROFILE = ProfileDefinition({ | ||
_PROFILE_TAG: { | ||
None: DEFAULT_API_VERSION | ||
}}, | ||
_PROFILE_TAG + " latest" | ||
) | ||
|
||
def __init__(self, api_version=None, profile=KnownProfiles.default): | ||
super(TestClient, self).__init__(api_version=api_version, profile=profile) | ||
|
||
def operations(self): | ||
return self._get_api_version("operations") | ||
|
||
# By default, use latest | ||
client = TestClient() | ||
assert client.operations() == TestClient.DEFAULT_API_VERSION | ||
|
||
# Dynamically change to a new profile | ||
KnownProfiles.default.use(KnownProfiles.v2017_03_09_profile) | ||
assert client.operations() == "2016-03-30" | ||
|
||
# I ask explicitly latest, where the default is not that | ||
client = TestClient(profile=KnownProfiles.latest) | ||
assert client.operations() == TestClient.DEFAULT_API_VERSION | ||
|
||
# Bring back default to latest for next tests | ||
KnownProfiles.default.use(KnownProfiles.latest) | ||
|
||
# I asked explicily a specific profile, must not be latest | ||
client = TestClient(profile=KnownProfiles.v2017_03_09_profile) | ||
assert client.operations() == "2016-03-30" | ||
|
||
# I refuse api_version and profile at the same time | ||
# https://github.com/Azure/azure-sdk-for-python/issues/1864 | ||
with pytest.raises(ValueError): | ||
TestClient(api_version="something", profile=KnownProfiles.latest) | ||
|
||
# If I provide only api_version, this creates a profile with just that | ||
client = TestClient(api_version="2666-05-15") | ||
assert client.operations() == "2666-05-15" | ||
|
||
# I can specify old profile syntax with dict | ||
client = TestClient(profile={ | ||
"operations": "1789-07-14" | ||
}) | ||
assert client.operations() == "1789-07-14" | ||
|
||
# If I give a profile definition with no default api-version | ||
# and I call a method not define in the profile, this fails | ||
client = TestClient(profile={ | ||
"operations2": "1789-07-14" | ||
}) | ||
with pytest.raises(ValueError): | ||
client.operations() == "1789-07-14" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.