mypy: Address issues with top-level files

Address issues in all files in the 'openstack' directory as well as the
'openstack/common', 'openstack/config' and 'openstack/test' directories.
With this done, we can start introducing mypy iteratively.

Note that we disable type hints in Sphinx. This is necessary because
Sphinx apparently can't tell the difference between 'Type' from 'typing'
and 'Type' from 'openstack.block_storage.v[23].Type', which causes a
build warning. This is okay since typing makes docs too noisy anyway.

Change-Id: Ia91c5da779b5b68c408dfc934a21d77e9ca2f550
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2023-07-25 13:15:11 +01:00
parent 3163c7597d
commit 2a8627d4f1
17 changed files with 146 additions and 88 deletions

View File

@ -60,7 +60,13 @@ add_module_names = True
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native' pygments_style = 'native'
autodoc_member_order = "bysource" autodoc_member_order = 'bysource'
# Include both the class and __init__ docstrings when describing the class
autoclass_content = 'both'
# Don't document type hints as they're too noisy
autodoc_typehints = 'none'
# Locations to exclude when looking for source files. # Locations to exclude when looking for source files.
exclude_patterns = [] exclude_patterns = []
@ -70,8 +76,7 @@ exclude_patterns = []
# Don't let openstackdocstheme insert TOCs automatically. # Don't let openstackdocstheme insert TOCs automatically.
theme_include_auto_toc = False theme_include_auto_toc = False
# Output file base name for HTML help builder. # -- Options for LaTeX output ---------------------------------------------
htmlhelp_basename = 'openstacksdkdoc'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass # (source start file, target name, title, author, documentclass
@ -91,6 +96,3 @@ latex_elements = {'maxlistdepth': 10}
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664 # Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False latex_use_xindy = False
# Include both the class and __init__ docstrings when describing the class
autoclass_content = "both"

View File

@ -9,12 +9,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack import exceptions from openstack import exceptions
from openstack import resource from openstack import resource
from openstack import utils from openstack import utils
class MetadataMixin: class MetadataMixin:
id: resource.Body
base_path: str
_body: resource._ComponentManager
#: *Type: list of tag strings* #: *Type: list of tag strings*
metadata = resource.Body('metadata', type=dict) metadata = resource.Body('metadata', type=dict)

View File

@ -9,6 +9,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import typing as ty
from openstack import exceptions from openstack import exceptions
from openstack import resource from openstack import resource
@ -88,7 +91,7 @@ class QuotaSet(resource.Resource):
body.pop("self", None) body.pop("self", None)
# Process body_attrs to strip usage and reservation out # Process body_attrs to strip usage and reservation out
normalized_attrs = dict( normalized_attrs: ty.Dict[str, ty.Any] = dict(
reservation={}, reservation={},
usage={}, usage={},
) )

View File

@ -9,12 +9,21 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack import exceptions from openstack import exceptions
from openstack import resource from openstack import resource
from openstack import utils from openstack import utils
class TagMixin: class TagMixin:
id: resource.Body
base_path: str
_body: resource._ComponentManager
@classmethod
def _get_session(cls, session):
...
_tag_query_parameters = { _tag_query_parameters = {
'tags': 'tags', 'tags': 'tags',
'any_tags': 'tags-any', 'any_tags': 'tags-any',

View File

@ -14,6 +14,7 @@
import copy import copy
import os.path import os.path
import typing as ty
import urllib import urllib
import warnings import warnings
@ -195,7 +196,7 @@ def from_conf(conf, session=None, service_types=None, **kwargs):
), ),
) )
continue continue
opt_dict = {} opt_dict: ty.Dict[str, str] = {}
# Populate opt_dict with (appropriately processed) Adapter conf opts # Populate opt_dict with (appropriately processed) Adapter conf opts
try: try:
ks_load_adap.process_conf_options(conf[project_name], opt_dict) ks_load_adap.process_conf_options(conf[project_name], opt_dict)

View File

@ -21,6 +21,7 @@ import json
import os import os
import re import re
import sys import sys
import typing as ty
import warnings import warnings
import appdirs import appdirs
@ -129,7 +130,7 @@ def _fix_argv(argv):
argv[index] = "=".join(split_args) argv[index] = "=".join(split_args)
# Save both for later so we can throw an error about dupes # Save both for later so we can throw an error about dupes
processed[new].add(orig) processed[new].add(orig)
overlap = [] overlap: ty.List[str] = []
for new, old in processed.items(): for new, old in processed.items():
if len(old) > 1: if len(old) > 1:
overlap.extend(old) overlap.extend(old)
@ -297,8 +298,8 @@ class OpenStackConfig:
self._cache_expiration_time = 0 self._cache_expiration_time = 0
self._cache_path = CACHE_PATH self._cache_path = CACHE_PATH
self._cache_class = 'dogpile.cache.null' self._cache_class = 'dogpile.cache.null'
self._cache_arguments = {} self._cache_arguments: ty.Dict[str, ty.Any] = {}
self._cache_expirations = {} self._cache_expirations: ty.Dict[str, int] = {}
self._influxdb_config = {} self._influxdb_config = {}
if 'cache' in self.cloud_config: if 'cache' in self.cloud_config:
cache_settings = _util.normalize_keys(self.cloud_config['cache']) cache_settings = _util.normalize_keys(self.cloud_config['cache'])
@ -514,8 +515,8 @@ class OpenStackConfig:
return self._expand_regions(regions) return self._expand_regions(regions)
else: else:
# crappit. we don't have a region defined. # crappit. we don't have a region defined.
new_cloud = dict() new_cloud: ty.Dict[str, ty.Any] = {}
our_cloud = self.cloud_config['clouds'].get(cloud, dict()) our_cloud = self.cloud_config['clouds'].get(cloud, {})
self._expand_vendor_profile(cloud, new_cloud, our_cloud) self._expand_vendor_profile(cloud, new_cloud, our_cloud)
if 'regions' in new_cloud and new_cloud['regions']: if 'regions' in new_cloud and new_cloud['regions']:
return self._expand_regions(new_cloud['regions']) return self._expand_regions(new_cloud['regions'])

View File

@ -15,6 +15,7 @@
import glob import glob
import json import json
import os import os
import typing as ty
import urllib import urllib
import requests import requests
@ -24,7 +25,7 @@ from openstack.config import _util
from openstack import exceptions from openstack import exceptions
_VENDORS_PATH = os.path.dirname(os.path.realpath(__file__)) _VENDORS_PATH = os.path.dirname(os.path.realpath(__file__))
_VENDOR_DEFAULTS = {} _VENDOR_DEFAULTS: ty.Dict[str, ty.Dict] = {}
_WELL_KNOWN_PATH = "{scheme}://{netloc}/.well-known/openstack/api" _WELL_KNOWN_PATH = "{scheme}://{netloc}/.well-known/openstack/api"

View File

@ -209,7 +209,7 @@ try:
import importlib.metadata as importlib_metadata import importlib.metadata as importlib_metadata
except ImportError: except ImportError:
# For everyone else # For everyone else
import importlib_metadata import importlib_metadata # type: ignore
import keystoneauth1.exceptions import keystoneauth1.exceptions
import requestsexceptions import requestsexceptions

View File

@ -18,6 +18,7 @@ Exception definitions.
import json import json
import re import re
import typing as ty
from requests import exceptions as _rex from requests import exceptions as _rex
@ -214,6 +215,7 @@ def raise_from_response(response, error_message=None):
if response.status_code < 400: if response.status_code < 400:
return return
cls: ty.Type[SDKException]
if response.status_code == 400: if response.status_code == 400:
cls = BadRequestException cls = BadRequestException
elif response.status_code == 403: elif response.status_code == 403:
@ -251,6 +253,7 @@ def raise_from_response(response, error_message=None):
message = re.sub(r'<.+?>', '', line.strip()) message = re.sub(r'<.+?>', '', line.strip())
if message not in messages: if message not in messages:
messages.append(message) messages.append(message)
# Return joined string separated by colons. # Return joined string separated by colons.
details = ': '.join(messages) details = ': '.join(messages)

View File

@ -9,10 +9,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from typing import Generic
from typing import Optional import typing as ty
from typing import Type
from typing import TypeVar
from openstack import exceptions from openstack import exceptions
from openstack.network.v2 import address_group as _address_group from openstack.network.v2 import address_group as _address_group
@ -93,11 +91,10 @@ from openstack.network.v2 import (
) )
from openstack.network.v2 import vpn_service as _vpn_service from openstack.network.v2 import vpn_service as _vpn_service
from openstack import proxy from openstack import proxy
from openstack import resource
T = TypeVar('T')
class Proxy(proxy.Proxy, Generic[T]): class Proxy(proxy.Proxy):
_resource_registry = { _resource_registry = {
"address_group": _address_group.AddressGroup, "address_group": _address_group.AddressGroup,
"address_scope": _address_scope.AddressScope, "address_scope": _address_scope.AddressScope,
@ -179,24 +176,24 @@ class Proxy(proxy.Proxy, Generic[T]):
@proxy._check_resource(strict=False) @proxy._check_resource(strict=False)
def _update( def _update(
self, self,
resource_type: Type[T], resource_type: ty.Type[resource.Resource],
value, value,
base_path=None, base_path=None,
if_revision=None, if_revision=None,
**attrs, **attrs,
) -> T: ) -> resource.Resource:
res = self._get_resource(resource_type, value, **attrs) res = self._get_resource(resource_type, value, **attrs)
return res.commit(self, base_path=base_path, if_revision=if_revision) return res.commit(self, base_path=base_path, if_revision=if_revision)
@proxy._check_resource(strict=False) @proxy._check_resource(strict=False)
def _delete( def _delete(
self, self,
resource_type: Type[T], resource_type: ty.Type[resource.Resource],
value, value,
ignore_missing=True, ignore_missing=True,
if_revision=None, if_revision=None,
**attrs, **attrs,
) -> Optional[T]: ) -> ty.Optional[resource.Resource]:
res = self._get_resource(resource_type, value, **attrs) res = self._get_resource(resource_type, value, **attrs)
try: try:

View File

@ -9,7 +9,8 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from typing import List
import typing as ty
from openstack.common import tag from openstack.common import tag
from openstack.network.v2 import _base from openstack.network.v2 import _base
@ -58,7 +59,7 @@ class Port(_base.NetworkResource, tag.TagMixin):
# Properties # Properties
#: Allowed address pairs list. Dictionary key ``ip_address`` is required #: Allowed address pairs list. Dictionary key ``ip_address`` is required
#: and key ``mac_address`` is optional. #: and key ``mac_address`` is optional.
allowed_address_pairs: List[dict] = resource.Body( allowed_address_pairs: ty.List[dict] = resource.Body(
'allowed_address_pairs', type=list 'allowed_address_pairs', type=list
) )
#: The ID of the host where the port is allocated. In some cases, #: The ID of the host where the port is allocated. In some cases,

View File

@ -11,11 +11,7 @@
# under the License. # under the License.
import functools import functools
from typing import Generator import typing as ty
from typing import Generic
from typing import Optional
from typing import Type
from typing import TypeVar
import urllib import urllib
from urllib.parse import urlparse from urllib.parse import urlparse
@ -24,7 +20,7 @@ try:
JSONDecodeError = simplejson.scanner.JSONDecodeError JSONDecodeError = simplejson.scanner.JSONDecodeError
except ImportError: except ImportError:
JSONDecodeError = ValueError JSONDecodeError = ValueError # type: ignore
import iso8601 import iso8601
import jmespath import jmespath
from keystoneauth1 import adapter from keystoneauth1 import adapter
@ -33,7 +29,8 @@ from openstack import _log
from openstack import exceptions from openstack import exceptions
from openstack import resource from openstack import resource
T = TypeVar('T')
ResourceType = ty.TypeVar('ResourceType', bound=resource.Resource)
# The _check_resource decorator is used on Proxy methods to ensure that # The _check_resource decorator is used on Proxy methods to ensure that
@ -74,7 +71,7 @@ def normalize_metric_name(name):
return name return name
class Proxy(adapter.Adapter, Generic[T]): class Proxy(adapter.Adapter):
"""Represents a service.""" """Represents a service."""
retriable_status_codes = None retriable_status_codes = None
@ -84,7 +81,7 @@ class Proxy(adapter.Adapter, Generic[T]):
``<service-type>_status_code_retries``. ``<service-type>_status_code_retries``.
""" """
_resource_registry = dict() _resource_registry: ty.Dict[str, ty.Type[resource.Resource]] = {}
"""Registry of the supported resourses. """Registry of the supported resourses.
Dictionary of resource names (key) types (value). Dictionary of resource names (key) types (value).
@ -431,7 +428,9 @@ class Proxy(adapter.Adapter, Generic[T]):
self, '_connection', getattr(self.session, '_sdk_connection', None) self, '_connection', getattr(self.session, '_sdk_connection', None)
) )
def _get_resource(self, resource_type: Type[T], value, **attrs) -> T: def _get_resource(
self, resource_type: ty.Type[ResourceType], value, **attrs
) -> ResourceType:
"""Get a resource object to work on """Get a resource object to work on
:param resource_type: The type of resource to operate on. This should :param resource_type: The type of resource to operate on. This should
@ -478,8 +477,12 @@ class Proxy(adapter.Adapter, Generic[T]):
return value return value
def _find( def _find(
self, resource_type: Type[T], name_or_id, ignore_missing=True, **attrs self,
) -> Optional[T]: resource_type: ty.Type[ResourceType],
name_or_id,
ignore_missing=True,
**attrs,
) -> ty.Optional[ResourceType]:
"""Find a resource """Find a resource
:param name_or_id: The name or ID of a resource to find. :param name_or_id: The name or ID of a resource to find.
@ -500,8 +503,12 @@ class Proxy(adapter.Adapter, Generic[T]):
@_check_resource(strict=False) @_check_resource(strict=False)
def _delete( def _delete(
self, resource_type: Type[T], value, ignore_missing=True, **attrs self,
): resource_type: ty.Type[ResourceType],
value,
ignore_missing=True,
**attrs,
) -> ty.Optional[ResourceType]:
"""Delete a resource """Delete a resource
:param resource_type: The type of resource to delete. This should :param resource_type: The type of resource to delete. This should
@ -538,8 +545,12 @@ class Proxy(adapter.Adapter, Generic[T]):
@_check_resource(strict=False) @_check_resource(strict=False)
def _update( def _update(
self, resource_type: Type[T], value, base_path=None, **attrs self,
) -> T: resource_type: ty.Type[ResourceType],
value,
base_path=None,
**attrs,
) -> ResourceType:
"""Update a resource """Update a resource
:param resource_type: The type of resource to update. :param resource_type: The type of resource to update.
@ -563,7 +574,12 @@ class Proxy(adapter.Adapter, Generic[T]):
res = self._get_resource(resource_type, value, **attrs) res = self._get_resource(resource_type, value, **attrs)
return res.commit(self, base_path=base_path) return res.commit(self, base_path=base_path)
def _create(self, resource_type: Type[T], base_path=None, **attrs): def _create(
self,
resource_type: ty.Type[ResourceType],
base_path=None,
**attrs,
) -> ResourceType:
"""Create a resource from attributes """Create a resource from attributes
:param resource_type: The type of resource to create. :param resource_type: The type of resource to create.
@ -588,8 +604,11 @@ class Proxy(adapter.Adapter, Generic[T]):
return res.create(self, base_path=base_path) return res.create(self, base_path=base_path)
def _bulk_create( def _bulk_create(
self, resource_type: Type[T], data, base_path=None self,
) -> Generator[T, None, None]: resource_type: ty.Type[ResourceType],
data,
base_path=None,
) -> ty.Generator[ResourceType, None, None]:
"""Create a resource from attributes """Create a resource from attributes
:param resource_type: The type of resource to create. :param resource_type: The type of resource to create.
@ -612,13 +631,13 @@ class Proxy(adapter.Adapter, Generic[T]):
@_check_resource(strict=False) @_check_resource(strict=False)
def _get( def _get(
self, self,
resource_type: Type[T], resource_type: ty.Type[ResourceType],
value=None, value=None,
requires_id=True, requires_id=True,
base_path=None, base_path=None,
skip_cache=False, skip_cache=False,
**attrs, **attrs,
): ) -> ResourceType:
"""Fetch a resource """Fetch a resource
:param resource_type: The type of resource to get. :param resource_type: The type of resource to get.
@ -655,12 +674,12 @@ class Proxy(adapter.Adapter, Generic[T]):
def _list( def _list(
self, self,
resource_type: Type[T], resource_type: ty.Type[ResourceType],
paginated=True, paginated=True,
base_path=None, base_path=None,
jmespath_filters=None, jmespath_filters=None,
**attrs, **attrs,
) -> Generator[T, None, None]: ) -> ty.Generator[ResourceType, None, None]:
"""List a resource """List a resource
:param resource_type: The type of resource to list. This should :param resource_type: The type of resource to list. This should
@ -696,8 +715,12 @@ class Proxy(adapter.Adapter, Generic[T]):
return data return data
def _head( def _head(
self, resource_type: Type[T], value=None, base_path=None, **attrs self,
): resource_type: ty.Type[ResourceType],
value=None,
base_path=None,
**attrs,
) -> ResourceType:
"""Retrieve a resource's header """Retrieve a resource's header
:param resource_type: The type of resource to retrieve. :param resource_type: The type of resource to retrieve.

View File

@ -32,10 +32,12 @@ converted into this Resource class' appropriate components and types
and then returned to the caller. and then returned to the caller.
""" """
import abc
import collections import collections
import inspect import inspect
import itertools import itertools
import operator import operator
import typing as ty
import urllib.parse import urllib.parse
import warnings import warnings
@ -93,11 +95,11 @@ def _convert_type(value, data_type, list_type=None):
return data_type() return data_type()
class _BaseComponent: class _BaseComponent(abc.ABC):
# The name this component is being tracked as in the Resource # The name this component is being tracked as in the Resource
key = None key: str
# The class to be used for mappings # The class to be used for mappings
_map_cls = dict _map_cls: ty.Type[ty.Mapping] = dict
#: Marks the property as deprecated. #: Marks the property as deprecated.
deprecated = False deprecated = False
@ -270,6 +272,8 @@ class Computed(_BaseComponent):
class _ComponentManager(collections.abc.MutableMapping): class _ComponentManager(collections.abc.MutableMapping):
"""Storage of a component type""" """Storage of a component type"""
attributes: ty.Dict[str, ty.Any]
def __init__(self, attributes=None, synchronized=False): def __init__(self, attributes=None, synchronized=False):
self.attributes = dict() if attributes is None else attributes.copy() self.attributes = dict() if attributes is None else attributes.copy()
self._dirty = set() if synchronized else set(self.attributes.keys()) self._dirty = set() if synchronized else set(self.attributes.keys())
@ -452,14 +456,15 @@ class Resource(dict):
# will work properly. # will work properly.
#: Singular form of key for resource. #: Singular form of key for resource.
resource_key = None resource_key: ty.Optional[str] = None
#: Plural form of key for resource. #: Plural form of key for resource.
resources_key = None resources_key: ty.Optional[str] = None
#: Key used for pagination links #: Key used for pagination links
pagination_key = None pagination_key = None
#: The ID of this resource. #: The ID of this resource.
id = Body("id") id = Body("id")
#: The name of this resource. #: The name of this resource.
name = Body("name") name = Body("name")
#: The OpenStack location of this resource. #: The OpenStack location of this resource.
@ -469,7 +474,7 @@ class Resource(dict):
_query_mapping = QueryParameters() _query_mapping = QueryParameters()
#: The base part of the URI for this resource. #: The base part of the URI for this resource.
base_path = "" base_path: str = ""
#: Allow create operation for this resource. #: Allow create operation for this resource.
allow_create = False allow_create = False
@ -508,22 +513,22 @@ class Resource(dict):
create_returns_body = None create_returns_body = None
#: Maximum microversion to use for getting/creating/updating the Resource #: Maximum microversion to use for getting/creating/updating the Resource
_max_microversion = None _max_microversion: ty.Optional[str] = None
#: API microversion (string or None) this Resource was loaded with #: API microversion (string or None) this Resource was loaded with
microversion = None microversion = None
_connection = None _connection = None
_body = None _body: _ComponentManager
_header = None _header: _ComponentManager
_uri = None _uri: _ComponentManager
_computed = None _computed: _ComponentManager
_original_body = None _original_body: ty.Dict[str, ty.Any] = {}
_store_unknown_attrs_as_properties = False _store_unknown_attrs_as_properties = False
_allow_unknown_attrs_in_body = False _allow_unknown_attrs_in_body = False
_unknown_attrs_in_body = None _unknown_attrs_in_body: ty.Dict[str, ty.Any] = {}
# Placeholder for aliases as dict of {__alias__:__original} # Placeholder for aliases as dict of {__alias__:__original}
_attr_aliases = {} _attr_aliases: ty.Dict[str, str] = {}
def __init__(self, _synchronized=False, connection=None, **attrs): def __init__(self, _synchronized=False, connection=None, **attrs):
"""The base resource """The base resource
@ -1072,12 +1077,13 @@ class Resource(dict):
:return: A dictionary of key/value pairs where keys are named :return: A dictionary of key/value pairs where keys are named
as they exist as attributes of this class. as they exist as attributes of this class.
""" """
mapping: ty.Union[utils.Munch, ty.Dict]
if _to_munch: if _to_munch:
mapping = utils.Munch() mapping = utils.Munch()
else: else:
mapping = {} mapping = {}
components = [] components: ty.List[ty.Type[_BaseComponent]] = []
if body: if body:
components.append(Body) components.append(Body)
if headers: if headers:
@ -1089,9 +1095,6 @@ class Resource(dict):
"At least one of `body`, `headers` or `computed` must be True" "At least one of `body`, `headers` or `computed` must be True"
) )
# isinstance stricly requires this to be a tuple
components = tuple(components)
if body and self._allow_unknown_attrs_in_body: if body and self._allow_unknown_attrs_in_body:
for key in self._unknown_attrs_in_body: for key in self._unknown_attrs_in_body:
converted = self._attr_to_dict( converted = self._attr_to_dict(
@ -1105,7 +1108,8 @@ class Resource(dict):
# but is slightly different in that we're looking at an instance # but is slightly different in that we're looking at an instance
# and we're mapping names on this class to their actual stored # and we're mapping names on this class to their actual stored
# values. # values.
for attr, component in self._attributes_iterator(components): # NOTE: isinstance stricly requires components to be a tuple
for attr, component in self._attributes_iterator(tuple(components)):
if original_names: if original_names:
key = component.name key = component.name
else: else:
@ -1167,6 +1171,7 @@ class Resource(dict):
*, *,
resource_request_key=None, resource_request_key=None,
): ):
body: ty.Union[ty.Dict[str, ty.Any], ty.List[ty.Any]]
if patch: if patch:
if not self._store_unknown_attrs_as_properties: if not self._store_unknown_attrs_as_properties:
# Default case # Default case
@ -1592,7 +1597,7 @@ class Resource(dict):
"Invalid create method: %s" % cls.create_method "Invalid create method: %s" % cls.create_method
) )
body = [] _body: ty.List[ty.Any] = []
resources = [] resources = []
for attrs in data: for attrs in data:
# NOTE(gryf): we need to create resource objects, since # NOTE(gryf): we need to create resource objects, since
@ -1605,9 +1610,12 @@ class Resource(dict):
request = resource._prepare_request( request = resource._prepare_request(
requires_id=requires_id, base_path=base_path requires_id=requires_id, base_path=base_path
) )
body.append(request.body) _body.append(request.body)
body: ty.Union[ty.Dict[str, ty.Any], ty.List[ty.Any]] = _body
if prepend_key: if prepend_key:
assert cls.resources_key
body = {cls.resources_key: body} body = {cls.resources_key: body}
response = method( response = method(

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import typing as ty
import warnings import warnings
import os_service_types import os_service_types
@ -44,11 +45,11 @@ class _ServiceDisabledProxyShim:
class ServiceDescription: class ServiceDescription:
#: Dictionary of supported versions and proxy classes for that version #: Dictionary of supported versions and proxy classes for that version
supported_versions = None supported_versions: ty.Dict[str, ty.Type[proxy_mod.Proxy]] = {}
#: main service_type to use to find this service in the catalog #: main service_type to use to find this service in the catalog
service_type = None service_type: str
#: list of aliases this service might be registered as #: list of aliases this service might be registered as
aliases = [] aliases: ty.List[str] = []
def __init__(self, service_type, supported_versions=None, aliases=None): def __init__(self, service_type, supported_versions=None, aliases=None):
"""Class describing how to interact with a REST service. """Class describing how to interact with a REST service.

View File

@ -67,7 +67,7 @@ def generate_fake_resource(
:raises NotImplementedError: If a resource attribute specifies a ``type`` :raises NotImplementedError: If a resource attribute specifies a ``type``
or ``list_type`` that cannot be automatically generated or ``list_type`` that cannot be automatically generated
""" """
base_attrs = dict() base_attrs: Dict[str, Any] = {}
for name, value in inspect.getmembers( for name, value in inspect.getmembers(
resource_type, resource_type,
predicate=lambda x: isinstance(x, (resource.Body, resource.URI)), predicate=lambda x: isinstance(x, (resource.Body, resource.URI)),
@ -182,7 +182,7 @@ def generate_fake_resources(
# (better) type annotations # (better) type annotations
def generate_fake_proxy( def generate_fake_proxy(
service: Type[service_description.ServiceDescription], service: Type[service_description.ServiceDescription],
api_version: Optional[int] = None, api_version: Optional[str] = None,
) -> proxy.Proxy: ) -> proxy.Proxy:
"""Generate a fake proxy for the given service type """Generate a fake proxy for the given service type
@ -246,10 +246,10 @@ def generate_fake_proxy(
) )
else: else:
api_version = list(supported_versions)[0] api_version = list(supported_versions)[0]
elif str(api_version) not in supported_versions: elif api_version not in supported_versions:
raise ValueError( raise ValueError(
f"API version {api_version} is not supported by openstacksdk. " f"API version {api_version} is not supported by openstacksdk. "
f"Supported API versions are: {', '.join(supported_versions)}" f"Supported API versions are: {', '.join(supported_versions)}"
) )
return mock.create_autospec(supported_versions[str(api_version)]) return mock.create_autospec(supported_versions[api_version])

View File

@ -13,11 +13,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from io import StringIO import io
import logging import logging
import os import os
import pprint import pprint
import sys import sys
import typing as ty
import fixtures import fixtures
from oslotest import base from oslotest import base
@ -59,8 +60,10 @@ class TestCase(base.BaseTestCase):
self.warnings = self.useFixture(os_fixtures.WarningsFixture()) self.warnings = self.useFixture(os_fixtures.WarningsFixture())
self._log_stream: ty.TextIO
if os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES: if os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES:
self._log_stream = StringIO() self._log_stream = io.StringIO()
if os.environ.get('OS_ALWAYS_LOG') in _TRUE_VALUES: if os.environ.get('OS_ALWAYS_LOG') in _TRUE_VALUES:
self.addCleanup(self.printLogs) self.addCleanup(self.printLogs)
else: else:

View File

@ -16,6 +16,7 @@ import queue
import string import string
import threading import threading
import time import time
import typing as ty
import keystoneauth1 import keystoneauth1
from keystoneauth1 import adapter as ks_adapter from keystoneauth1 import adapter as ks_adapter
@ -417,7 +418,7 @@ class TinyDAG:
def _start_traverse(self): def _start_traverse(self):
"""Initialize graph traversing""" """Initialize graph traversing"""
self._run_in_degree = self._get_in_degree() self._run_in_degree = self._get_in_degree()
self._queue = queue.Queue() self._queue: queue.Queue[str] = queue.Queue()
self._done = set() self._done = set()
self._it_cnt = len(self._graph) self._it_cnt = len(self._graph)
@ -427,8 +428,7 @@ class TinyDAG:
def _get_in_degree(self): def _get_in_degree(self):
"""Calculate the in_degree (count incoming) for nodes""" """Calculate the in_degree (count incoming) for nodes"""
_in_degree = dict() _in_degree: ty.Dict[str, int] = {u: 0 for u in self._graph.keys()}
_in_degree = {u: 0 for u in self._graph.keys()}
for u in self._graph: for u in self._graph:
for v in self._graph[u]: for v in self._graph[u]:
_in_degree[v] += 1 _in_degree[v] += 1
@ -568,7 +568,7 @@ class Munch(dict):
def munchify(x, factory=Munch): def munchify(x, factory=Munch):
"""Recursively transforms a dictionary into a Munch via copy.""" """Recursively transforms a dictionary into a Munch via copy."""
# Munchify x, using `seen` to track object cycles # Munchify x, using `seen` to track object cycles
seen = dict() seen: ty.Dict[int, ty.Any] = dict()
def munchify_cycles(obj): def munchify_cycles(obj):
try: try:
@ -608,7 +608,7 @@ def unmunchify(x):
"""Recursively converts a Munch into a dictionary.""" """Recursively converts a Munch into a dictionary."""
# Munchify x, using `seen` to track object cycles # Munchify x, using `seen` to track object cycles
seen = dict() seen: ty.Dict[int, ty.Any] = dict()
def unmunchify_cycles(obj): def unmunchify_cycles(obj):
try: try: