Просмотр исходного кода

Type the Azure provider; run mypy with deps and pin mypy 2.1

Annotate cloudbridge/providers/azure/ (~475 callables incl. azure_client.py)
and add providers.azure.* to the pragmatic mypy tier. With this, all five
providers are fully typed. Azure conforms to the decided contracts (start
implemented; direction -> TrafficDirection; required id getters raise; creates
raise on error; upload -> BucketObject; find -> ResultList; optional getters ->
| None) and the agents fixed several latent bugs surfaced by typing
(AzureBucket.exists called a nonexistent self.get; log.exception(err.message)
would AttributeError on a ValueError; AzureInstanceService.delete guarded the
unresolved id).

Make the type-check environment correct now that the providers (heavy SDK
users) are typed:
- tox [testenv:mypy] installs the full deps (requirements.txt) instead of
  skip_install. Without the cloud SDKs, mypy infers SDK values as Any and
  reports spurious redundant-cast / unused-ignore errors that don't occur in a
  real dev env.
- Pin mypy>=2.1,<3 in the dev extra so CI and developers use the same mypy;
  mypy 2.x narrows `list[X] | list[Y]` after isinstance checks, which made five
  vm_firewalls casts (aws/gcp/openstack services) redundant -- those casts are
  removed (runtime no-ops).

Verified: tox -e mypy (mypy 2.1.0, with deps) green across all 43 files;
flake8 clean.
Nuwan Goonasekera 10 часов назад
Родитель
Сommit
965576350d

+ 1 - 2
cloudbridge/providers/aws/services.py

@@ -850,8 +850,7 @@ class AWSInstanceService(BaseInstanceService):
         vm_firewall_ids: list[str] | None
         if vm_firewalls and isinstance(vm_firewalls, list) and isinstance(
                 vm_firewalls[0], VMFirewall):
-            vm_firewall_ids = [fw.id for fw in
-                               cast("list[VMFirewall]", vm_firewalls)]
+            vm_firewall_ids = [fw.id for fw in vm_firewalls]
         else:
             vm_firewall_ids = cast("list[str] | None", vm_firewalls)
         return subnet.id if subnet else None, zone_id, vm_firewall_ids

+ 168 - 135
cloudbridge/providers/azure/azure_client.py

@@ -1,11 +1,8 @@
+from __future__ import annotations
+
 import datetime
 import logging
-
-import tenacity
-from cloudbridge.interfaces.exceptions import (DuplicateResourceException,
-                                               InvalidLabelException,
-                                               ProviderConnectionException,
-                                               WaitStateException)
+from typing import Any
 
 from azure.core.credentials import AzureNamedKeyCredential
 from azure.core.exceptions import (ClientAuthenticationError,
@@ -33,6 +30,13 @@ from azure.mgmt.subscription import SubscriptionClient
 from azure.storage.blob import (BlobBlock, BlobSasPermissions,
                                 BlobServiceClient, generate_blob_sas)
 
+import tenacity
+
+from cloudbridge.interfaces.exceptions import (DuplicateResourceException,
+                                               InvalidLabelException,
+                                               ProviderConnectionException,
+                                               WaitStateException)
+
 from . import helpers as azure_helpers
 
 log = logging.getLogger(__name__)
@@ -167,27 +171,30 @@ class AzureClient(object):
     """
     Azure client is the wrapper on top of azure python sdk
     """
-    def __init__(self, config):
+    def __init__(self, config: dict[str, Any]) -> None:
         self._config = config
         self.subscription_id = str(config.get('azure_subscription_id'))
+        # config.get() yields Any | None; the typed azure.identity SDK wants
+        # str. These auth keys are always supplied by the provider, so the
+        # None branch is not reachable in practice.
         self._credentials = ClientSecretCredential(
-            tenant_id=config.get('azure_tenant'),
-            client_id=config.get('azure_client_id'),
-            client_secret=config.get('azure_secret')
+            tenant_id=config.get('azure_tenant'),  # type: ignore[arg-type]
+            client_id=config.get('azure_client_id'),  # type: ignore[arg-type]
+            client_secret=config.get('azure_secret')  # type: ignore[arg-type]
         )
 
         self._access_token = config.get('azure_access_token')
-        self._resource_client = None
-        self._storage_client = None
-        self._network_management_client = None
-        self._subscription_client = None
-        self._compute_client = None
-        self._dns_client = None
-        self._access_key_result = None
-        self._block_blob_service = None
-        self._table_service_client = None
-        self._public_key_table_client = None
-        self._storage_account = None
+        self._resource_client: Any = None
+        self._storage_client: Any = None
+        self._network_management_client: Any = None
+        self._subscription_client: Any = None
+        self._compute_client: Any = None
+        self._dns_client: Any = None
+        self._access_key_result: Any = None
+        self._block_blob_service: Any = None
+        self._table_service_client: Any = None
+        self._public_key_table_client: Any = None
+        self._storage_account: Any = None
 
         log.debug("azure subscription : %s", self.subscription_id)
 
@@ -204,7 +211,7 @@ class AzureClient(object):
         retry=tenacity.retry_if_exception_type(WaitStateException),
         reraise=True,
     )
-    def access_key_result(self):
+    def access_key_result(self) -> Any:
         if not self._access_key_result:
             storage_account = self.storage_account
 
@@ -225,27 +232,27 @@ class AzureClient(object):
         return self._access_key_result
 
     @property
-    def resource_group(self):
+    def resource_group(self) -> Any:
         return self._config.get('azure_resource_group')
 
     @property
-    def networking_resource_group(self):
+    def networking_resource_group(self) -> Any:
         return self._config.get('azure_networking_resource_group')
 
     @property
-    def storage_account(self):
+    def storage_account(self) -> Any:
         return self._config.get('azure_storage_account')
 
     @property
-    def region_name(self):
+    def region_name(self) -> Any:
         return self._config.get('azure_region_name')
 
     @property
-    def public_key_storage_table_name(self):
+    def public_key_storage_table_name(self) -> Any:
         return self._config.get('azure_public_key_storage_table_name')
 
     @property
-    def storage_client(self):
+    def storage_client(self) -> Any:
         if not self._storage_client:
             self._storage_client = \
                 StorageManagementClient(self._credentials,
@@ -253,13 +260,13 @@ class AzureClient(object):
         return self._storage_client
 
     @property
-    def subscription_client(self):
+    def subscription_client(self) -> Any:
         if not self._subscription_client:
             self._subscription_client = SubscriptionClient(self._credentials)
         return self._subscription_client
 
     @property
-    def resource_client(self):
+    def resource_client(self) -> Any:
         if not self._resource_client:
             self._resource_client = \
                 ResourceManagementClient(self._credentials,
@@ -267,7 +274,7 @@ class AzureClient(object):
         return self._resource_client
 
     @property
-    def compute_client(self):
+    def compute_client(self) -> Any:
         if not self._compute_client:
             self._compute_client = \
                 ComputeManagementClient(self._credentials,
@@ -275,21 +282,21 @@ class AzureClient(object):
         return self._compute_client
 
     @property
-    def network_management_client(self):
+    def network_management_client(self) -> Any:
         if not self._network_management_client:
             self._network_management_client = NetworkManagementClient(
                 self._credentials, self.subscription_id)
         return self._network_management_client
 
     @property
-    def dns_client(self):
+    def dns_client(self) -> Any:
         if not self._dns_client:
             self._dns_client = DnsManagementClient(
                 self._credentials, self.subscription_id)
         return self._dns_client
 
     @property
-    def blob_service(self):
+    def blob_service(self) -> Any:
         self._get_or_create_storage_account()
         if not self._block_blob_service:
             if self._access_token:
@@ -303,7 +310,7 @@ class AzureClient(object):
         return self._block_blob_service
 
     @property
-    def table_service(self):
+    def table_service(self) -> Any:
         self._get_or_create_storage_account()
         if not self._table_service_client:
             credential = AzureNamedKeyCredential(
@@ -318,21 +325,21 @@ class AzureClient(object):
                     table_name=self.public_key_storage_table_name)
         return self._public_key_table_client
 
-    def blob_client(self, container_name, blob_name):
+    def blob_client(self, container_name: str, blob_name: str) -> Any:
         return self.blob_service.get_blob_client(container=container_name, blob=blob_name)
 
-    def get_resource_group(self, name):
+    def get_resource_group(self, name: str) -> Any:
         return self.resource_client.resource_groups.get(name)
 
-    def create_resource_group(self, name, parameters):
+    def create_resource_group(self, name: str, parameters: dict[str, Any]) -> Any:
         return self.resource_client.resource_groups. \
             create_or_update(name, ResourceGroup(**parameters))
 
-    def get_storage_account(self, storage_account):
+    def get_storage_account(self, storage_account: str) -> Any:
         return self.storage_client.storage_accounts. \
             get_properties(self.resource_group, storage_account)
 
-    def create_storage_account(self, name, params):
+    def create_storage_account(self, name: str, params: dict[str, Any]) -> Any:
         return self.storage_client.storage_accounts. \
             begin_create(self.resource_group, name.lower(),
                          StorageAccountCreateParameters(**params)).result()
@@ -342,7 +349,7 @@ class AzureClient(object):
     @tenacity.retry(stop=tenacity.stop.stop_after_attempt(2),
                     retry=tenacity.retry_if_exception_type(HttpResponseError),
                     reraise=True)
-    def _get_or_create_storage_account(self):
+    def _get_or_create_storage_account(self) -> Any:
         if self._storage_account:
             return self._storage_account
         else:
@@ -386,43 +393,44 @@ class AzureClient(object):
                                % exists_err
                     raise InvalidLabelException(mess)
 
-    def list_locations(self):
+    def list_locations(self) -> Any:
         return self.subscription_client.subscriptions. \
             list_locations(self.subscription_id)
 
-    def list_vm_firewall(self):
+    def list_vm_firewall(self) -> Any:
         return self.network_management_client.network_security_groups. \
             list(self.resource_group)
 
-    def create_vm_firewall(self, name, parameters):
+    def create_vm_firewall(self, name: str, parameters: dict[str, Any]) -> Any:
         return self.network_management_client.network_security_groups. \
             begin_create_or_update(
                 self.resource_group, name,
                 NetworkSecurityGroup(**parameters)).result()
 
-    def update_vm_firewall_tags(self, fw_id, tags):
+    def update_vm_firewall_tags(self, fw_id: str, tags: dict[str, str]) -> Any:
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
                                              fw_id)
         name = url_params.get(VM_FIREWALL_NAME, "")
         return self.network_management_client.network_security_groups. \
             update_tags(self.resource_group, name, TagsObject(tags=tags))
 
-    def get_vm_firewall(self, fw_id):
+    def get_vm_firewall(self, fw_id: str) -> Any:
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
                                              fw_id)
         fw_name = url_params.get(VM_FIREWALL_NAME, "")
         return self.network_management_client.network_security_groups. \
             get(self.resource_group, fw_name)
 
-    def delete_vm_firewall(self, fw_id):
+    def delete_vm_firewall(self, fw_id: str) -> None:
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
                                              fw_id)
         name = url_params.get(VM_FIREWALL_NAME, "")
         self.network_management_client \
             .network_security_groups.begin_delete(self.resource_group, name).wait()
 
-    def create_vm_firewall_rule(self, fw_id,
-                                rule_name, parameters):
+    def create_vm_firewall_rule(self, fw_id: str,
+                                rule_name: str,
+                                parameters: dict[str, Any] | SecurityRule) -> Any:
         url_params = azure_helpers.parse_url(VM_FIREWALL_RESOURCE_ID,
                                              fw_id)
         vm_firewall_name = url_params.get(VM_FIREWALL_NAME, "")
@@ -435,20 +443,22 @@ class AzureClient(object):
             begin_create_or_update(self.resource_group, vm_firewall_name,
                                    rule_name, rule).result()
 
-    def delete_vm_firewall_rule(self, fw_rule_id, vm_firewall):
+    def delete_vm_firewall_rule(self, fw_rule_id: str, vm_firewall: str) -> Any:
         url_params = azure_helpers.parse_url(VM_FIREWALL_RULE_RESOURCE_ID,
                                              fw_rule_id)
         name = url_params.get(VM_FIREWALL_RULE_NAME, "")
         return self.network_management_client.security_rules. \
             begin_delete(self.resource_group, vm_firewall, name).result()
 
-    def list_containers(self, prefix=None, limit=None, marker=None):
+    def list_containers(self, prefix: str | None = None,
+                        limit: int | None = None,
+                        marker: str | None = None) -> Any:
         results = self.blob_service.list_containers(name_starts_with=prefix,
                                                     results_per_page=limit,
                                                     marker=marker)
         return results
 
-    def create_container(self, container_name):
+    def create_container(self, container_name: str) -> Any:
         try:
             return self.blob_service.create_container(container_name)
         except ResourceExistsError:
@@ -459,40 +469,46 @@ class AzureClient(object):
                     "in Storage Accounts." % container_name
             raise DuplicateResourceException(msg)
 
-    def get_container(self, container_name):
+    def get_container(self, container_name: str) -> Any:
         return self.blob_service.get_container_client(container_name)
 
-    def delete_container(self, container_name):
+    def delete_container(self, container_name: str) -> None:
         self.blob_service.delete_container(container_name)
 
-    def list_blobs(self, container_name, prefix=None, include=None):
+    def list_blobs(self, container_name: str, prefix: str | None = None,
+                   include: Any = None) -> Any:
         container_client = self.get_container(container_name)
         return container_client.list_blobs(name_starts_with=prefix, include=include)
 
-    def upload_blob(self, container_name, blob_name, data, length=None,
-                    max_concurrency=1):
+    def upload_blob(self, container_name: str, blob_name: str, data: Any,
+                    length: int | None = None,
+                    max_concurrency: int = 1) -> None:
         blob_client = self.blob_client(container_name, blob_name)
         blob_client.upload_blob(data=data, length=length, overwrite=True,
                                 max_concurrency=max_concurrency)
 
-    def stage_block(self, container_name, blob_name, block_id, data):
+    def stage_block(self, container_name: str, blob_name: str,
+                    block_id: str, data: Any) -> None:
         blob_client = self.blob_client(container_name, blob_name)
         blob_client.stage_block(block_id, data)
 
-    def commit_block_list(self, container_name, blob_name, block_ids):
+    def commit_block_list(self, container_name: str, blob_name: str,
+                          block_ids: list[str]) -> None:
         blob_client = self.blob_client(container_name, blob_name)
         block_list = [BlobBlock(block_id=block_id) for block_id in block_ids]
         blob_client.commit_block_list(block_list)
 
-    def get_blob(self, container_name, blob_name):
+    def get_blob(self, container_name: str, blob_name: str) -> Any:
         blob_client = self.blob_client(container_name, blob_name)
         return blob_client.get_blob_properties(container_name, blob_name)
 
-    def delete_blob(self, container_name, blob_name, delete_snapshots="include"):
+    def delete_blob(self, container_name: str, blob_name: str,
+                    delete_snapshots: str = "include") -> None:
         blob_client = self.blob_client(container_name, blob_name)
         blob_client.delete_blob(delete_snapshots)
 
-    def get_blob_url(self, container_name, blob_name, expiry_time, writable):
+    def get_blob_url(self, container_name: Any, blob_name: str,
+                     expiry_time: int, writable: bool) -> str:
         now = datetime.datetime.utcnow()
         expiry = now + datetime.timedelta(
             seconds=expiry_time)
@@ -509,37 +525,37 @@ class AzureClient(object):
         url = f"https://{self.storage_account}.blob.core.windows.net/{container_name}/{blob_name}?{sas}"
         return url
 
-    def create_empty_disk(self, disk_name, params):
+    def create_empty_disk(self, disk_name: str, params: dict[str, Any]) -> Any:
         return self.compute_client.disks.begin_create_or_update(
             self.resource_group,
             disk_name,
             Disk(**params)
         ).result()
 
-    def create_snapshot_disk(self, disk_name, params):
+    def create_snapshot_disk(self, disk_name: str, params: dict[str, Any]) -> Any:
         return self.compute_client.disks.begin_create_or_update(
             self.resource_group,
             disk_name,
             Disk(**params)
         ).result()
 
-    def get_disk(self, disk_id):
+    def get_disk(self, disk_id: str) -> Any:
         url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
                                              disk_id)
         disk_name = url_params.get(VOLUME_NAME, "")
         return self.compute_client.disks.get(self.resource_group, disk_name)
 
-    def list_disks(self):
+    def list_disks(self) -> Any:
         return self.compute_client.disks. \
             list_by_resource_group(self.resource_group)
 
-    def delete_disk(self, disk_id):
+    def delete_disk(self, disk_id: str) -> None:
         url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
                                              disk_id)
         disk_name = url_params.get(VOLUME_NAME, "")
         self.compute_client.disks.begin_delete(self.resource_group, disk_name).wait()
 
-    def update_disk_tags(self, disk_id, tags):
+    def update_disk_tags(self, disk_id: str, tags: dict[str, str]) -> Any:
         url_params = azure_helpers.parse_url(VOLUME_RESOURCE_ID,
                                              disk_id)
         disk_name = url_params.get(VOLUME_NAME, "")
@@ -549,22 +565,25 @@ class AzureClient(object):
             DiskUpdate(tags=tags)
         ).wait()
 
-    def list_snapshots(self):
+    def list_snapshots(self) -> Any:
         return self.compute_client.snapshots. \
             list_by_resource_group(self.resource_group)
 
-    def get_snapshot(self, snapshot_id):
+    def get_snapshot(self, snapshot_id: str) -> Any:
         url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
                                              snapshot_id)
         snapshot_name = url_params.get(SNAPSHOT_NAME, "")
         return self.compute_client.snapshots.get(self.resource_group,
                                                  snapshot_name)
 
-    def create_snapshot(self, snapshot_name, volume, tags):
+    def create_snapshot(self, snapshot_name: str, volume: Any,
+                        tags: dict[str, str] | None) -> Any:
+        # The typed azure.mgmt.compute stub's keyword overload omits the
+        # top-level creation_data kwarg that the runtime model accepts.
         snapshot = self.compute_client.snapshots.begin_create_or_update(
             self.resource_group,
             snapshot_name,
-            Snapshot(
+            Snapshot(  # type: ignore[call-overload]
                 location=volume.location,
                 creation_data=CreationData(
                     create_option='Copy',
@@ -577,14 +596,15 @@ class AzureClient(object):
         self.update_snapshot_tags(snapshot.id, tags)
         return snapshot
 
-    def delete_snapshot(self, snapshot_id):
+    def delete_snapshot(self, snapshot_id: str) -> None:
         url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
                                              snapshot_id)
         snapshot_name = url_params.get(SNAPSHOT_NAME, "")
         self.compute_client.snapshots.begin_delete(self.resource_group,
                                                    snapshot_name).wait()
 
-    def update_snapshot_tags(self, snapshot_id, tags):
+    def update_snapshot_tags(self, snapshot_id: str,
+                             tags: dict[str, str] | None) -> Any:
         url_params = azure_helpers.parse_url(SNAPSHOT_RESOURCE_ID,
                                              snapshot_id)
         snapshot_name = url_params.get(SNAPSHOT_NAME, "")
@@ -594,33 +614,33 @@ class AzureClient(object):
             SnapshotUpdate(tags=tags)
         ).wait()
 
-    def is_gallery_image(self, image_id):
+    def is_gallery_image(self, image_id: str) -> bool:
         url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
                                              image_id)
         # If it is a gallery image, it will always have an offer
         return 'offer' in url_params
 
-    def create_image(self, name, params):
+    def create_image(self, name: str, params: dict[str, Any]) -> Any:
         return self.compute_client.images. \
             begin_create_or_update(
                 self.resource_group, name, Image(**params)).result()
 
-    def delete_image(self, image_id):
+    def delete_image(self, image_id: str) -> None:
         url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
                                              image_id)
         if not self.is_gallery_image(image_id):
             name = url_params.get(IMAGE_NAME, "")
             self.compute_client.images.begin_delete(self.resource_group, name).wait()
 
-    def list_images(self):
+    def list_images(self) -> list[Any]:
         azure_images = list(self.compute_client.images.
                             list_by_resource_group(self.resource_group))
         return azure_images
 
-    def list_gallery_refs(self):
+    def list_gallery_refs(self) -> list[Any]:
         return gallery_image_references
 
-    def get_image(self, image_id):
+    def get_image(self, image_id: str) -> Any:
         url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
                                              image_id)
         if self.is_gallery_image(image_id):
@@ -632,7 +652,7 @@ class AzureClient(object):
             name = url_params.get(IMAGE_NAME, "")
             return self.compute_client.images.get(self.resource_group, name)
 
-    def update_image_tags(self, image_id, tags):
+    def update_image_tags(self, image_id: str, tags: dict[str, str]) -> Any:
         url_params = azure_helpers.parse_url(IMAGE_RESOURCE_ID,
                                              image_id)
         if self.is_gallery_image(image_id):
@@ -643,54 +663,55 @@ class AzureClient(object):
                 self.resource_group, name,
                 ImageUpdate(tags=tags)).result()
 
-    def list_vm_types(self):
+    def list_vm_types(self) -> Any:
         return self.compute_client.virtual_machine_sizes. \
             list(self.region_name)
 
-    def list_networks(self):
+    def list_networks(self) -> Any:
         return self.network_management_client.virtual_networks.list(
             self.networking_resource_group)
 
-    def get_network(self, network_id):
+    def get_network(self, network_id: str) -> Any:
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID,
                                              network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         return self.network_management_client.virtual_networks.get(
             self.networking_resource_group, network_name)
 
-    def create_network(self, name, params):
+    def create_network(self, name: str, params: dict[str, Any]) -> Any:
         return self.network_management_client.virtual_networks. \
             begin_create_or_update(
                 self.networking_resource_group, name,
                 parameters=VirtualNetwork(**params)).result()
 
-    def delete_network(self, network_id):
+    def delete_network(self, network_id: str) -> Any:
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         return self.network_management_client.virtual_networks. \
             begin_delete(self.networking_resource_group, network_name).wait()
 
-    def update_network_tags(self, network_id, tags):
+    def update_network_tags(self, network_id: str,
+                            tags: dict[str, str]) -> Any:
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         return self.network_management_client.virtual_networks. \
             update_tags(self.networking_resource_group, network_name,
                         TagsObject(tags=tags))
 
-    def get_network_id_for_subnet(self, subnet_id):
+    def get_network_id_for_subnet(self, subnet_id: str) -> str:
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID, subnet_id)
         network_id = NETWORK_RESOURCE_ID[0]
         for key, val in url_params.items():
             network_id = network_id.replace("{" + key + "}", val)
         return network_id
 
-    def list_subnets(self, network_id):
+    def list_subnets(self, network_id: str) -> Any:
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         return self.network_management_client.subnets. \
             list(self.networking_resource_group, network_name)
 
-    def get_subnet(self, subnet_id):
+    def get_subnet(self, subnet_id: str) -> Any:
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
                                              subnet_id)
         network_name = url_params.get(NETWORK_NAME, "")
@@ -698,7 +719,8 @@ class AzureClient(object):
         return self.network_management_client.subnets. \
             get(self.networking_resource_group, network_name, subnet_name)
 
-    def create_subnet(self, network_id, subnet_name, params):
+    def create_subnet(self, network_id: str, subnet_name: str,
+                      params: dict[str, Any]) -> Any:
         url_params = azure_helpers.parse_url(NETWORK_RESOURCE_ID, network_id)
         network_name = url_params.get(NETWORK_NAME, "")
         result_create = self.network_management_client \
@@ -712,7 +734,8 @@ class AzureClient(object):
 
         return subnet_info
 
-    def __if_subnet_in_use(e):
+    @staticmethod
+    def __if_subnet_in_use(e: BaseException) -> bool:
         # return True if the CloudError exception is due to subnet being in use
         if isinstance(e, HttpResponseError):
             if "InUseSubnetCannotBeDeleted" in e.message:
@@ -723,7 +746,7 @@ class AzureClient(object):
                     retry=tenacity.retry_if_exception(__if_subnet_in_use),
                     wait=tenacity.wait.wait_fixed(5),
                     reraise=True)
-    def delete_subnet(self, subnet_id):
+    def delete_subnet(self, subnet_id: str) -> None:
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
                                              subnet_id)
         network_name = url_params.get(NETWORK_NAME, "")
@@ -741,21 +764,22 @@ class AzureClient(object):
             log.exception(cloud_error.message)
             raise cloud_error
 
-    def create_floating_ip(self, public_ip_name, public_ip_parameters):
+    def create_floating_ip(self, public_ip_name: str,
+                           public_ip_parameters: dict[str, Any]) -> Any:
         return self.network_management_client.public_ip_addresses. \
             begin_create_or_update(
                 self.networking_resource_group,
                 public_ip_name,
                 PublicIPAddress(**public_ip_parameters)).result()
 
-    def get_floating_ip(self, public_ip_id):
+    def get_floating_ip(self, public_ip_id: str) -> Any:
         url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
                                              public_ip_id)
         public_ip_name = url_params.get(PUBLIC_IP_NAME, "")
         return self.network_management_client. \
             public_ip_addresses.get(self.networking_resource_group, public_ip_name)
 
-    def delete_floating_ip(self, public_ip_id):
+    def delete_floating_ip(self, public_ip_id: str) -> None:
         url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
                                              public_ip_id)
         public_ip_name = url_params.get(PUBLIC_IP_NAME, "")
@@ -763,7 +787,7 @@ class AzureClient(object):
             public_ip_addresses.begin_delete(self.networking_resource_group,
                                              public_ip_name).wait()
 
-    def update_fip_tags(self, fip_id, tags):
+    def update_fip_tags(self, fip_id: str, tags: dict[str, str]) -> None:
         url_params = azure_helpers.parse_url(PUBLIC_IP_RESOURCE_ID,
                                              fip_id)
         fip_name = url_params.get(PUBLIC_IP_NAME, "")
@@ -771,37 +795,37 @@ class AzureClient(object):
             update_tags(self.networking_resource_group, fip_name,
                         TagsObject(tags=tags))
 
-    def list_floating_ips(self):
+    def list_floating_ips(self) -> Any:
         return self.network_management_client.public_ip_addresses.list(
             self.networking_resource_group)
 
-    def list_vm(self):
+    def list_vm(self) -> Any:
         return self.compute_client.virtual_machines.list(
             self.resource_group
         )
 
-    def restart_vm(self, vm_id):
+    def restart_vm(self, vm_id: str) -> Any:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         return self.compute_client.virtual_machines.begin_restart(
             self.resource_group, vm_name).wait()
 
-    def stop_vm(self, vm_id):
+    def stop_vm(self, vm_id: str) -> None:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         self.compute_client.virtual_machines. \
             begin_power_off(self.resource_group, vm_name).wait()
 
-    def delete_vm(self, vm_id):
+    def delete_vm(self, vm_id: str) -> Any:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         return self.compute_client.virtual_machines.begin_delete(
             self.resource_group, vm_name).wait()
 
-    def get_vm(self, vm_id):
+    def get_vm(self, vm_id: str) -> Any:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
@@ -811,13 +835,13 @@ class AzureClient(object):
             expand='instanceView'
         )
 
-    def create_vm(self, vm_name, params):
+    def create_vm(self, vm_name: str, params: dict[str, Any]) -> Any:
         return self.compute_client.virtual_machines. \
             begin_create_or_update(
                 self.resource_group, vm_name,
                 VirtualMachine(**params)).result()
 
-    def update_vm(self, vm_id, params):
+    def update_vm(self, vm_id: str, params: dict[str, Any]) -> Any:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
@@ -826,28 +850,28 @@ class AzureClient(object):
                 self.resource_group, vm_name,
                 VirtualMachine(**params)).wait()
 
-    def deallocate_vm(self, vm_id):
+    def deallocate_vm(self, vm_id: str) -> None:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         self.compute_client. \
             virtual_machines.begin_deallocate(self.resource_group, vm_name).wait()
 
-    def generalize_vm(self, vm_id):
+    def generalize_vm(self, vm_id: str) -> None:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         self.compute_client.virtual_machines. \
             generalize(self.resource_group, vm_name)
 
-    def start_vm(self, vm_id):
+    def start_vm(self, vm_id: str) -> None:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
         self.compute_client.virtual_machines. \
             begin_start(self.resource_group, vm_name).wait()
 
-    def update_vm_tags(self, vm_id, tags):
+    def update_vm_tags(self, vm_id: str, tags: dict[str, str]) -> None:
         url_params = azure_helpers.parse_url(VM_RESOURCE_ID,
                                              vm_id)
         vm_name = url_params.get(VM_NAME, "")
@@ -855,21 +879,22 @@ class AzureClient(object):
             self.resource_group, vm_name,
             VirtualMachineUpdate(tags=tags)).result()
 
-    def delete_nic(self, nic_id):
+    def delete_nic(self, nic_id: str) -> None:
         nic_params = azure_helpers.\
             parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
         nic_name = nic_params.get(NETWORK_INTERFACE_NAME, "")
         self.network_management_client. \
             network_interfaces.begin_delete(self.resource_group, nic_name).wait()
 
-    def get_nic(self, nic_id):
+    def get_nic(self, nic_id: str) -> Any:
         nic_params = azure_helpers.\
             parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
         nic_name = nic_params.get(NETWORK_INTERFACE_NAME, "")
         return self.network_management_client. \
             network_interfaces.get(self.resource_group, nic_name)
 
-    def update_nic(self, nic_id, params):
+    def update_nic(self, nic_id: str,
+                   params: dict[str, Any] | NetworkInterface) -> Any:
         nic_params = azure_helpers.\
             parse_url(NETWORK_INTERFACE_RESOURCE_ID, nic_id)
         nic_name = nic_params.get(NETWORK_INTERFACE_NAME, "")
@@ -886,7 +911,7 @@ class AzureClient(object):
         nic_info = async_nic_creation.result()
         return nic_info
 
-    def create_nic(self, nic_name, params):
+    def create_nic(self, nic_name: str, params: dict[str, Any]) -> Any:
         return self.network_management_client. \
             network_interfaces.begin_create_or_update(
                 self.resource_group,
@@ -894,21 +919,23 @@ class AzureClient(object):
                 NetworkInterface(**params)
             ).result()
 
-    def create_public_key(self, entity):
+    def create_public_key(self, entity: dict[str, Any]) -> Any:
         return self.table_service.upsert_entity(entity)
 
-    def get_public_key(self, name):
+    def get_public_key(self, name: str) -> Any:
         entities = list(self.table_service.query_entities(
             query_filter="Name eq '{0}'".format(name),
             results_per_page=1))
         return entities[0] if entities else None
 
-    def delete_public_key(self, entity):
+    def delete_public_key(self, entity: dict[str, Any]) -> None:
         self.table_service.delete_entity(
             partition_key=entity['PartitionKey'],
             row_key=entity['RowKey'])
 
-    def list_public_keys(self, partition_key, limit=None, marker=None):
+    def list_public_keys(self, partition_key: str, limit: int | None = None,
+                         marker: str | None = None
+                         ) -> tuple[list[Any], str | None]:
         pager = self.table_service.query_entities(
             query_filter="PartitionKey eq '{0}'".format(partition_key),
             results_per_page=limit).by_page(continuation_token=marker)
@@ -919,12 +946,13 @@ class AzureClient(object):
         items = list(page)
         return (items, pager.continuation_token)
 
-    def delete_route_table(self, route_table_name):
+    def delete_route_table(self, route_table_name: str) -> None:
         self.network_management_client. \
             route_tables.begin_delete(self.resource_group,
                                       route_table_name).wait()
 
-    def attach_subnet_to_route_table(self, subnet_id, route_table_id):
+    def attach_subnet_to_route_table(self, subnet_id: str,
+                                     route_table_id: str) -> Any:
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
                                              subnet_id)
         network_name = url_params.get(NETWORK_NAME, "")
@@ -943,12 +971,13 @@ class AzureClient(object):
                  self.resource_group,
                  network_name,
                  subnet_name,
-                 subnet_info)  # type: ignore
+                 subnet_info)
             subnet_info = result_create.result()
 
         return subnet_info
 
-    def detach_subnet_to_route_table(self, subnet_id, route_table_id):
+    def detach_subnet_to_route_table(self, subnet_id: str,
+                                     route_table_id: str) -> Any:
         url_params = azure_helpers.parse_url(SUBNET_RESOURCE_ID,
                                              subnet_id)
         network_name = url_params.get(NETWORK_NAME, "")
@@ -968,64 +997,68 @@ class AzureClient(object):
                  self.resource_group,
                  network_name,
                  subnet_name,
-                 subnet_info)  # type: ignore
+                 subnet_info)
             subnet_info = result_create.result()
 
         return subnet_info
 
-    def list_route_tables(self):
+    def list_route_tables(self) -> Any:
         return self.network_management_client. \
             route_tables.list(self.resource_group)
 
-    def get_route_table(self, router_id):
+    def get_route_table(self, router_id: str) -> Any:
         url_params = azure_helpers.parse_url(ROUTER_RESOURCE_ID,
                                              router_id)
         router_name = url_params.get(ROUTER_NAME, "")
         return self.network_management_client. \
             route_tables.get(self.resource_group, router_name)
 
-    def create_route_table(self, route_table_name, params):
+    def create_route_table(self, route_table_name: str,
+                           params: dict[str, Any]) -> Any:
         return self.network_management_client. \
             route_tables.begin_create_or_update(
              self.resource_group,
              route_table_name, RouteTable(**params)).result()
 
-    def update_route_table_tags(self, route_table_name, tags):
+    def update_route_table_tags(self, route_table_name: str,
+                                tags: dict[str, str]) -> None:
         self.network_management_client.route_tables.update_tags(
             self.resource_group, route_table_name,
             TagsObject(tags=tags))
 
     # DNS operations
-    def get_dns_zone(self, zone_name):
+    def get_dns_zone(self, zone_name: str) -> Any:
         return self.dns_client.zones.get(self.resource_group, zone_name)
 
-    def list_dns_zones(self):
+    def list_dns_zones(self) -> list[Any]:
         return list(self.dns_client.zones.list_by_resource_group(
             self.resource_group))
 
-    def create_dns_zone(self, zone_name, params):
+    def create_dns_zone(self, zone_name: str, params: dict[str, Any]) -> Any:
         return self.dns_client.zones.create_or_update(
             self.resource_group, zone_name, Zone(**params))
 
-    def delete_dns_zone(self, zone_name):
+    def delete_dns_zone(self, zone_name: str) -> None:
         self.dns_client.zones.begin_delete(
             self.resource_group, zone_name).wait()
 
-    def get_dns_record(self, zone_name, relative_record_name, record_type):
+    def get_dns_record(self, zone_name: str, relative_record_name: str,
+                       record_type: str) -> Any:
         return self.dns_client.record_sets.get(
             self.resource_group, zone_name, relative_record_name, record_type)
 
-    def list_dns_records(self, zone_name):
+    def list_dns_records(self, zone_name: str) -> list[Any]:
         return list(self.dns_client.record_sets.list_all_by_dns_zone(
             self.resource_group, zone_name))
 
-    def create_dns_record(self, zone_name, relative_record_name,
-                          record_type, params):
+    def create_dns_record(self, zone_name: str, relative_record_name: str,
+                          record_type: str, params: dict[str, Any]) -> Any:
         from azure.mgmt.dns.models import RecordSet
         return self.dns_client.record_sets.create_or_update(
             self.resource_group, zone_name, relative_record_name,
             record_type, RecordSet(**params))
 
-    def delete_dns_record(self, zone_name, relative_record_name, record_type):
+    def delete_dns_record(self, zone_name: str, relative_record_name: str,
+                          record_type: str) -> None:
         self.dns_client.record_sets.delete(
             self.resource_group, zone_name, relative_record_name, record_type)

+ 7 - 6
cloudbridge/providers/azure/helpers.py

@@ -1,4 +1,5 @@
 import re
+from typing import Any
 
 from cloudbridge.interfaces.exceptions import InvalidValueException
 
@@ -6,7 +7,7 @@ from cloudbridge.interfaces.exceptions import InvalidValueException
 _RG_NAME_RE = re.compile(r'(/resourceGroups/)([^/]+)', re.IGNORECASE)
 
 
-def normalize_rg_case(azure_id):
+def normalize_rg_case(azure_id: str | None) -> str | None:
     # Microsoft.Compute/images list_by_resource_group returns the RG segment
     # in a case that can differ from what create/get echo back (we've seen
     # uppercase from list, lowercase from create/get for the same RG).
@@ -38,7 +39,7 @@ def normalize_rg_case(azure_id):
 #         return list_items
 
 
-def parse_url(template_urls, original_url):
+def parse_url(template_urls: list[str], original_url: str) -> dict[str, str]:
     """
     In Azure all the resource IDs are returned as URIs.
     ex: '/subscriptions/{subscriptionId}/resourceGroups/' \
@@ -52,7 +53,7 @@ def parse_url(template_urls, original_url):
     https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-ps-findimage
     """
     if not original_url:
-        raise InvalidValueException(template_urls, original_url)
+        raise InvalidValueException(str(template_urls), original_url)
     original_url_parts = original_url.split('/')
     if len(original_url_parts) == 1:
         original_url_parts = original_url.split(':')
@@ -63,15 +64,15 @@ def parse_url(template_urls, original_url):
         if len(template_url_parts) == len(original_url_parts):
             break
     if len(template_url_parts) != len(original_url_parts):
-        raise InvalidValueException(template_urls, original_url)
-    resource_param = {}
+        raise InvalidValueException(str(template_urls), original_url)
+    resource_param: dict[str, str] = {}
     for key, value in zip(template_url_parts, original_url_parts):
         if key.startswith('{') and key.endswith('}'):
             resource_param.update({key[1:-1]: value})
     return resource_param
 
 
-def generate_urn(gallery_image):
+def generate_urn(gallery_image: Any) -> str:
     """
     This function takes an azure gallery image and outputs a corresponding URN
     :param gallery_image: a GalleryImageReference object

+ 23 - 13
cloudbridge/providers/azure/provider.py

@@ -1,5 +1,6 @@
 import logging
 import uuid
+from typing import Any
 
 from azure.core.exceptions import HttpResponseError
 from azure.core.exceptions import ResourceNotFoundError
@@ -12,6 +13,11 @@ import cloudbridge
 from cloudbridge.base import BaseCloudProvider
 from cloudbridge.base.helpers import get_env
 from cloudbridge.interfaces.exceptions import ProviderConnectionException
+from cloudbridge.interfaces.services import ComputeService
+from cloudbridge.interfaces.services import DnsService
+from cloudbridge.interfaces.services import NetworkingService
+from cloudbridge.interfaces.services import SecurityService
+from cloudbridge.interfaces.services import StorageService
 from cloudbridge.providers.azure.azure_client import AzureClient
 
 from .services import AzureComputeService
@@ -24,9 +30,9 @@ log = logging.getLogger(__name__)
 
 
 class AzureCloudProvider(BaseCloudProvider):
-    PROVIDER_ID = 'azure'
+    PROVIDER_ID: str = 'azure'
 
-    def __init__(self, config):
+    def __init__(self, config: dict[str, Any]) -> None:
         super(AzureCloudProvider, self).__init__(config)
 
         # mandatory config values
@@ -74,7 +80,7 @@ class AzureCloudProvider(BaseCloudProvider):
             'azure_public_key_storage_table_name', get_env(
                 'AZURE_PUBLIC_KEY_STORAGE_TABLE_NAME', 'cbcerts'))
 
-        self._azure_client = None
+        self._azure_client: AzureClient | None = None
 
         self._security = AzureSecurityService(self)
         self._storage = AzureStorageService(self)
@@ -82,7 +88,7 @@ class AzureCloudProvider(BaseCloudProvider):
         self._networking = AzureNetworkingService(self)
         self._dns = AzureDnsService(self)
 
-    def __get_deprecated_username(self, default):
+    def __get_deprecated_username(self, default: str) -> str:
         username = self._get_config_value(
             'azure_vm_default_user_name', get_env(
                 'AZURE_VM_DEFAULT_USER_NAME', None))
@@ -96,31 +102,31 @@ class AzureCloudProvider(BaseCloudProvider):
                 current_version=cloudbridge.__version__,
                 details='AZURE_VM_DEFAULT_USER_NAME was deprecated in favor '
                         'of AZURE_VM_DEFAULT_USERNAME')
-    def __wrap_deprecated_username(self, username):
+    def __wrap_deprecated_username(self, username: str) -> str:
         return username
 
     @property
-    def compute(self):
+    def compute(self) -> ComputeService:
         return self._compute
 
     @property
-    def networking(self):
+    def networking(self) -> NetworkingService:
         return self._networking
 
     @property
-    def security(self):
+    def security(self) -> SecurityService:
         return self._security
 
     @property
-    def storage(self):
+    def storage(self) -> StorageService:
         return self._storage
 
     @property
-    def dns(self):
+    def dns(self) -> DnsService:
         return self._dns
 
     @property
-    def azure_client(self):
+    def azure_client(self) -> Any:
         if not self._azure_client:
 
             # create a dict with both optional and mandatory configuration
@@ -148,12 +154,16 @@ class AzureCloudProvider(BaseCloudProvider):
     @tenacity.retry(stop=tenacity.stop_after_attempt(2),
                     retry=tenacity.retry_if_exception_type(HttpResponseError),
                     reraise=True)
-    def _initialize(self):
+    def _initialize(self) -> None:
         """
         Verifying that resource group and storage account exists
         if not create one with the name provided in the
         configuration
         """
+        # ``_initialize`` is only ever invoked by the ``azure_client`` property
+        # immediately after assigning ``self._azure_client``, so the client is
+        # guaranteed to be set here.
+        assert self._azure_client is not None
         try:
             self._azure_client.get_resource_group(self.resource_group)
 
@@ -164,7 +174,7 @@ class AzureCloudProvider(BaseCloudProvider):
                     create_resource_group(self.resource_group,
                                           resource_group_params)
             except HttpResponseError as cloud_error2:  # pragma: no cover
-                if getattr(cloud_error2, 'error', None) and \
+                if cloud_error2.error and \
                         cloud_error2.error.code == "AuthorizationFailed":
                     mess = 'The following error was returned by Azure:\n' \
                            '%s\n\nThis is likely because the Role' \

Разница между файлами не показана из-за своего большого размера
+ 263 - 200
cloudbridge/providers/azure/resources.py


Разница между файлами не показана из-за своего большого размера
+ 369 - 223
cloudbridge/providers/azure/services.py


+ 13 - 6
cloudbridge/providers/azure/subservices.py

@@ -6,40 +6,47 @@ from cloudbridge.base.subservices import BaseFloatingIPSubService
 from cloudbridge.base.subservices import BaseGatewaySubService
 from cloudbridge.base.subservices import BaseSubnetSubService
 from cloudbridge.base.subservices import BaseVMFirewallRuleSubService
+from cloudbridge.interfaces.provider import CloudProvider
+from cloudbridge.interfaces.resources import Bucket
+from cloudbridge.interfaces.resources import DnsZone
+from cloudbridge.interfaces.resources import Gateway
+from cloudbridge.interfaces.resources import Network
+from cloudbridge.interfaces.resources import VMFirewall
 
 log = logging.getLogger(__name__)
 
 
 class AzureBucketObjectSubService(BaseBucketObjectSubService):
 
-    def __init__(self, provider, bucket):
+    def __init__(self, provider: CloudProvider, bucket: Bucket) -> None:
         super(AzureBucketObjectSubService, self).__init__(provider, bucket)
 
 
 class AzureGatewaySubService(BaseGatewaySubService):
-    def __init__(self, provider, network):
+    def __init__(self, provider: CloudProvider, network: Network) -> None:
         super(AzureGatewaySubService, self).__init__(provider, network)
 
 
 class AzureVMFirewallRuleSubService(BaseVMFirewallRuleSubService):
 
-    def __init__(self, provider, firewall):
+    def __init__(self, provider: CloudProvider,
+                 firewall: VMFirewall) -> None:
         super(AzureVMFirewallRuleSubService, self).__init__(provider, firewall)
 
 
 class AzureFloatingIPSubService(BaseFloatingIPSubService):
 
-    def __init__(self, provider, gateway):
+    def __init__(self, provider: CloudProvider, gateway: Gateway) -> None:
         super(AzureFloatingIPSubService, self).__init__(provider, gateway)
 
 
 class AzureSubnetSubService(BaseSubnetSubService):
 
-    def __init__(self, provider, network):
+    def __init__(self, provider: CloudProvider, network: Network) -> None:
         super(AzureSubnetSubService, self).__init__(provider, network)
 
 
 class AzureDnsRecordSubService(BaseDnsRecordSubService):
 
-    def __init__(self, provider, dns_zone):
+    def __init__(self, provider: CloudProvider, dns_zone: DnsZone) -> None:
         super(AzureDnsRecordSubService, self).__init__(provider, dns_zone)

+ 2 - 3
cloudbridge/providers/gcp/services.py

@@ -669,10 +669,9 @@ class GCPInstanceService(BaseInstanceService):
         if vm_firewalls and isinstance(vm_firewalls, list):
             vm_firewall_names: builtins.list[str] = []
             if isinstance(vm_firewalls[0], VMFirewall):
-                vm_firewall_names = [
-                    f.name for f in cast("list[VMFirewall]", vm_firewalls)]
+                vm_firewall_names = [f.name for f in vm_firewalls]
             elif isinstance(vm_firewalls[0], str):
-                vm_firewall_names = cast("list[str]", vm_firewalls)
+                vm_firewall_names = vm_firewalls
             if len(vm_firewall_names) > 0:
                 config['tags'] = {}
                 config['tags']['items'] = vm_firewall_names

+ 2 - 3
cloudbridge/providers/openstack/services.py

@@ -1025,11 +1025,10 @@ class OpenStackInstanceService(BaseInstanceService):
             if vm_firewalls:
                 if isinstance(vm_firewalls, list) and \
                         isinstance(vm_firewalls[0], VMFirewall):
-                    sg_name_list = [sg.name for sg in
-                                    cast("list[VMFirewall]", vm_firewalls)]
+                    sg_name_list = [sg.name for sg in vm_firewalls]
                 else:
                     sg_list = (self.provider.security.vm_firewalls.get(sg)
-                               for sg in cast("list[str]", vm_firewalls))
+                               for sg in vm_firewalls)
                     sg_name_list = (sg[0].name for sg in sg_list if sg)
 
         log.debug("Launching in subnet %s", subnet_id)

+ 2 - 1
pyproject.toml

@@ -91,7 +91,7 @@ dev = [
     "pydevd",
     "flake8>=3.3.0",
     "flake8-import-order>=0.12",
-    "mypy>=1.11",
+    "mypy>=2.1,<3",
 ]
 
 [tool.setuptools.dynamic]
@@ -153,6 +153,7 @@ module = [
     "cloudbridge.providers.mock.*",
     "cloudbridge.providers.openstack.*",
     "cloudbridge.providers.gcp.*",
+    "cloudbridge.providers.azure.*",
 ]
 ignore_errors = false
 warn_return_any = false

+ 7 - 5
tox.ini

@@ -91,9 +91,11 @@ commands = flake8 cloudbridge tests
 deps = flake8
 
 [testenv:mypy]
-# Type-check the public interface layer (see [tool.mypy] in pyproject.toml).
-# The interfaces import no third-party packages, so the package itself does not
-# need installing here, keeping this env fast.
-skip_install = true
-deps = mypy
+# Type-check the codebase (see [tool.mypy] in pyproject.toml). The provider
+# layer is typed against the cloud SDKs, so the env installs the full deps
+# (via requirements.txt -> .[dev], which includes mypy) rather than running
+# bare: without the SDKs installed, mypy infers SDK values as Any and reports
+# spurious redundant-cast / unused-ignore errors that don't occur in a real
+# dev environment.
+deps = -rrequirements.txt
 commands = mypy

Некоторые файлы не были показаны из-за большого количества измененных файлов