cloudbridge 4.1.0__tar.gz → 4.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/PKG-INFO +2 -2
  2. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/__init__.py +9 -6
  3. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/helpers.py +25 -40
  4. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/middleware.py +7 -3
  5. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/provider.py +36 -25
  6. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/resources.py +514 -141
  7. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/services.py +132 -75
  8. cloudbridge-4.2.0/cloudbridge/base/subservices.py +249 -0
  9. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/factory.py +14 -10
  10. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/exceptions.py +4 -4
  11. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/provider.py +34 -18
  12. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/resources.py +387 -167
  13. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/services.py +290 -117
  14. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/subservices.py +57 -34
  15. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/helpers.py +43 -21
  16. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/provider.py +20 -14
  17. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/resources.py +381 -300
  18. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/services.py +455 -273
  19. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/subservices.py +13 -6
  20. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/azure_client.py +178 -146
  21. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/helpers.py +7 -6
  22. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/provider.py +22 -32
  23. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/resources.py +483 -353
  24. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/services.py +703 -423
  25. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/subservices.py +13 -6
  26. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/helpers.py +35 -19
  27. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/provider.py +52 -41
  28. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/resources.py +709 -486
  29. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/services.py +975 -586
  30. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/subservices.py +12 -6
  31. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/mock/provider.py +5 -3
  32. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/helpers.py +12 -5
  33. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/provider.py +82 -60
  34. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/resources.py +383 -287
  35. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/services.py +539 -304
  36. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/subservices.py +14 -6
  37. cloudbridge-4.2.0/cloudbridge/py.typed +0 -0
  38. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge.egg-info/PKG-INFO +2 -2
  39. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge.egg-info/SOURCES.txt +2 -0
  40. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge.egg-info/requires.txt +1 -1
  41. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/pyproject.toml +40 -1
  42. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_base_helpers.py +0 -42
  43. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_block_store_service.py +11 -1
  44. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_compute_service.py +12 -23
  45. cloudbridge-4.2.0/tests/test_multipart_driver.py +223 -0
  46. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_object_store_service.py +206 -0
  47. cloudbridge-4.1.0/cloudbridge/base/subservices.py +0 -202
  48. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/LICENSE +0 -0
  49. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/README.rst +0 -0
  50. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/base/__init__.py +0 -0
  51. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/interfaces/__init__.py +0 -0
  52. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/__init__.py +0 -0
  53. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/aws/__init__.py +0 -0
  54. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/azure/__init__.py +0 -0
  55. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/gcp/__init__.py +0 -0
  56. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/mock/__init__.py +0 -0
  57. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge/providers/openstack/__init__.py +0 -0
  58. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge.egg-info/dependency_links.txt +0 -0
  59. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/cloudbridge.egg-info/top_level.txt +0 -0
  60. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/setup.cfg +0 -0
  61. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_cloud_factory.py +0 -0
  62. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_cloud_helpers.py +0 -0
  63. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_dns_service.py +0 -0
  64. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_image_service.py +0 -0
  65. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_interface.py +0 -0
  66. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_middleware_system.py +0 -0
  67. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_network_service.py +0 -0
  68. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_object_life_cycle.py +0 -0
  69. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_region_service.py +0 -0
  70. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_security_service.py +0 -0
  71. {cloudbridge-4.1.0 → cloudbridge-4.2.0}/tests/test_vm_types_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudbridge
3
- Version: 4.1.0
3
+ Version: 4.2.0
4
4
  Summary: A simple layer of abstraction over multiple cloud providers.
5
5
  Author-email: Galaxy and GVL Projects <help@genome.edu.au>
6
6
  License-Expression: MIT
@@ -22,7 +22,6 @@ Requires-Python: >=3.13
22
22
  Description-Content-Type: text/x-rst
23
23
  License-File: LICENSE
24
24
  Requires-Dist: tenacity>=6.0
25
- Requires-Dist: deprecation>=2.0.7
26
25
  Requires-Dist: pyeventsystem<2
27
26
  Provides-Extra: aws
28
27
  Requires-Dist: boto3<2.0.0,>=1.9.86; extra == "aws"
@@ -60,6 +59,7 @@ Requires-Dist: sphinx>=1.3.1; extra == "dev"
60
59
  Requires-Dist: pydevd; extra == "dev"
61
60
  Requires-Dist: flake8>=3.3.0; extra == "dev"
62
61
  Requires-Dist: flake8-import-order>=0.12; extra == "dev"
62
+ Requires-Dist: mypy<3,>=2.1; extra == "dev"
63
63
  Dynamic: license-file
64
64
 
65
65
  CloudBridge provides a consistent layer of abstraction over different
@@ -1,11 +1,12 @@
1
1
  """Library setup."""
2
2
  import logging
3
+ from typing import Any
3
4
 
4
5
  # Current version of the library
5
- __version__ = '4.1.0'
6
+ __version__ = '4.2.0'
6
7
 
7
8
 
8
- def get_version():
9
+ def get_version() -> str:
9
10
  """
10
11
  Return a string with the current version of the library.
11
12
 
@@ -15,7 +16,7 @@ def get_version():
15
16
  return __version__
16
17
 
17
18
 
18
- def init_logging():
19
+ def init_logging() -> None:
19
20
  """
20
21
  Initialize logging for testing.
21
22
 
@@ -35,7 +36,7 @@ class CBLogger(logging.Logger):
35
36
  Add a ``trace`` log level, numeric value 5: ``log.trace("Log message")``
36
37
  """
37
38
 
38
- def trace(self, msg, *args, **kwargs):
39
+ def trace(self, msg: object, *args: object, **kwargs: Any) -> None:
39
40
  """Add ``trace`` log level."""
40
41
  self.log(TRACE, msg, *args, **kwargs)
41
42
 
@@ -61,7 +62,8 @@ log.addHandler(logging.NullHandler())
61
62
  # cloudbridge.set_file_logger(__name__, '/tmp/log')
62
63
 
63
64
 
64
- def set_stream_logger(name, level=TRACE, format_string=None):
65
+ def set_stream_logger(name: str, level: int = TRACE,
66
+ format_string: str | None = None) -> None:
65
67
  """A convenience method to set the global logger to stream."""
66
68
  global log
67
69
  if not format_string:
@@ -76,7 +78,8 @@ def set_stream_logger(name, level=TRACE, format_string=None):
76
78
  log = logger
77
79
 
78
80
 
79
- def set_file_logger(name, filepath, level=logging.INFO, format_string=None):
81
+ def set_file_logger(name: str, filepath: str, level: int = logging.INFO,
82
+ format_string: str | None = None) -> None:
80
83
  """A convenience method to set the global logger to a file."""
81
84
  global log
82
85
  if not format_string:
@@ -1,24 +1,26 @@
1
1
  import fnmatch
2
- import functools
3
2
  import logging
4
3
  import os
5
4
  import re
5
+ from collections.abc import Callable
6
+ from collections.abc import Iterator
6
7
  from contextlib import contextmanager
8
+ from typing import Any
9
+ from typing import TypeVar
10
+ from typing import overload
7
11
 
8
12
  from cryptography.hazmat.backends import default_backend
9
13
  from cryptography.hazmat.primitives import serialization as crypt_serialization
10
14
  from cryptography.hazmat.primitives.asymmetric import rsa
11
15
 
12
- from deprecation import deprecated
13
-
14
- import cloudbridge
15
-
16
16
  from ..interfaces.exceptions import InvalidParamException
17
17
 
18
18
  log = logging.getLogger(__name__)
19
19
 
20
+ T = TypeVar("T")
21
+
20
22
 
21
- def generate_key_pair():
23
+ def generate_key_pair() -> tuple[str, str]:
22
24
  """
23
25
  This method generates a keypair and returns it as a tuple
24
26
  of (public, private) keys.
@@ -38,7 +40,8 @@ def generate_key_pair():
38
40
  return public_key, private_key
39
41
 
40
42
 
41
- def filter_by(prop_name, kwargs, objs):
43
+ def filter_by(prop_name: str, kwargs: dict[str, Any],
44
+ objs: list[T]) -> list[T]:
42
45
  """
43
46
  Utility method for filtering a list of objects by a property.
44
47
  If the given property has a non empty value in kwargs, then
@@ -60,7 +63,8 @@ def filter_by(prop_name, kwargs, objs):
60
63
  return objs
61
64
 
62
65
 
63
- def generic_find(filter_names, kwargs, objs):
66
+ def generic_find(filter_names: list[str], kwargs: dict[str, Any],
67
+ objs: list[T]) -> list[T]:
64
68
  """
65
69
  Utility method for filtering a list of objects by a list of filters.
66
70
  """
@@ -78,7 +82,7 @@ def generic_find(filter_names, kwargs, objs):
78
82
 
79
83
 
80
84
  @contextmanager
81
- def cleanup_action(cleanup_func):
85
+ def cleanup_action(cleanup_func: Callable[[], object]) -> Iterator[None]:
82
86
  """
83
87
  Context manager to carry out a given
84
88
  cleanup action after carrying out a set
@@ -109,7 +113,17 @@ def cleanup_action(cleanup_func):
109
113
  log.exception("Error during exception cleanup: ")
110
114
 
111
115
 
112
- def get_env(varname, default_value=None):
116
+ @overload
117
+ def get_env(varname: str) -> str | None:
118
+ ...
119
+
120
+
121
+ @overload
122
+ def get_env(varname: str, default_value: T) -> str | T:
123
+ ...
124
+
125
+
126
+ def get_env(varname: str, default_value: object = None) -> object:
113
127
  """
114
128
  Return the value of the environment variable or default_value.
115
129
 
@@ -125,39 +139,10 @@ def get_env(varname, default_value=None):
125
139
  return os.environ.get(varname, default_value)
126
140
 
127
141
 
128
- # Alias deprecation decorator, following:
129
- # https://stackoverflow.com/questions/49802412/
130
- # how-to-implement-deprecation-in-python-with-argument-alias
131
- def deprecated_alias(**aliases):
132
- def deco(f):
133
- @functools.wraps(f)
134
- def wrapper(*args, **kwargs):
135
- rename_kwargs(f.__name__, kwargs, aliases)
136
- return f(*args, **kwargs)
137
- return wrapper
138
- return deco
139
-
140
-
141
- def rename_kwargs(func_name, kwargs, aliases):
142
- for alias, new in aliases.items():
143
- if alias in kwargs:
144
- if new in kwargs:
145
- raise InvalidParamException(
146
- '{} received both {} and {}'.format(func_name, alias, new))
147
- # Manually invoke the deprecated decorator with an empty lambda
148
- # to signal deprecation
149
- deprecated(deprecated_in='1.1',
150
- removed_in='2.0',
151
- current_version=cloudbridge.__version__,
152
- details='{} is deprecated, use {} instead'.format(
153
- alias, new))(lambda: None)()
154
- kwargs[new] = kwargs.pop(alias)
155
-
156
-
157
142
  NON_ALPHA_NUM = re.compile(r"[^A-Za-z0-9]+")
158
143
 
159
144
 
160
- def to_resource_name(value, replace_with="-"):
145
+ def to_resource_name(value: str, replace_with: str = "-") -> str:
161
146
  """
162
147
  Converts a given string to a valid resource name by stripping
163
148
  all characters that are not alphanumeric.
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import Any
2
3
 
3
4
  from pyeventsystem.middleware import dispatch as pyevent_dispatch
4
5
  from pyeventsystem.middleware import intercept
@@ -19,12 +20,14 @@ class EventDebugLoggingMiddleware(object):
19
20
  access keys.
20
21
  """
21
22
  @observe(event_pattern="*", priority=100)
22
- def pre_log_event(self, event_args, *args, **kwargs):
23
+ def pre_log_event(self, event_args: dict[str, Any],
24
+ *args: Any, **kwargs: Any) -> None:
23
25
  log.debug("Event: {0}, args: {1} kwargs: {2}".format(
24
26
  event_args.get("event"), args, kwargs))
25
27
 
26
28
  @observe(event_pattern="*", priority=4900)
27
- def post_log_event(self, event_args, *args, **kwargs):
29
+ def post_log_event(self, event_args: dict[str, Any],
30
+ *args: Any, **kwargs: Any) -> None:
28
31
  log.debug("Event: {0}, result: {1}".format(
29
32
  event_args.get("event"), event_args.get("result")))
30
33
 
@@ -34,7 +37,8 @@ class ExceptionWrappingMiddleware(object):
34
37
  Wraps all unhandled exceptions in cloudbridge exceptions.
35
38
  """
36
39
  @intercept(event_pattern="*", priority=1050)
37
- def wrap_exception(self, event_args, *args, **kwargs):
40
+ def wrap_exception(self, event_args: dict[str, Any],
41
+ *args: Any, **kwargs: Any) -> Any:
38
42
  next_handler = event_args.pop("next_handler")
39
43
  if not next_handler:
40
44
  return
@@ -5,13 +5,17 @@ import logging
5
5
  import os
6
6
  from configparser import ConfigParser
7
7
  from os.path import expanduser
8
+ from typing import Any
9
+ from typing import cast
8
10
 
11
+ from pyeventsystem.middleware import MiddlewareManager
9
12
  from pyeventsystem.middleware import SimpleMiddlewareManager
10
13
 
11
14
  from ..base.middleware import ExceptionWrappingMiddleware
12
15
  from ..interfaces import CloudProvider
13
16
  from ..interfaces.exceptions import ProviderConnectionException
14
17
  from ..interfaces.resources import Configuration
18
+ from ..interfaces.resources import PlacementZone
15
19
 
16
20
  log = logging.getLogger(__name__)
17
21
 
@@ -28,11 +32,11 @@ CloudBridgeConfigLocations.append(UserConfigPath)
28
32
 
29
33
  class BaseConfiguration(Configuration):
30
34
 
31
- def __init__(self, user_config):
35
+ def __init__(self, user_config: dict[str, Any]) -> None:
32
36
  self.update(user_config)
33
37
 
34
38
  @property
35
- def default_result_limit(self):
39
+ def default_result_limit(self) -> int:
36
40
  """
37
41
  Get the maximum number of results to return for a
38
42
  list method
@@ -42,28 +46,30 @@ class BaseConfiguration(Configuration):
42
46
  """
43
47
  log.debug("Maximum number of results for list methods %s",
44
48
  DEFAULT_RESULT_LIMIT)
45
- return self.get('default_result_limit', DEFAULT_RESULT_LIMIT)
49
+ return cast(int, self.get('default_result_limit', DEFAULT_RESULT_LIMIT))
46
50
 
47
51
  @property
48
- def default_wait_timeout(self):
52
+ def default_wait_timeout(self) -> int:
49
53
  """
50
54
  Gets the default wait timeout for LifeCycleObjects.
51
55
  """
52
56
  log.debug("Default wait timeout for LifeCycleObjects %s",
53
57
  DEFAULT_WAIT_TIMEOUT)
54
- return self.get('default_wait_timeout', DEFAULT_WAIT_TIMEOUT)
58
+ return cast(int, self.get('default_wait_timeout',
59
+ DEFAULT_WAIT_TIMEOUT))
55
60
 
56
61
  @property
57
- def default_wait_interval(self):
62
+ def default_wait_interval(self) -> int:
58
63
  """
59
64
  Gets the default wait interval for LifeCycleObjects.
60
65
  """
61
66
  log.debug("Default wait interfal for LifeCycleObjects %s",
62
67
  DEFAULT_WAIT_INTERVAL)
63
- return self.get('default_wait_interval', DEFAULT_WAIT_INTERVAL)
68
+ return cast(int, self.get('default_wait_interval',
69
+ DEFAULT_WAIT_INTERVAL))
64
70
 
65
71
  @property
66
- def debug_mode(self):
72
+ def debug_mode(self) -> bool:
67
73
  """
68
74
  A flag indicating whether CloudBridge is in debug mode. Setting
69
75
  this to True will cause the underlying provider's debug
@@ -75,59 +81,63 @@ class BaseConfiguration(Configuration):
75
81
  :rtype: ``bool``
76
82
  :return: Whether debug mode is on.
77
83
  """
78
- return self.get('cb_debug', os.environ.get('CB_DEBUG', False))
84
+ return cast(bool, self.get('cb_debug',
85
+ os.environ.get('CB_DEBUG', False)))
79
86
 
80
87
 
81
88
  class BaseCloudProvider(CloudProvider):
82
- def __init__(self, config):
89
+
90
+ PROVIDER_ID: str
91
+
92
+ def __init__(self, config: dict[str, Any]) -> None:
83
93
  self._config = BaseConfiguration(config)
84
94
  self._config_parser = ConfigParser()
85
95
  self._config_parser.read(CloudBridgeConfigLocations)
86
96
  self._middleware = SimpleMiddlewareManager()
87
97
  self.add_required_middleware()
88
- self._region_name = None
89
- self._zone_name = None
98
+ self._region_name: str | None = None
99
+ self._zone_name: str | None = None
90
100
 
91
101
  @property
92
- def region_name(self):
102
+ def region_name(self) -> str | None:
93
103
  return self._region_name
94
104
 
95
105
  @property
96
- def zone_name(self):
106
+ def zone_name(self) -> str | None:
97
107
  if not self._zone_name:
98
108
  region = self.compute.regions.current
99
- zone = region.default_zone
109
+ zone = region.default_zone if region else None
100
110
  self._zone_name = zone.name if zone else None
101
111
  return self._zone_name
102
112
  else:
103
113
  try:
104
114
  zone_dict = ast.literal_eval(self._zone_name)
105
115
  if isinstance(zone_dict, dict):
106
- return zone_dict
116
+ return cast("str | None", zone_dict)
107
117
  except (ValueError, SyntaxError):
108
118
  pass
109
119
  return self._zone_name
110
120
 
111
121
  @property
112
- def config(self):
122
+ def config(self) -> Configuration:
113
123
  return self._config
114
124
 
115
125
  @property
116
- def name(self):
126
+ def name(self) -> str:
117
127
  return str(self.__class__.__name__)
118
128
 
119
129
  @property
120
- def middleware(self):
130
+ def middleware(self) -> MiddlewareManager:
121
131
  return self._middleware
122
132
 
123
- def add_required_middleware(self):
133
+ def add_required_middleware(self) -> None:
124
134
  """
125
135
  Adds common middleware that is essential for cloudbridge to function.
126
136
  Any other extra middleware can be added through the provider factory.
127
137
  """
128
138
  self.middleware.add(ExceptionWrappingMiddleware())
129
139
 
130
- def authenticate(self):
140
+ def authenticate(self) -> bool:
131
141
  """
132
142
  A basic implementation which simply runs a low impact command to
133
143
  check whether cloud credentials work. Providers should override with
@@ -142,7 +152,7 @@ class BaseCloudProvider(CloudProvider):
142
152
  raise ProviderConnectionException(
143
153
  "Authentication with cloud provider failed: %s" % (e,))
144
154
 
145
- def clone(self, zone=None):
155
+ def clone(self, zone: PlacementZone | None = None) -> CloudProvider:
146
156
  cloned_config = self.config.copy()
147
157
  cloned_provider = self.__class__(cloned_config)
148
158
  if zone:
@@ -150,11 +160,11 @@ class BaseCloudProvider(CloudProvider):
150
160
  cloned_provider._zone_name = zone.name
151
161
  return cloned_provider
152
162
 
153
- def _deepgetattr(self, obj, attr):
163
+ def _deepgetattr(self, obj: object, attr: str) -> Any:
154
164
  """Recurses through an attribute chain to get the ultimate value."""
155
165
  return functools.reduce(getattr, attr.split('.'), obj)
156
166
 
157
- def has_service(self, service_type):
167
+ def has_service(self, service_type: str) -> bool:
158
168
  """
159
169
  Checks whether this provider supports a given service.
160
170
 
@@ -178,7 +188,8 @@ class BaseCloudProvider(CloudProvider):
178
188
  service_type)
179
189
  return False
180
190
 
181
- def _get_config_value(self, key, default_value=None):
191
+ def _get_config_value(self, key: str,
192
+ default_value: Any = None) -> Any:
182
193
  """
183
194
  A convenience method to extract a configuration value.
184
195