diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8ecad6d4..9fdd03479 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] exclude: '.*\.(svg)$' - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements @@ -15,11 +15,11 @@ repos: files: .*\.(yaml|yml)$ exclude: '^zuul.d/.*$' - repo: https://github.com/PyCQA/doc8 - rev: v1.1.2 + rev: v2.0.0 hooks: - id: doc8 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.12.12 hooks: - id: ruff-check args: ['--fix', '--unsafe-fixes'] @@ -32,7 +32,7 @@ repos: - flake8-import-order~=0.18.2 exclude: '^(doc|releasenotes|tools)/.*$' - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.17.1 hooks: - id: mypy additional_dependencies: diff --git a/openstack/block_storage/v2/service.py b/openstack/block_storage/v2/service.py index cc091bb42..0f8c46c9b 100644 --- a/openstack/block_storage/v2/service.py +++ b/openstack/block_storage/v2/service.py @@ -10,6 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext from openstack import exceptions from openstack import resource @@ -52,9 +56,72 @@ class Service(resource.Resource): #: The date and time when the resource was updated updated_at = resource.Body('updated_at') + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: # No direct request possible, thus go directly to list + if list_base_path: + params['base_path'] = list_base_path + + # all_projects is a special case that is used by multiple services. We + # handle it here since it doesn't make sense to pass it to the .fetch + # call above + if all_projects is not None: + params['all_projects'] = all_projects + data = cls.list(session, **params) result = None diff --git a/openstack/block_storage/v3/service.py b/openstack/block_storage/v3/service.py index cefd65975..6141d3c06 100644 --- a/openstack/block_storage/v3/service.py +++ b/openstack/block_storage/v3/service.py @@ -13,7 +13,8 @@ import enum import typing as ty -from keystoneauth1 import adapter as ksa_adapter +from keystoneauth1 import adapter +import typing_extensions as ty_ext from openstack import exceptions from openstack import resource @@ -95,9 +96,72 @@ class Service(resource.Resource): # 3.32 introduced the 'set-log' action _max_microversion = '3.32' + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: # No direct request possible, thus go directly to list + if list_base_path: + params['base_path'] = list_base_path + + # all_projects is a special case that is used by multiple services. We + # handle it here since it doesn't make sense to pass it to the .fetch + # call above + if all_projects is not None: + params['all_projects'] = all_projects + data = cls.list(session, **params) result = None @@ -172,7 +236,7 @@ class Service(resource.Resource): @classmethod def set_log_levels( cls, - session: ksa_adapter.Adapter, + session: adapter.Adapter, *, level: Level, binary: Binary | None = None, @@ -207,7 +271,7 @@ class Service(resource.Resource): @classmethod def get_log_levels( cls, - session: ksa_adapter.Adapter, + session: adapter.Adapter, *, binary: Binary | None = None, server: str | None = None, diff --git a/openstack/cloud/openstackcloud.py b/openstack/cloud/openstackcloud.py index 57256e0d0..2f45e94ce 100644 --- a/openstack/cloud/openstackcloud.py +++ b/openstack/cloud/openstackcloud.py @@ -73,6 +73,13 @@ class _OpenStackCloudMixin(_services_mixin.ServicesMixin): config: cloud_region.CloudRegion + cache_enabled: bool + _cache_expirations: dict[str, int] + _cache: 'cache_region.CacheRegion' + + verify: bool | str | None + cert: str | tuple[str, str] | None + def __init__( self, cloud: str | None = None, @@ -228,7 +235,7 @@ class _OpenStackCloudMixin(_services_mixin.ServicesMixin): self.default_interface = self.config.get_interface() self.force_ipv4 = self.config.force_ipv4 - (self.verify, self.cert) = self.config.get_requests_verify_args() + self.verify, self.cert = self.config.get_requests_verify_args() # Turn off urllib3 warnings about insecure certs if we have # explicitly configured requests to tell it we do not want diff --git a/openstack/common/quota_set.py b/openstack/common/quota_set.py index ce8517010..08c7a7bef 100644 --- a/openstack/common/quota_set.py +++ b/openstack/common/quota_set.py @@ -142,7 +142,7 @@ class QuotaSet(resource.Resource): self._header.attributes.update(headers) self._header.clean() self._update_location() - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) def _prepare_request_body( self, diff --git a/openstack/compute/v2/flavor.py b/openstack/compute/v2/flavor.py index 40d7a2588..e17b57aa7 100644 --- a/openstack/compute/v2/flavor.py +++ b/openstack/compute/v2/flavor.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import exceptions from openstack import resource from openstack import utils @@ -91,11 +96,16 @@ class Flavor(resource.Resource): @classmethod def list( cls, - session, - paginated=True, - base_path='/flavors/detail', - **params, - ): + session: adapter.Adapter, + paginated: bool = True, + base_path: str | None = '/flavors/detail', + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + max_items: int | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: # Find will invoke list when name was passed. Since we want to return # flavor with details (same as direct get) we need to swap default here # and list with "/flavors" if no details explicitely requested @@ -104,7 +114,14 @@ class Flavor(resource.Resource): # Force it to string to avoid requests skipping it. params['is_public'] = 'None' return super().list( - session, paginated=paginated, base_path=base_path, **params + session, + paginated=paginated, + base_path=base_path, + allow_unknown_params=allow_unknown_params, + microversion=microversion, + headers=headers, + max_items=max_items, + **params, ) def _action(self, session, body, microversion=None): diff --git a/openstack/compute/v2/server_ip.py b/openstack/compute/v2/server_ip.py index 8bac8d10b..519a48fc6 100644 --- a/openstack/compute/v2/server_ip.py +++ b/openstack/compute/v2/server_ip.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import resource from openstack import utils @@ -34,13 +39,18 @@ class ServerIP(resource.Resource): @classmethod def list( cls, - session, - paginated=False, - server_id=None, - network_label=None, - base_path=None, - **params, - ): + session: adapter.Adapter, + paginated: bool = False, + base_path: str | None = None, + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + max_items: int | None = None, + server_id: str | None = None, + network_label: str | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: if base_path is None: base_path = cls.base_path diff --git a/openstack/compute/v2/service.py b/openstack/compute/v2/service.py index 0623fa0a7..68b0ee5bc 100644 --- a/openstack/compute/v2/service.py +++ b/openstack/compute/v2/service.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import exceptions from openstack import resource from openstack import utils @@ -55,10 +60,76 @@ class Service(resource.Resource): _max_microversion = '2.69' + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: # No direct request possible, thus go directly to list - data = cls.list(session, **params) + if list_base_path: + params['base_path'] = list_base_path + + # all_projects is a special case that is used by multiple services. We + # handle it here since it doesn't make sense to pass it to the .fetch + # call above + if all_projects is not None: + params['all_projects'] = all_projects + + data = cls.list( + session, + base_path=list_base_path, + ) result = None for maybe_result in data: diff --git a/openstack/config/cloud_region.py b/openstack/config/cloud_region.py index b7899358b..ffb58e5be 100644 --- a/openstack/config/cloud_region.py +++ b/openstack/config/cloud_region.py @@ -434,7 +434,9 @@ class CloudRegion: """Sets the Session constructor.""" self._session_constructor = session_constructor - def get_requests_verify_args(self) -> tuple[bool, str | None]: + def get_requests_verify_args( + self, + ) -> tuple[bool | str | None, str | tuple[str, str] | None]: """Return the verify and cert values for the requests library.""" insecure = self.config.get('insecure', False) verify = self.config.get('verify', True) diff --git a/openstack/dns/v2/_base.py b/openstack/dns/v2/_base.py index 16aa81079..f7c6acf6c 100644 --- a/openstack/dns/v2/_base.py +++ b/openstack/dns/v2/_base.py @@ -10,15 +10,73 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty import urllib.parse +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import exceptions from openstack import resource class Resource(resource.Resource): + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: """Find a resource by its name or id. :param session: The session to use for making this request. @@ -46,7 +104,9 @@ class Resource(resource.Resource): # Try to short-circuit by looking directly for a matching ID. try: match = cls.existing( - id=name_or_id, connection=session._get_connection(), **params + id=name_or_id, + connection=session._get_connection(), # type: ignore + **params, ) return match.fetch(session) except exceptions.SDKException: @@ -59,7 +119,13 @@ class Resource(resource.Resource): ): params['name'] = name_or_id - data = cls.list(session, **params) + data = cls.list( + session, + list_base_path=list_base_path, + microversion=microversion, + all_projects=all_projects, + **params, + ) result = cls._get_one_match(name_or_id, data) if result is not None: @@ -74,16 +140,21 @@ class Resource(resource.Resource): @classmethod def list( cls, - session, - project_id=None, - all_projects=None, - **params, - ): - headers: dict[str, str] | None = ( - {} if project_id or all_projects else None - ) - - if headers is not None: + session: adapter.Adapter, + paginated: bool = True, + base_path: str | None = None, + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + max_items: int | None = None, + project_id: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: + if project_id or all_projects is not None: + if headers is None: + headers = {} if project_id: headers["x-auth-sudo-project-id"] = str(project_id) if all_projects: diff --git a/openstack/identity/v2/extension.py b/openstack/identity/v2/extension.py index 7e47bf610..629e31d64 100644 --- a/openstack/identity/v2/extension.py +++ b/openstack/identity/v2/extension.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import resource @@ -43,7 +48,18 @@ class Extension(resource.Resource): updated_at = resource.Body('updated') @classmethod - def list(cls, session, paginated=False, base_path=None, **params): + def list( + cls, + session: adapter.Adapter, + paginated: bool = True, + base_path: str | None = None, + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + max_items: int | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: if base_path is None: base_path = cls.base_path diff --git a/openstack/identity/v3/limit.py b/openstack/identity/v3/limit.py index 0b83e1cac..891aed2b0 100644 --- a/openstack/identity/v3/limit.py +++ b/openstack/identity/v3/limit.py @@ -150,4 +150,4 @@ class Limit(resource.Resource): self._header.attributes.update(headers) self._header.clean() self._update_location() - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) diff --git a/openstack/identity/v3/registered_limit.py b/openstack/identity/v3/registered_limit.py index 4931a04f5..5a7de0cfe 100644 --- a/openstack/identity/v3/registered_limit.py +++ b/openstack/identity/v3/registered_limit.py @@ -149,4 +149,4 @@ class RegisteredLimit(resource.Resource): self._header.attributes.update(headers) self._header.clean() self._update_location() - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) diff --git a/openstack/identity/version.py b/openstack/identity/version.py index 2742e7a22..ceafc70a7 100644 --- a/openstack/identity/version.py +++ b/openstack/identity/version.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import resource @@ -27,7 +32,17 @@ class Version(resource.Resource): updated = resource.Body('updated') @classmethod - def list(cls, session, paginated=False, base_path=None, **params): + def list( + cls, + session: adapter.Adapter, + paginated: bool = True, + base_path: str | None = None, + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: if base_path is None: base_path = cls.base_path diff --git a/openstack/image/v1/image.py b/openstack/image/v1/image.py index 173e2544a..9868d0c34 100644 --- a/openstack/image/v1/image.py +++ b/openstack/image/v1/image.py @@ -10,6 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack import exceptions from openstack.image import _download from openstack import resource @@ -87,8 +92,62 @@ class Image(resource.Resource, _download.DownloadMixin): #: The timestamp when this image was last updated. updated_at = resource.Body('updated_at') + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: """Find a resource by its name or id. :param session: The session to use for making this request. @@ -117,7 +176,7 @@ class Image(resource.Resource, _download.DownloadMixin): try: match = cls.existing( id=name_or_id, - connection=session._get_connection(), + connection=session._get_connection(), # type: ignore **params, ) return match.fetch(session, **params) @@ -126,7 +185,12 @@ class Image(resource.Resource, _download.DownloadMixin): params['name'] = name_or_id - data = cls.list(session, base_path='/images/detail', **params) + data = cls.list( + session, + base_path='/images/detail', + all_projects=all_projects, + **params, + ) result = cls._get_one_match(name_or_id, data) if result is not None: diff --git a/openstack/image/v2/image.py b/openstack/image/v2/image.py index 66336e57c..914e97565 100644 --- a/openstack/image/v2/image.py +++ b/openstack/image/v2/image.py @@ -12,6 +12,9 @@ import typing as ty +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack.common import tag from openstack import exceptions from openstack.image import _download @@ -403,10 +406,72 @@ class Image(resource.Resource, tag.TagMixin, _download.DownloadMixin): return request + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: # Do a regular search first (ignoring missing) - result = super().find(session, name_or_id, True, **params) + result = super().find( + session, + name_or_id, + ignore_missing=True, + list_base_path=list_base_path, + microversion=microversion, + all_projects=all_projects, + **params, + ) if result: return result diff --git a/openstack/orchestration/v1/stack.py b/openstack/orchestration/v1/stack.py index 8e8712d35..4bd96a77e 100644 --- a/openstack/orchestration/v1/stack.py +++ b/openstack/orchestration/v1/stack.py @@ -9,6 +9,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +import typing as ty + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + from openstack.common import tag from openstack import exceptions from openstack import resource @@ -246,8 +252,62 @@ class Stack(resource.Resource): raise exceptions.NotFoundException(f"No stack found for {self.id}") return self + @ty.overload @classmethod - def find(cls, session, name_or_id, ignore_missing=True, **params): + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[True] = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: ty.Literal[False], + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self: ... + + # excuse the duplication here: it's mypy's fault + # https://github.com/python/mypy/issues/14764 + @ty.overload + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: ... + + @classmethod + def find( + cls, + session: adapter.Adapter, + name_or_id: str, + ignore_missing: bool = True, + list_base_path: str | None = None, + *, + microversion: str | None = None, + all_projects: bool | None = None, + **params: ty.Any, + ) -> ty_ext.Self | None: """Find a resource by its name or id. :param session: The session to use for making this request. @@ -275,7 +335,9 @@ class Stack(resource.Resource): # Try to short-circuit by looking directly for a matching ID. try: match = cls.existing( - id=name_or_id, connection=session._get_connection(), **params + id=name_or_id, + connection=session._get_connection(), # type: ignore + **params, ) return match.fetch(session, **params) except exceptions.NotFoundException: @@ -286,6 +348,7 @@ class Stack(resource.Resource): if ignore_missing: return None + raise exceptions.NotFoundException( f"No {cls.__name__} found for {name_or_id}" ) diff --git a/openstack/resource.py b/openstack/resource.py index 0196e6197..1d3bf2dea 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -512,7 +512,7 @@ class Resource(dict): # obj.items() ... but I think the if not obj: is short-circuiting down # in the C code and thus since we don't store the data in self[] it's # always False even if we override __len__ or __bool__. - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) @classmethod def _attributes_iterator( @@ -703,7 +703,7 @@ class Resource(dict): # obj.items() ... but I think the if not obj: is short-circuiting down # in the C code and thus since we don't store the data in self[] it's # always False even if we override __len__ or __bool__. - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) def _collect_attrs(self, attrs): """Given attributes, return a dict per type of attribute @@ -1268,7 +1268,7 @@ class Resource(dict): self._header.attributes.update(headers) self._header.clean() self._update_location() - dict.update(self, self.to_dict()) # type: ignore + dict.update(self, self.to_dict()) @classmethod def _get_session(cls, session: AdapterT) -> AdapterT: