# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from typing import Any, ClassVar, Literal, overload
from collections.abc import Callable, Generator
import warnings

from openstack._utils import renamed_param
import openstack.exceptions as exception
from openstack.identity.v3 import (
    application_credential as _application_credential,
)
from openstack.identity.v3 import access_rule as _access_rule
from openstack.identity.v3 import credential as _credential
from openstack.identity.v3 import domain as _domain
from openstack.identity.v3 import domain_config as _domain_config
from openstack.identity.v3 import endpoint as _endpoint
from openstack.identity.v3 import federation_protocol as _federation_protocol
from openstack.identity.v3 import group as _group
from openstack.identity.v3 import identity_provider as _identity_provider
from openstack.identity.v3 import limit as _limit
from openstack.identity.v3 import mapping as _mapping
from openstack.identity.v3 import policy as _policy
from openstack.identity.v3 import project as _project
from openstack.identity.v3 import region as _region
from openstack.identity.v3 import registered_limit as _registered_limit
from openstack.identity.v3 import role as _role
from openstack.identity.v3 import role_assignment as _role_assignment
from openstack.identity.v3 import (
    role_domain_group_assignment as _role_domain_group_assignment,
)
from openstack.identity.v3 import (
    role_domain_user_assignment as _role_domain_user_assignment,
)
from openstack.identity.v3 import (
    role_project_group_assignment as _role_project_group_assignment,
)
from openstack.identity.v3 import (
    role_project_user_assignment as _role_project_user_assignment,
)
from openstack.identity.v3 import (
    role_system_group_assignment as _role_system_group_assignment,
)
from openstack.identity.v3 import (
    role_system_user_assignment as _role_system_user_assignment,
)
from openstack.identity.v3 import service as _service
from openstack.identity.v3 import service_provider as _service_provider
from openstack.identity.v3 import system as _system
from openstack.identity.v3 import token as _token
from openstack.identity.v3 import trust as _trust
from openstack.identity.v3 import user as _user
from openstack import proxy
from openstack import resource
from openstack import utils
from openstack import warnings as os_warnings


class Proxy(proxy.Proxy):
    api_version: ClassVar[Literal['3']] = '3'

    _resource_registry = {
        "application_credential": _application_credential.ApplicationCredential,  # noqa: E501
        "access_rule": _access_rule.AccessRule,
        "credential": _credential.Credential,
        "domain": _domain.Domain,
        "endpoint": _endpoint.Endpoint,
        "federation_protocol": _federation_protocol.FederationProtocol,
        "group": _group.Group,
        "identity_provider": _identity_provider.IdentityProvider,
        "limit": _limit.Limit,
        "mapping": _mapping.Mapping,
        "policy": _policy.Policy,
        "project": _project.Project,
        "region": _region.Region,
        "registered_limit": _registered_limit.RegisteredLimit,
        "role": _role.Role,
        "role_assignment": _role_assignment.RoleAssignment,
        "role_domain_group_assignment": _role_domain_group_assignment.RoleDomainGroupAssignment,  # noqa: E501
        "role_domain_user_assignment": _role_domain_user_assignment.RoleDomainUserAssignment,  # noqa: E501
        "role_project_group_assignment": _role_project_group_assignment.RoleProjectGroupAssignment,  # noqa: E501
        "role_project_user_assignment": _role_project_user_assignment.RoleProjectUserAssignment,  # noqa: E501
        "role_system_group_assignment": _role_system_group_assignment.RoleSystemGroupAssignment,  # noqa: E501
        "role_system_user_assignment": _role_system_user_assignment.RoleSystemUserAssignment,  # noqa: E501
        "service": _service.Service,
        "system": _system.System,
        "trust": _trust.Trust,
        "token": _token.Token,
        "user": _user.User,
    }

    # ========== Credentials ==========

    def create_credential(self, **attrs: Any) -> _credential.Credential:
        """Create a new credential from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.credential.Credential`,
            comprised of the properties on the Credential class.

        :returns: The results of credential creation
        """
        return self._create(_credential.Credential, **attrs)

    def delete_credential(
        self,
        credential: str | _credential.Credential,
        ignore_missing: bool = True,
    ) -> None:
        """Delete a credential

        :param credential: The value can be either the ID of a credential or a
            :class:`~openstack.identity.v3.credential.Credential` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the credential does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent credential.

        :returns: ``None``
        """
        self._delete(
            _credential.Credential, credential, ignore_missing=ignore_missing
        )

    @overload
    def find_credential(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _credential.Credential: ...

    @overload
    def find_credential(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _credential.Credential | None: ...

    def find_credential(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _credential.Credential | None:
        """Find a single credential

        :param name_or_id: The name or ID of a credential.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.credential.Credential`
            or None
        """
        return self._find(
            _credential.Credential, name_or_id, ignore_missing=ignore_missing
        )

    def get_credential(
        self, credential: str | _credential.Credential
    ) -> _credential.Credential:
        """Get a single credential

        :param credential: The value can be the ID of a credential or a
            :class:`~openstack.identity.v3.credential.Credential` instance.

        :returns: One :class:`~openstack.identity.v3.credential.Credential`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_credential.Credential, credential)

    def credentials(
        self,
        **query: Any,
    ) -> Generator[_credential.Credential, None, None]:
        """Retrieve a generator of credentials

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of credentials instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_credential.Credential, **query)

    def update_credential(
        self,
        credential: str | _credential.Credential,
        **attrs: Any,
    ) -> _credential.Credential:
        """Update a credential

        :param credential: Either the ID of a credential or a
            :class:`~openstack.identity.v3.credential.Credential` instance.
        :param attrs: The attributes to update on the credential represented
            by ``credential``.

        :returns: The updated credential
        """
        return self._update(_credential.Credential, credential, **attrs)

    # ========== Domains ==========

    def create_domain(self, **attrs: Any) -> _domain.Domain:
        """Create a new domain from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.domain.Domain`,
            comprised of the properties on the Domain class.

        :returns: The results of domain creation
        """
        return self._create(_domain.Domain, **attrs)

    def delete_domain(
        self, domain: str | _domain.Domain, ignore_missing: bool = True
    ) -> None:
        """Delete a domain

        :param domain: The value can be either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the domain does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent domain.

        :returns: ``None``
        """
        self._delete(_domain.Domain, domain, ignore_missing=ignore_missing)

    @overload
    def find_domain(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _domain.Domain: ...

    @overload
    def find_domain(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _domain.Domain | None: ...

    def find_domain(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _domain.Domain | None:
        """Find a single domain

        :param name_or_id: The name or ID of a domain.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.domain.Domain` or None
        """
        return self._find(
            _domain.Domain, name_or_id, ignore_missing=ignore_missing
        )

    def get_domain(self, domain: str | _domain.Domain) -> _domain.Domain:
        """Get a single domain

        :param domain: The value can be the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.

        :returns: One :class:`~openstack.identity.v3.domain.Domain`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_domain.Domain, domain)

    def domains(self, **query: Any) -> Generator[_domain.Domain, None, None]:
        """Retrieve a generator of domains

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of domain instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_domain.Domain, **query)

    def update_domain(
        self, domain: str | _domain.Domain, **attrs: Any
    ) -> _domain.Domain:
        """Update a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param attrs: The attributes to update on the domain represented
            by ``domain``.

        :returns: The updated domain
        """
        return self._update(_domain.Domain, domain, **attrs)

    # ========== Domain configs ==========

    def create_domain_config(
        self,
        domain: str | _domain.Domain,
        **attrs: Any,
    ) -> _domain_config.DomainConfig:
        """Create a new config for a domain from attributes.

        :param domain: The value can be the ID of a domain or
            a :class:`~openstack.identity.v3.domain.Domain` instance.
        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.domain_config.DomainConfig`
            comprised of the properties on the DomainConfig class.

        :returns: The results of domain config creation
        """
        domain_id = resource.Resource._get_id(domain)
        return self._create(
            _domain_config.DomainConfig,
            domain_id=domain_id,
            **attrs,
        )

    def delete_domain_config(
        self, domain: str | _domain.Domain, ignore_missing: bool = True
    ) -> None:
        """Delete a config for a domain

        :param domain: The value can be the ID of a domain or a
            a :class:`~openstack.identity.v3.domain.Domain` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the identity provider does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent config for a domain.

        :returns: ``None``
        """
        domain_id = resource.Resource._get_id(domain)
        self._delete(
            _domain_config.DomainConfig,
            None,
            domain_id=domain_id,
            ignore_missing=ignore_missing,
        )

    def get_domain_config(
        self, domain: str | _domain.Domain
    ) -> _domain_config.DomainConfig:
        """Get a single config for a domain

        :param domain_id: The value can be the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.

        :returns: One
            :class:`~openstack.identity.v3.domain_config.DomainConfig`
        :raises: :class:`~openstack.exceptions.NotFoundException` when no
             resource can be found.
        """
        domain_id = resource.Resource._get_id(domain)
        return self._get(
            _domain_config.DomainConfig,
            domain_id=domain_id,
            requires_id=False,
        )

    def update_domain_config(
        self, domain: str | _domain.Domain, **attrs: Any
    ) -> _domain_config.DomainConfig:
        """Update a config for a domain

        :param domain_id: The value can be the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param attrs: The attributes to update on the config for a domain
            represented by ``domain_id``.

        :returns: The updated config for a domain
        """
        domain_id = resource.Resource._get_id(domain)
        return self._update(
            _domain_config.DomainConfig,
            None,
            domain_id=domain_id,
            **attrs,
        )

    # ========== Endpoints ==========

    def create_endpoint(self, **attrs: Any) -> _endpoint.Endpoint:
        """Create a new endpoint from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.endpoint.Endpoint`,
            comprised of the properties on the Endpoint class.

        :returns: The results of endpoint creation
        """
        return self._create(_endpoint.Endpoint, **attrs)

    def delete_endpoint(
        self, endpoint: str | _endpoint.Endpoint, ignore_missing: bool = True
    ) -> None:
        """Delete an endpoint

        :param endpoint: The value can be either the ID of an endpoint or a
            :class:`~openstack.identity.v3.endpoint.Endpoint` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the endpoint does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent endpoint.

        :returns: ``None``
        """
        self._delete(
            _endpoint.Endpoint, endpoint, ignore_missing=ignore_missing
        )

    @overload
    def find_endpoint(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _endpoint.Endpoint: ...

    @overload
    def find_endpoint(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _endpoint.Endpoint | None: ...

    def find_endpoint(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _endpoint.Endpoint | None:
        """Find a single endpoint

        :param name_or_id: The name or ID of a endpoint.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.endpoint.Endpoint` or None
        """
        return self._find(
            _endpoint.Endpoint, name_or_id, ignore_missing=ignore_missing
        )

    # TODO(stephenfin): This conflicts with Adapter.get_endpoint
    def get_endpoint(  # type: ignore[override]
        self, endpoint: str | _endpoint.Endpoint
    ) -> _endpoint.Endpoint:
        """Get a single endpoint

        :param endpoint: The value can be the ID of an endpoint or a
            :class:`~openstack.identity.v3.endpoint.Endpoint`
            instance.

        :returns: One :class:`~openstack.identity.v3.endpoint.Endpoint`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_endpoint.Endpoint, endpoint)

    def endpoints(
        self,
        **query: Any,
    ) -> Generator[_endpoint.Endpoint, None, None]:
        """Retrieve a generator of endpoints

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of endpoint instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_endpoint.Endpoint, **query)

    def update_endpoint(
        self, endpoint: str | _endpoint.Endpoint, **attrs: Any
    ) -> _endpoint.Endpoint:
        """Update a endpoint

        :param endpoint: Either the ID of an endpoint or a
            :class:`~openstack.identity.v3.endpoint.Endpoint` instance.
        :param attrs: The attributes to update on the endpoint represented
            by ``endpoint``.

        :returns: The updated endpoint
        """
        return self._update(_endpoint.Endpoint, endpoint, **attrs)

    # ========== Project endpoints ==========

    def project_endpoints(
        self,
        project: str | _project.Project,
        **query: Any,
    ) -> Generator[_endpoint.ProjectEndpoint, None, None]:
        """Retrieve a generator of endpoints which are associated with the
        project.

        :param project: Either the project ID or an instance of
            :class:`~openstack.identity.v3.project.Project`
        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of endpoint instances.
        """
        project_id = self._get_resource(_project.Project, project).id
        return self._list(
            _endpoint.ProjectEndpoint, project_id=project_id, **query
        )

    def associate_endpoint_with_project(
        self,
        project: str | _project.Project,
        endpoint: str | _endpoint.Endpoint,
    ) -> None:
        """Creates a direct association between project and endpoint

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project` instance.
        :param endpoint: Either the ID of an endpoint or a
            :class:`~openstack.identity.v3.endpoint.Endpoint` instance.
        :returns: None
        """
        project = self._get_resource(_project.Project, project)
        endpoint = self._get_resource(_endpoint.Endpoint, endpoint)
        project.associate_endpoint(self, endpoint.id)

    def disassociate_endpoint_from_project(
        self,
        project: str | _project.Project,
        endpoint: str | _endpoint.Endpoint,
    ) -> None:
        """Removes a direct association between project and endpoint

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project` instance.
        :param endpoint: Either the ID of an endpoint or a
            :class:`~openstack.identity.v3.endpoint.Endpoint` instance.
        :returns: None
        """
        project = self._get_resource(_project.Project, project)
        endpoint = self._get_resource(_endpoint.Endpoint, endpoint)
        project.disassociate_endpoint(self, endpoint.id)

    # ========== Groups ==========

    def create_group(self, **attrs: Any) -> _group.Group:
        """Create a new group from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.group.Group`,
            comprised of the properties on the Group class.

        :returns: The results of group creation
        """
        return self._create(_group.Group, **attrs)

    def delete_group(
        self, group: str | _group.Group, ignore_missing: bool = True
    ) -> None:
        """Delete a group

        :param group: The value can be either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the group does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent group.

        :returns: ``None``
        """
        self._delete(_group.Group, group, ignore_missing=ignore_missing)

    @overload
    def find_group(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
        **query: Any,
    ) -> _group.Group: ...

    @overload
    def find_group(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _group.Group | None: ...

    def find_group(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _group.Group | None:
        """Find a single group

        :param name_or_id: The name or ID of a group.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.group.Group` or None
        """
        return self._find(
            _group.Group,
            name_or_id,
            ignore_missing=ignore_missing,
            **query,
        )

    def get_group(self, group: str | _group.Group) -> _group.Group:
        """Get a single group

        :param group: The value can be the ID of a group or a
            :class:`~openstack.identity.v3.group.Group`
            instance.

        :returns: One :class:`~openstack.identity.v3.group.Group`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_group.Group, group)

    def groups(self, **query: Any) -> Generator[_group.Group, None, None]:
        """Retrieve a generator of groups

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of group instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_group.Group, **query)

    def update_group(
        self, group: str | _group.Group, **attrs: Any
    ) -> _group.Group:
        """Update a group

        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param attrs: The attributes to update on the group represented
            by ``group``.

        :returns: The updated group
        """
        return self._update(_group.Group, group, **attrs)

    def add_user_to_group(
        self,
        user: str | _user.User,
        group: str | _group.Group,
    ) -> None:
        """Add user to group

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        group = self._get_resource(_group.Group, group)
        group.add_user(self, user)

    def remove_user_from_group(
        self,
        user: str | _user.User,
        group: str | _group.Group,
    ) -> None:
        """Remove user to group

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        group = self._get_resource(_group.Group, group)
        group.remove_user(self, user)

    def check_user_in_group(
        self,
        user: str | _user.User,
        group: str | _group.Group,
    ) -> bool:
        """Check whether user belongsto group

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :returns: A boolean representing current relation
        """
        user = self._get_resource(_user.User, user)
        group = self._get_resource(_group.Group, group)
        return group.check_user(self, user)

    def group_users(
        self,
        group: str | _group.Group,
        **attrs: Any,
    ) -> Generator[_user.User, None, None]:
        """List users in a group

        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param attrs: Only password_expires_at can be filter for result.

        :returns: List of :class:`~openstack.identity.v3.user.User`
        """
        group = self._get_resource(_group.Group, group)
        base_path = utils.urljoin(group.base_path, group.id, 'users')
        users = self._list(_user.User, base_path=base_path, **attrs)
        return users

    # ========== Policies ==========

    def create_policy(self, **attrs: Any) -> _policy.Policy:
        """Create a new policy from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.policy.Policy`,
            comprised of the properties on the Policy class.

        :returns: The results of policy creation
        """
        return self._create(_policy.Policy, **attrs)

    def delete_policy(
        self, policy: str | _policy.Policy, ignore_missing: bool = True
    ) -> None:
        """Delete a policy

        :param policy: The value can be either the ID of a policy or a
            :class:`~openstack.identity.v3.policy.Policy` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the policy does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent policy.

        :returns: ``None``
        """
        self._delete(_policy.Policy, policy, ignore_missing=ignore_missing)

    @overload
    def find_policy(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _policy.Policy: ...

    @overload
    def find_policy(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _policy.Policy | None: ...

    def find_policy(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _policy.Policy | None:
        """Find a single policy

        :param name_or_id: The name or ID of a policy.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.policy.Policy` or None
        """
        warnings.warn(
            "find_policy is deprecated and will be removed in a future "
            "release; please use get_policy instead.",
            os_warnings.RemovedInSDK60Warning,
        )
        return self._find(
            _policy.Policy, name_or_id, ignore_missing=ignore_missing
        )

    def get_policy(self, policy: str | _policy.Policy) -> _policy.Policy:
        """Get a single policy

        :param policy: The value can be the ID of a policy or a
            :class:`~openstack.identity.v3.policy.Policy` instance.

        :returns: One :class:`~openstack.identity.v3.policy.Policy`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_policy.Policy, policy)

    def policies(self, **query: Any) -> Generator[_policy.Policy, None, None]:
        """Retrieve a generator of policies

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of policy instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_policy.Policy, **query)

    def update_policy(
        self, policy: str | _policy.Policy, **attrs: Any
    ) -> _policy.Policy:
        """Update a policy

        :param policy: Either the ID of a policy or a
            :class:`~openstack.identity.v3.policy.Policy` instance.
        :param attrs: The attributes to update on the policy represented
            by ``policy``.

        :returns: The updated policy
        """
        return self._update(_policy.Policy, policy, **attrs)

    # ========== Project ==========

    def create_project(self, **attrs: Any) -> _project.Project:
        """Create a new project from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.project.Project`,
            comprised of the properties on the Project class.

        :returns: The results of project creation
        """
        return self._create(_project.Project, **attrs)

    def delete_project(
        self, project: str | _project.Project, ignore_missing: bool = True
    ) -> None:
        """Delete a project

        :param project: The value can be either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the project does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent project.

        :returns: ``None``
        """
        self._delete(_project.Project, project, ignore_missing=ignore_missing)

    @overload
    def find_project(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
        **query: Any,
    ) -> _project.Project: ...

    @overload
    def find_project(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _project.Project | None: ...

    def find_project(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _project.Project | None:
        """Find a single project

        :param name_or_id: The name or ID of a project.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.project.Project` or None
        """
        return self._find(
            _project.Project,
            name_or_id,
            ignore_missing=ignore_missing,
            **query,
        )

    def get_project(self, project: str | _project.Project) -> _project.Project:
        """Get a single project

        :param project: The value can be the ID of a project or a
            :class:`~openstack.identity.v3.project.Project` instance.

        :returns: One :class:`~openstack.identity.v3.project.Project`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_project.Project, project)

    def projects(
        self,
        **query: Any,
    ) -> Generator[_project.Project, None, None]:
        """Retrieve a generator of projects

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of project instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_project.Project, **query)

    def user_projects(
        self,
        user: str | _user.User,
        **query: Any,
    ) -> Generator[_project.UserProject, None, None]:
        """Retrieve a generator of projects to which the user has authorization
           to access.

        :param user: Either the user id or an instance of
            :class:`~openstack.identity.v3.user.User`
        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of project instances.
        """
        user = self._get_resource(_user.User, user)
        return self._list(_project.UserProject, user_id=user.id, **query)

    def endpoint_projects(
        self,
        endpoint: str | _endpoint.Endpoint,
        **query: Any,
    ) -> Generator[_project.EndpointProject, None, None]:
        """Retrieve a generator of projects which are associated with the
        endpoint.

        :param endpoint: Either the endpoint ID or an instance of
            :class:`~openstack.identity.v3.endpoint.Endpoint`
        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of project instances.
        """
        endpoint_id = self._get_resource(_endpoint.Endpoint, endpoint).id
        return self._list(
            _project.EndpointProject, endpoint_id=endpoint_id, **query
        )

    def update_project(
        self, project: str | _project.Project, **attrs: Any
    ) -> _project.Project:
        """Update a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project` instance.
        :param attrs: The attributes to update on the project represented
            by ``project``.

        :returns: The updated project
        """
        return self._update(_project.Project, project, **attrs)

    # ========== Services ==========

    def create_service(self, **attrs: Any) -> _service.Service:
        """Create a new service from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.service.Service`,
            comprised of the properties on the Service class.

        :returns: The results of service creation
        """
        return self._create(_service.Service, **attrs)

    def delete_service(
        self, service: str | _service.Service, ignore_missing: bool = True
    ) -> None:
        """Delete a service

        :param service: The value can be either the ID of a service or a
            :class:`~openstack.identity.v3.service.Service` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the service does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent service.

        :returns: ``None``
        """
        self._delete(_service.Service, service, ignore_missing=ignore_missing)

    @overload
    def find_service(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _service.Service: ...

    @overload
    def find_service(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _service.Service | None: ...

    def find_service(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _service.Service | None:
        """Find a single service

        :param name_or_id: The name or ID of a service.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.service.Service` or None
        """
        return self._find(
            _service.Service, name_or_id, ignore_missing=ignore_missing
        )

    def get_service(self, service: str | _service.Service) -> _service.Service:
        """Get a single service

        :param service: The value can be the ID of a service or a
            :class:`~openstack.identity.v3.service.Service` instance.

        :returns: One :class:`~openstack.identity.v3.service.Service`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_service.Service, service)

    def services(
        self,
        **query: Any,
    ) -> Generator[_service.Service, None, None]:
        """Retrieve a generator of services

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of service instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_service.Service, **query)

    def update_service(
        self, service: str | _service.Service, **attrs: Any
    ) -> _service.Service:
        """Update a service

        :param service: Either the ID of a service or a
            :class:`~openstack.identity.v3.service.Service` instance.
        :param attrs: The attributes to update on the service represented
            by ``service``.

        :returns: The updated service
        """
        return self._update(_service.Service, service, **attrs)

    # ========== Users ==========

    def create_user(self, **attrs: Any) -> _user.User:
        """Create a new user from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.user.User`,
            comprised of the properties on the User class.

        :returns: The results of user creation
        """
        return self._create(_user.User, **attrs)

    def delete_user(
        self, user: str | _user.User, ignore_missing: bool = True
    ) -> None:
        """Delete a user

        :param user: The value can be either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the user does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent user.

        :returns: ``None``
        """
        self._delete(_user.User, user, ignore_missing=ignore_missing)

    @overload
    def find_user(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
        **query: Any,
    ) -> _user.User: ...

    @overload
    def find_user(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _user.User | None: ...

    def find_user(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _user.User | None:
        """Find a single user

        :param name_or_id: The name or ID of a user.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.user.User` or None
        """
        return self._find(
            _user.User,
            name_or_id,
            ignore_missing=ignore_missing,
            **query,
        )

    def get_user(self, user: str | _user.User) -> _user.User:
        """Get a single user

        :param user: The value can be the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.

        :returns: One :class:`~openstack.identity.v3.user.User`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_user.User, user)

    def user_groups(
        self,
        user: str | _user.User,
    ) -> Generator[_group.UserGroup, None, None]:
        """List groups a user is in

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance

        :returns: List of :class:`~openstack.identity.v3.group.group`
        """
        user_id = self._get_resource(_user.User, user).id
        groups = self._list(_group.UserGroup, user_id=user_id)
        return groups

    def users(self, **query: Any) -> Generator[_user.User, None, None]:
        """Retrieve a generator of users

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of user instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_user.User, **query)

    def update_user(self, user: str | _user.User, **attrs: Any) -> _user.User:
        """Update a user

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param attrs: The attributes to update on the user represented
            by ``attrs``.

        :returns: The updated user
        """
        return self._update(_user.User, user, **attrs)

    def update_password(
        self,
        user: str | _user.User,
        current_password: str,
        password: str,
    ) -> None:
        """Update the password for the user the token belongs to.

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param current_password: The user's current password.
        :param password: The user's new password.
        :returns: ``None``
        """
        user_obj = self._get_resource(_user.User, user)
        user_obj.update_password(self, current_password, password)

    # ========== Tokens ==========

    def validate_token(
        self, token: str, nocatalog: bool = False, allow_expired: bool = False
    ) -> _token.Token:
        """Validate a token

        :param token: The token to validate.
        :param nocatalog: Whether the returned token should not include a
            catalog.
        :param allow_expired: Whether to allow expired tokens.

        :returns: A :class:`~openstack.identity.v3.token.Token`.
        """
        return _token.Token.validate(
            self, token, nocatalog=nocatalog, allow_expired=allow_expired
        )

    def check_token(self, token: str, allow_expired: bool = False) -> bool:
        """Check if a token is valid.

        :param token: The token to check.
        :param allow_expired: Whether to allow expired tokens.

        :returns: True if valid, else False.
        """
        return _token.Token.check(self, token, allow_expired=allow_expired)

    def revoke_token(self, token: str) -> None:
        """Revoke a token.

        :param token: The token to revoke.

        :returns: None
        """
        _token.Token.revoke(self, token)

    # ========== Trusts ==========

    def create_trust(self, **attrs: Any) -> _trust.Trust:
        """Create a new trust from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.trust.Trust`,
            comprised of the properties on the Trust class.

        :returns: The results of trust creation
        """
        return self._create(_trust.Trust, **attrs)

    def delete_trust(
        self, trust: str | _trust.Trust, ignore_missing: bool = True
    ) -> None:
        """Delete a trust

        :param trust: The value can be either the ID of a trust or a
            :class:`~openstack.identity.v3.trust.Trust` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the credential does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent credential.

        :returns: ``None``
        """
        self._delete(_trust.Trust, trust, ignore_missing=ignore_missing)

    @overload
    def find_trust(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _trust.Trust: ...

    @overload
    def find_trust(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _trust.Trust | None: ...

    def find_trust(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _trust.Trust | None:
        """Find a single trust

        :param name_or_id: The name or ID of a trust.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.trust.Trust` or None
        """
        return self._find(
            _trust.Trust, name_or_id, ignore_missing=ignore_missing
        )

    def get_trust(self, trust: str | _trust.Trust) -> _trust.Trust:
        """Get a single trust

        :param trust: The value can be the ID of a trust or a
            :class:`~openstack.identity.v3.trust.Trust` instance.

        :returns: One :class:`~openstack.identity.v3.trust.Trust`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_trust.Trust, trust)

    def trusts(self, **query: Any) -> Generator[_trust.Trust, None, None]:
        """Retrieve a generator of trusts

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of trust instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_trust.Trust, **query)

    # ========== Regions ==========

    def create_region(self, **attrs: Any) -> _region.Region:
        """Create a new region from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.region.Region`,
            comprised of the properties on the Region class.

        :returns: The results of region creation.
        """
        return self._create(_region.Region, **attrs)

    def delete_region(
        self, region: str | _region.Region, ignore_missing: bool = True
    ) -> None:
        """Delete a region

        :param region: The value can be either the ID of a region or a
            :class:`~openstack.identity.v3.region.Region` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the region does not exist.
            When set to ``True``, no exception will be thrown when
            attempting to delete a nonexistent region.

        :returns: ``None``
        """
        self._delete(_region.Region, region, ignore_missing=ignore_missing)

    @overload
    def find_region(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _region.Region: ...

    @overload
    def find_region(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _region.Region | None: ...

    def find_region(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _region.Region | None:
        """Find a single region

        :param name_or_id: The name or ID of a region.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the region does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent region.
        :returns: One :class:`~openstack.identity.v3.region.Region` or None
        """
        warnings.warn(
            "find_region is deprecated and will be removed in a future "
            "release; please use get_region instead.",
            os_warnings.RemovedInSDK60Warning,
        )
        return self._find(
            _region.Region, name_or_id, ignore_missing=ignore_missing
        )

    def get_region(self, region: str | _region.Region) -> _region.Region:
        """Get a single region

        :param region: The value can be the ID of a region or a
            :class:`~openstack.identity.v3.region.Region` instance.

        :returns: One :class:`~openstack.identity.v3.region.Region`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no matching region can be found.
        """
        return self._get(_region.Region, region)

    def regions(self, **query: Any) -> Generator[_region.Region, None, None]:
        """Retrieve a generator of regions

        :param query: Optional query parameters to be sent to limit
            the regions being returned.

        :returns: A generator of region instances.
        """
        # TODO(briancurtin): This is paginated but requires base list changes.
        return self._list(_region.Region, **query)

    def update_region(
        self, region: str | _region.Region, **attrs: Any
    ) -> _region.Region:
        """Update a region

        :param region: Either the ID of a region or a
            :class:`~openstack.identity.v3.region.Region` instance.
        :param attrs: The attributes to update on the region represented
            by ``region``.

        :returns: The updated region.
        """
        return self._update(_region.Region, region, **attrs)

    # ========== Roles ==========

    def create_role(self, **attrs: Any) -> _role.Role:
        """Create a new role from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.role.Role`,
            comprised of the properties on the Role class.

        :returns: The results of role creation.
        """
        return self._create(_role.Role, **attrs)

    def delete_role(
        self, role: str | _role.Role, ignore_missing: bool = True
    ) -> None:
        """Delete a role

        :param role: The value can be either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the role does not exist.
            When set to ``True``, no exception will be thrown when
            attempting to delete a nonexistent role.

        :returns: ``None``
        """
        self._delete(_role.Role, role, ignore_missing=ignore_missing)

    @overload
    def find_role(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
        **query: Any,
    ) -> _role.Role: ...

    @overload
    def find_role(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _role.Role | None: ...

    def find_role(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _role.Role | None:
        """Find a single role

        :param name_or_id: The name or ID of a role.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the role does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent role.
        :returns: One :class:`~openstack.identity.v3.role.Role` or None
        """
        return self._find(
            _role.Role,
            name_or_id,
            ignore_missing=ignore_missing,
            **query,
        )

    def get_role(self, role: str | _role.Role) -> _role.Role:
        """Get a single role

        :param role: The value can be the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.

        :returns: One :class:`~openstack.identity.v3.role.Role`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no matching role can be found.
        """
        return self._get(_role.Role, role)

    def roles(self, **query: Any) -> Generator[_role.Role, None, None]:
        """Retrieve a generator of roles

        :param query: Optional query parameters to be sent to limit
            the resources being returned. The options
            are: domain_id, name.
        :returns: A generator of role instances.
        """
        return self._list(_role.Role, **query)

    def update_role(self, role: str | _role.Role, **attrs: Any) -> _role.Role:
        """Update a role

        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param kwargs: The attributes to update on the role represented
            by ``value``. Only name can be updated

        :returns: The updated role.
        """
        return self._update(_role.Role, role, **attrs)

    # ========== Role assignments ==========

    def role_assignments_filter(
        self,
        domain: str | _domain.Domain | None = None,
        project: str | _project.Project | None = None,
        system: str | None = None,
        group: str | _group.Group | None = None,
        user: str | _user.User | None = None,
    ) -> Generator[resource.Resource, None, None]:
        """Retrieve a generator of roles assigned to user/group

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param system: Either the system name or a
            :class:`~openstack.identity.v3.system.System`
            instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :returns: A generator of role instances.
        """
        if domain and project and system:
            raise exception.InvalidRequest(
                'Only one of domain, project, or system can be specified'
            )

        if domain is None and project is None and system is None:
            raise exception.InvalidRequest(
                'Either domain, project, or system should be specified'
            )

        if group and user:
            raise exception.InvalidRequest(
                'Only one of group or user can be specified'
            )

        if group is None and user is None:
            raise exception.InvalidRequest(
                'Either group or user should be specified'
            )

        if domain:
            domain_id = resource.Resource._get_id(domain)
            if group:
                group_id = resource.Resource._get_id(group)
                return self._list(
                    _role_domain_group_assignment.RoleDomainGroupAssignment,
                    domain_id=domain_id,
                    group_id=group_id,
                )
            else:
                assert user is not None
                user_id = resource.Resource._get_id(user)
                return self._list(
                    _role_domain_user_assignment.RoleDomainUserAssignment,
                    domain_id=domain_id,
                    user_id=user_id,
                )
        elif project:
            project_id = resource.Resource._get_id(project)
            if group:
                group_id = resource.Resource._get_id(group)
                return self._list(
                    _role_project_group_assignment.RoleProjectGroupAssignment,
                    project_id=project_id,
                    group_id=group_id,
                )
            else:
                assert user is not None
                user_id = resource.Resource._get_id(user)
                return self._list(
                    _role_project_user_assignment.RoleProjectUserAssignment,
                    project_id=project_id,
                    user_id=user_id,
                )
        else:
            assert system is not None
            system_id = resource.Resource._get_id(system)
            if group:
                group_id = resource.Resource._get_id(group)
                return self._list(
                    _role_system_group_assignment.RoleSystemGroupAssignment,
                    system_id=system_id,
                    group_id=group_id,
                )
            else:
                assert user is not None
                user_id = resource.Resource._get_id(user)
                return self._list(
                    _role_system_user_assignment.RoleSystemUserAssignment,
                    system_id=system_id,
                    user_id=user_id,
                )

    def role_assignments(
        self,
        **query: Any,
    ) -> Generator[_role_assignment.RoleAssignment, None, None]:
        """Retrieve a generator of role assignments

        :param query: Optional query parameters to be sent to limit
            the resources being returned. The options
            are: group_id, role_id, scope_domain_id,
            scope_project_id, inherited_to, user_id, include_names,
            include_subtree.
        :returns:
            :class:`~openstack.identity.v3.role_assignment.RoleAssignment`
        """
        return self._list(_role_assignment.RoleAssignment, **query)

    def assign_domain_role_to_user(
        self,
        domain: str | _domain.Domain,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Assign role to user on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        domain = self._get_resource(_domain.Domain, domain)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        domain.assign_role_to_user(self, user, role, inherited)

    def unassign_domain_role_from_user(
        self,
        domain: str | _domain.Domain,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Unassign role from user on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        domain = self._get_resource(_domain.Domain, domain)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        domain.unassign_role_from_user(self, user, role, inherited)

    def validate_user_has_domain_role(
        self,
        domain: str | _domain.Domain,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> bool:
        """Validates that a user has a role on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :returns: True if user has role in domain
        """
        domain = self._get_resource(_domain.Domain, domain)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        return domain.validate_user_has_role(self, user, role, inherited)

    def assign_domain_role_to_group(
        self,
        domain: str | _domain.Domain,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Assign role to group on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        domain = self._get_resource(_domain.Domain, domain)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        domain.assign_role_to_group(self, group, role, inherited)

    def unassign_domain_role_from_group(
        self,
        domain: str | _domain.Domain,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Unassign role from group on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        domain = self._get_resource(_domain.Domain, domain)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        domain.unassign_role_from_group(self, group, role, inherited)

    def validate_group_has_domain_role(
        self,
        domain: str | _domain.Domain,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> bool:
        """Validates that a group has a role on a domain

        :param domain: Either the ID of a domain or a
            :class:`~openstack.identity.v3.domain.Domain` instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :returns: True if group has role on domain
        """
        domain = self._get_resource(_domain.Domain, domain)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        return domain.validate_group_has_role(self, group, role, inherited)

    def assign_project_role_to_user(
        self,
        project: str | _project.Project,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Assign role to user on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        project = self._get_resource(_project.Project, project)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        project.assign_role_to_user(self, user, role, inherited)

    def unassign_project_role_from_user(
        self,
        project: str | _project.Project,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Unassign role from user on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        project = self._get_resource(_project.Project, project)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        project.unassign_role_from_user(self, user, role, inherited)

    def validate_user_has_project_role(
        self,
        project: str | _project.Project,
        user: str | _user.User,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> bool:
        """Validates that a user has a role on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :returns: True if user has role in project
        """
        project = self._get_resource(_project.Project, project)
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        return project.validate_user_has_role(self, user, role, inherited)

    def assign_project_role_to_group(
        self,
        project: str | _project.Project,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Assign role to group on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        project = self._get_resource(_project.Project, project)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        project.assign_role_to_group(self, group, role, inherited)

    def unassign_project_role_from_group(
        self,
        project: str | _project.Project,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> None:
        """Unassign role from group on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param inherited: Whether the role assignment is inherited.
        :returns: ``None``
        """
        project = self._get_resource(_project.Project, project)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        project.unassign_role_from_group(self, group, role, inherited)

    def validate_group_has_project_role(
        self,
        project: str | _project.Project,
        group: str | _group.Group,
        role: str | _role.Role,
        *,
        inherited: bool = False,
    ) -> bool:
        """Validates that a group has a role on a project

        :param project: Either the ID of a project or a
            :class:`~openstack.identity.v3.project.Project`
            instance.
        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :returns: True if group has role in project
        """
        project = self._get_resource(_project.Project, project)
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        return project.validate_group_has_role(self, group, role, inherited)

    def assign_system_role_to_user(
        self,
        user: str | _user.User,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> None:
        """Assign a role to user on a system

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        system.assign_role_to_user(self, user, role)

    def unassign_system_role_from_user(
        self,
        user: str | _user.User,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> None:
        """Unassign a role from user on a system

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        system.unassign_role_from_user(self, user, role)

    def validate_user_has_system_role(
        self,
        user: str | _user.User,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> bool:
        """Validates that a user has a role on a system

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: True if user has role in system
        """
        user = self._get_resource(_user.User, user)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        return system.validate_user_has_role(self, user, role)

    def assign_system_role_to_group(
        self,
        group: str | _group.Group,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> None:
        """Assign a role to group on a system

        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: ``None``
        """
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        system.assign_role_to_group(self, group, role)

    def unassign_system_role_from_group(
        self,
        group: str | _group.Group,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> None:
        """Unassign a role from group on a system

        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: ``None``
        """
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        system.unassign_role_from_group(self, group, role)

    def validate_group_has_system_role(
        self,
        group: str | _group.Group,
        role: str | _role.Role,
        system: str | _system.System,
    ) -> bool:
        """Validates that a group has a role on a system

        :param group: Either the ID of a group or a
            :class:`~openstack.identity.v3.group.Group` instance.
        :param role: Either the ID of a role or a
            :class:`~openstack.identity.v3.role.Role` instance.
        :param system: The system name
        :returns: True if group has role on system
        """
        group = self._get_resource(_group.Group, group)
        role = self._get_resource(_role.Role, role)
        system = self._get_resource(_system.System, system)
        return system.validate_group_has_role(self, group, role)

    # ========== Registered limits ==========

    def registered_limits(
        self,
        **query: Any,
    ) -> Generator[_registered_limit.RegisteredLimit, None, None]:
        """Retrieve a generator of registered_limits

        :param query: Optional query parameters to be sent to limit
            the registered_limits being returned.

        :returns: A generator of registered_limits instances.
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`
        """
        return self._list(_registered_limit.RegisteredLimit, **query)

    def get_registered_limit(
        self, registered_limit: str | _registered_limit.RegisteredLimit
    ) -> _registered_limit.RegisteredLimit:
        """Get a single registered_limit

        :param registered_limit: The value can be the ID of a registered_limit
            or a
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`
            instance.

        :returns: One
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_registered_limit.RegisteredLimit, registered_limit)

    def create_registered_limit(
        self, **attrs: Any
    ) -> _registered_limit.RegisteredLimit:
        """Create a new registered_limit from attributes

        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`,
            comprised of the properties on the RegisteredLimit class.

        :returns: The results of registered_limit creation.
        """
        return self._create(_registered_limit.RegisteredLimit, **attrs)

    def update_registered_limit(
        self,
        registered_limit: str | _registered_limit.RegisteredLimit,
        **attrs: Any,
    ) -> _registered_limit.RegisteredLimit:
        """Update a registered_limit

        :param registered_limit: Either the ID of a registered_limit. or a
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`
            instance.
        :param kwargs: The attributes to update on the registered_limit
            represented by ``value``.

        :returns: The updated registered_limit.
        """
        return self._update(
            _registered_limit.RegisteredLimit, registered_limit, **attrs
        )

    def delete_registered_limit(
        self,
        registered_limit: str | _registered_limit.RegisteredLimit,
        ignore_missing: bool = True,
    ) -> None:
        """Delete a registered_limit

        :param registered_limit: The value can be either the ID of a
            registered_limit or a
            :class:`~openstack.identity.v3.registered_limit.RegisteredLimit`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the registered_limit does not exist. When set to ``True``, no
            exception will be thrown when attempting to delete a nonexistent
            registered_limit.

        :returns: ``None``
        """
        self._delete(
            _registered_limit.RegisteredLimit,
            registered_limit,
            ignore_missing=ignore_missing,
        )

    # ========== Limits ==========

    def limits(self, **query: Any) -> Generator[_limit.Limit, None, None]:
        """Retrieve a generator of limits

        :param query: Optional query parameters to be sent to limit
            the limits being returned.

        :returns: A generator of limits instances.
        """
        return self._list(_limit.Limit, **query)

    def get_limit(self, limit: str | _limit.Limit) -> _limit.Limit:
        """Get a single limit

        :param limit: The value can be the ID of a limit
            or a :class:`~openstack.identity.v3.limit.Limit` instance.

        :returns: One :class:`~openstack.identity.v3.limit.Limit`
        :raises: :class:`~openstack.exceptions.NotFoundException` when no
            resource can be found.
        """
        return self._get(_limit.Limit, limit)

    def create_limit(self, **attrs: Any) -> _limit.Limit:
        """Create a new limit from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.limit.Limit`, comprised of the
            properties on the Limit class.

        :returns: The results of limit creation.
        """
        return self._create(_limit.Limit, **attrs)

    def update_limit(
        self, limit: str | _limit.Limit, **attrs: Any
    ) -> _limit.Limit:
        """Update a limit

        :param limit: Either the ID of a limit. or a
            :class:`~openstack.identity.v3.limit.Limit` instance.
        :param kwargs: The attributes to update on the limit represented
            by ``value``.

        :returns: The updated limit.
        """
        return self._update(_limit.Limit, limit, **attrs)

    def delete_limit(
        self, limit: str | _limit.Limit, ignore_missing: bool = True
    ) -> None:
        """Delete a limit

        :param limit: The value can be either the ID of a limit or a
            :class:`~openstack.identity.v3.limit.Limit` instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the limit does not exist. When set to ``True``, no exception
            will be thrown when attempting to delete a nonexistent limit.

        :returns: ``None``
        """
        self._delete(_limit.Limit, limit, ignore_missing=ignore_missing)

    # ========== Application credentials ==========

    def application_credentials(
        self,
        user: str | _user.User,
        **query: Any,
    ) -> Generator[_application_credential.ApplicationCredential, None, None]:
        """Retrieve a generator of application credentials

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.

        :param query: Optional query parameters to be sent to
            limit the resources being returned.

        :returns: A generator of application credentials instances.
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`
        """
        user = self._get_resource(_user.User, user)
        return self._list(
            _application_credential.ApplicationCredential,
            user_id=user.id,
            **query,
        )

    def get_application_credential(
        self,
        user: str | _user.User,
        application_credential: str
        | _application_credential.ApplicationCredential,
    ) -> _application_credential.ApplicationCredential:
        """Get a single application credential

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.

        :param application_credential: The value can be the ID of a
            application credential or a
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`
            instance.

        :returns: One
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`
        :raises: :class:`~openstack.exceptions.NotFoundException` when no
            resource can be found.
        """
        user = self._get_resource(_user.User, user)
        return self._get(
            _application_credential.ApplicationCredential,
            application_credential,
            user_id=user.id,
        )

    def create_application_credential(
        self,
        user: str | _user.User,
        name: str,
        **attrs: Any,
    ) -> _application_credential.ApplicationCredential:
        """Create a new application credential from attributes

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param name: The name of the application credential which is
            unique to the user.
        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`,
            comprised of the properties on the ApplicationCredential class.


        :returns: The results of application credential creation.
        """

        user = self._get_resource(_user.User, user)
        return self._create(
            _application_credential.ApplicationCredential,
            name=name,
            user_id=user.id,
            **attrs,
        )

    @overload
    def find_application_credential(
        self,
        user: str | _user.User,
        name_or_id: str,
        ignore_missing: Literal[False],
        **query: Any,
    ) -> _application_credential.ApplicationCredential: ...

    @overload
    def find_application_credential(
        self,
        user: str | _user.User,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _application_credential.ApplicationCredential | None: ...

    def find_application_credential(
        self,
        user: str | _user.User,
        name_or_id: str,
        ignore_missing: bool = True,
        **query: Any,
    ) -> _application_credential.ApplicationCredential | None:
        """Find a single application credential

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param name_or_id: The name or ID of an application credential.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.

        :returns: One
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`
            or None
        """
        user = self._get_resource(_user.User, user)
        return self._find(
            _application_credential.ApplicationCredential,
            user_id=user.id,
            name_or_id=name_or_id,
            ignore_missing=ignore_missing,
            **query,
        )

    def delete_application_credential(
        self,
        user: str | _user.User,
        application_credential: str
        | _application_credential.ApplicationCredential,
        ignore_missing: bool = True,
    ) -> None:
        """Delete an application credential

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param application_credential: The value can be either the ID of an
            application credential or a
            :class:`~openstack.identity.v3.application_credential.ApplicationCredential`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the application credential does not exist. When set to
            ``True``, no exception will be thrown when attempting to delete
            a nonexistent application credential.

        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        self._delete(
            _application_credential.ApplicationCredential,
            application_credential,
            user_id=user.id,
            ignore_missing=ignore_missing,
        )

    # ========== Federation protocols ==========

    @renamed_param('idp_id', 'idp')
    def create_federation_protocol(
        self, idp: str | _identity_provider.IdentityProvider, **attrs: Any
    ) -> _federation_protocol.FederationProtocol:
        """Create a new federation protocol from attributes

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is to be attached
            to.
        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`,
            comprised of the properties on the
            FederationProtocol class.

        :returns: The results of federation protocol creation
        """
        return self._create(
            _federation_protocol.FederationProtocol,
            idp_id=resource.Resource._get_id(idp),
            **attrs,
        )

    @renamed_param('idp_id', 'idp')
    def delete_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider | None,
        protocol: str | _federation_protocol.FederationProtocol,
        ignore_missing: bool = True,
    ) -> None:
        """Delete a federation protocol

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is attached to. Can
            be None if protocol is a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.
        :param protocol: The ID of a federation protocol or a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the federation protocol does not exist.  When set to
            ``True``, no exception will be set when attempting to delete a
            nonexistent federation protocol.

        :returns: ``None``
        """
        cls = _federation_protocol.FederationProtocol
        if idp:
            idp_id = resource.Resource._get_id(idp)
        elif isinstance(protocol, cls):
            idp_id = protocol.idp_id
        self._delete(
            cls, protocol, ignore_missing=ignore_missing, idp_id=idp_id
        )

    @overload
    def find_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider,
        protocol: str,
        ignore_missing: Literal[False],
    ) -> _federation_protocol.FederationProtocol: ...

    @overload
    def find_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider,
        protocol: str,
        ignore_missing: bool = True,
    ) -> _federation_protocol.FederationProtocol | None: ...

    @renamed_param('idp_id', 'idp')
    def find_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider,
        protocol: str,
        ignore_missing: bool = True,
    ) -> _federation_protocol.FederationProtocol | None:
        """Find a single federation protocol

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is attached to.
        :param protocol: The name or ID of a federation protocol.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the resource does not exist.  When set to ``True``, None will
            be returned when attempting to find a nonexistent resource.
        :returns: One federation protocol or None
        """
        return self._find(
            _federation_protocol.FederationProtocol,
            protocol,
            ignore_missing=ignore_missing,
            idp_id=resource.Resource._get_id(idp),
        )

    @renamed_param('idp_id', 'idp')
    def get_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider | None,
        protocol: str | _federation_protocol.FederationProtocol,
    ) -> _federation_protocol.FederationProtocol:
        """Get a single federation protocol

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is attached to. Can
            be None if protocol is a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.
        :param protocol: The value can be the ID of a federation protocol or a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.

        :returns: One federation protocol
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        if idp:
            idp_id = resource.Resource._get_id(idp)
        elif isinstance(protocol, _federation_protocol.FederationProtocol):
            idp_id = protocol.idp_id
        return self._get(
            _federation_protocol.FederationProtocol, protocol, idp_id=idp_id
        )

    @renamed_param('idp_id', 'idp')
    def federation_protocols(
        self,
        idp: str,
        **query: Any,
    ) -> Generator[_federation_protocol.FederationProtocol, None, None]:
        """Retrieve a generator of federation protocols

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is attached to.
        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of federation protocol instances.
        """
        return self._list(
            _federation_protocol.FederationProtocol,
            idp_id=resource.Resource._get_id(idp),
            **query,
        )

    @renamed_param('idp_id', 'idp')
    def update_federation_protocol(
        self,
        idp: str | _identity_provider.IdentityProvider | None,
        protocol: str | _federation_protocol.FederationProtocol,
        **attrs: Any,
    ) -> _federation_protocol.FederationProtocol:
        """Update a federation protocol

        :param idp: The ID or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance of the identity provider the protocol is attached to. Can
            be None if protocol is a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.
        :param protocol: Either the ID of a federation protocol or a
            :class:`~openstack.identity.v3.federation_protocol.FederationProtocol`
            instance.
        :param attrs: The attributes to update on the federation protocol
            represented by ``protocol``.

        :returns: The updated federation protocol
        """
        if idp:
            idp_id = resource.Resource._get_id(idp)
        elif isinstance(protocol, _federation_protocol.FederationProtocol):
            idp_id = protocol.idp_id
        return self._update(
            _federation_protocol.FederationProtocol,
            protocol,
            idp_id=idp_id,
            **attrs,
        )

    # ========== Mappings ==========

    def create_mapping(self, **attrs: Any) -> _mapping.Mapping:
        """Create a new mapping from attributes

        :param attrs: Keyword arguments which will be used to create
            a :class:`~openstack.identity.v3.mapping.Mapping`,
            comprised of the properties on the Mapping class.

        :returns: The results of mapping creation
        """
        return self._create(_mapping.Mapping, **attrs)

    def delete_mapping(
        self, mapping: str | _mapping.Mapping, ignore_missing: bool = True
    ) -> None:
        """Delete a mapping

        :param mapping: The ID of a mapping or a
            :class:`~openstack.identity.v3.mapping.Mapping`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the mapping does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent mapping.

        :returns: ``None``
        """
        self._delete(_mapping.Mapping, mapping, ignore_missing=ignore_missing)

    @overload
    def find_mapping(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _mapping.Mapping: ...

    @overload
    def find_mapping(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _mapping.Mapping | None: ...

    def find_mapping(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _mapping.Mapping | None:
        """Find a single mapping

        :param name_or_id: The name or ID of a mapping.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: One :class:`~openstack.identity.v3.mapping.Mapping` or None
        """
        warnings.warn(
            "find_mapping is deprecated and will be removed in a future "
            "release; please use get_mapping instead.",
            os_warnings.RemovedInSDK60Warning,
        )
        return self._find(
            _mapping.Mapping, name_or_id, ignore_missing=ignore_missing
        )

    def get_mapping(self, mapping: str | _mapping.Mapping) -> _mapping.Mapping:
        """Get a single mapping

        :param mapping: The value can be the ID of a mapping or a
            :class:`~openstack.identity.v3.mapping.Mapping`
            instance.

        :returns: One :class:`~openstack.identity.v3.mapping.Mapping`
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_mapping.Mapping, mapping)

    def mappings(
        self,
        **query: Any,
    ) -> Generator[_mapping.Mapping, None, None]:
        """Retrieve a generator of mappings

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of mapping instances.
        """
        return self._list(_mapping.Mapping, **query)

    def update_mapping(
        self, mapping: str | _mapping.Mapping, **attrs: Any
    ) -> _mapping.Mapping:
        """Update a mapping

        :param mapping: Either the ID of a mapping or a
            :class:`~openstack.identity.v3.mapping.Mapping` instance.
        :param attrs: The attributes to update on the mapping represented
            by ``mapping``.

        :returns: The updated mapping
        """
        return self._update(_mapping.Mapping, mapping, **attrs)

    # ========== Identity providers ==========

    def create_identity_provider(
        self, **attrs: Any
    ) -> _identity_provider.IdentityProvider:
        """Create a new identity provider from attributes

        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            comprised of the properties on the IdentityProvider class.

        :returns: The results of identity provider creation
        """
        return self._create(_identity_provider.IdentityProvider, **attrs)

    def delete_identity_provider(
        self,
        identity_provider: str | _identity_provider.IdentityProvider,
        ignore_missing: bool = True,
    ) -> None:
        """Delete an identity provider

        :param identity_provider: The ID of an identity provider or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the identity provider does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent identity provider.

        :returns: ``None``
        """
        self._delete(
            _identity_provider.IdentityProvider,
            identity_provider,
            ignore_missing=ignore_missing,
        )

    @overload
    def find_identity_provider(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _identity_provider.IdentityProvider: ...

    @overload
    def find_identity_provider(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _identity_provider.IdentityProvider | None: ...

    def find_identity_provider(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _identity_provider.IdentityProvider | None:
        """Find a single identity provider

        :param name_or_id: The name or ID of an identity provider
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the resource does not exist.
            When set to ``True``, None will be returned when
            attempting to find a nonexistent resource.
        :returns: The details of an identity provider or None.
        """
        warnings.warn(
            "find_identity_provider is deprecated and will be removed in a "
            "future release; please use get_identity_provider instead.",
            os_warnings.RemovedInSDK60Warning,
        )
        return self._find(
            _identity_provider.IdentityProvider,
            name_or_id,
            ignore_missing=ignore_missing,
        )

    def get_identity_provider(
        self, identity_provider: str | _identity_provider.IdentityProvider
    ) -> _identity_provider.IdentityProvider:
        """Get a single mapping

        :param mapping: The value can be the ID of an identity provider or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance.

        :returns: The details of an identity provider.
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(
            _identity_provider.IdentityProvider, identity_provider
        )

    def identity_providers(
        self,
        **query: Any,
    ) -> Generator[_identity_provider.IdentityProvider, None, None]:
        """Retrieve a generator of identity providers

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of identity provider instances.
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
        """
        return self._list(_identity_provider.IdentityProvider, **query)

    def update_identity_provider(
        self,
        identity_provider: str | _identity_provider.IdentityProvider,
        **attrs: Any,
    ) -> _identity_provider.IdentityProvider:
        """Update a mapping

        :param mapping: Either the ID of an identity provider or a
            :class:`~openstack.identity.v3.identity_provider.IdentityProvider`
            instance.
        :param attrs: The attributes to update on the identity_provider
            represented by ``identity_provider``.

        :returns: The updated identity provider.
        """
        return self._update(
            _identity_provider.IdentityProvider, identity_provider, **attrs
        )

    # ========== Access rules ==========

    def access_rules(
        self,
        user: str | _user.User,
        **query: Any,
    ) -> Generator[_access_rule.AccessRule, None, None]:
        """Retrieve a generator of access rules

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param query: Optional query parameters to be sent to
            limit the resources being returned.

        :returns: A generator of access rules instances.
        """
        user = self._get_resource(_user.User, user)
        return self._list(_access_rule.AccessRule, user_id=user.id, **query)

    def get_access_rule(
        self,
        user: str | _user.User,
        access_rule: str | _access_rule.AccessRule,
    ) -> _access_rule.AccessRule:
        """Get a single access rule

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param access_rule: The value can be the ID of an access rule or a
            :class:`~.access_rule.AccessRule` instance.

        :returns: One :class:`~.access_rule.AccessRule`
        :raises: :class:`~openstack.exceptions.NotFoundException` when no
            resource can be found.
        """
        user = self._get_resource(_user.User, user)
        return self._get(_access_rule.AccessRule, access_rule, user_id=user.id)

    def delete_access_rule(
        self,
        user: str | _user.User,
        access_rule: str | _access_rule.AccessRule,
        ignore_missing: bool = True,
    ) -> None:
        """Delete an access rule

        :param user: Either the ID of a user or a
            :class:`~openstack.identity.v3.user.User` instance.
        :param access_rule: The value can be either the ID of an access rule
            or a :class:`~openstack.identity.v3.access_rule.AccessRule`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the access rule does not exist. When set to ``True``, no
            exception will be thrown when attempting to delete a nonexistent
            access rule.

        :returns: ``None``
        """
        user = self._get_resource(_user.User, user)
        self._delete(
            _access_rule.AccessRule,
            access_rule,
            user_id=user.id,
            ignore_missing=ignore_missing,
        )

    # ========== Service providers ==========

    def create_service_provider(
        self, **attrs: Any
    ) -> _service_provider.ServiceProvider:
        """Create a new service provider from attributes

        :param attrs: Keyword arguments which will be used to create a
            :class:`~openstack.identity.v3.service_provider.ServiceProvider`,
            comprised of the properties on the ServiceProvider class.

        :returns: The results of service provider creation
        """
        return self._create(_service_provider.ServiceProvider, **attrs)

    def delete_service_provider(
        self,
        service_provider: str | _service_provider.ServiceProvider,
        ignore_missing: bool = True,
    ) -> None:
        """Delete a service provider

        :param service_provider: The ID of a service provider or a
            :class:`~openstack.identity.v3.service_provider.ServiceProvider`
            instance.
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be
            raised when the service provider does not exist.
            When set to ``True``, no exception will be set when
            attempting to delete a nonexistent service provider.

        :returns: ``None``
        """
        self._delete(
            _service_provider.ServiceProvider,
            service_provider,
            ignore_missing=ignore_missing,
        )

    @overload
    def find_service_provider(
        self,
        name_or_id: str,
        ignore_missing: Literal[False],
    ) -> _service_provider.ServiceProvider: ...

    @overload
    def find_service_provider(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _service_provider.ServiceProvider | None: ...

    def find_service_provider(
        self,
        name_or_id: str,
        ignore_missing: bool = True,
    ) -> _service_provider.ServiceProvider | None:
        """Find a single service provider

        :param name_or_id: The name or ID of a service provider
        :param ignore_missing: When set to ``False``
            :class:`~openstack.exceptions.NotFoundException` will be raised
            when the resource does not exist. When set to ``True``, None will
            be returned when attempting to find a nonexistent resource.

        :returns: The details of an service provider or None.
        """
        return self._find(
            _service_provider.ServiceProvider,
            name_or_id,
            ignore_missing=ignore_missing,
        )

    def get_service_provider(
        self, service_provider: str | _service_provider.ServiceProvider
    ) -> _service_provider.ServiceProvider:
        """Get a single service provider

        :param service_provider: The value can be the ID of a service provider
            or a
            :class:`~openstack.identity.v3.server_provider.ServiceProvider`
            instance.

        :returns: The details of an service provider.
        :raises: :class:`~openstack.exceptions.NotFoundException`
            when no resource can be found.
        """
        return self._get(_service_provider.ServiceProvider, service_provider)

    def service_providers(
        self,
        **query: Any,
    ) -> Generator[_service_provider.ServiceProvider, None, None]:
        """Retrieve a generator of service providers

        :param query: Optional query parameters to be sent to limit
            the resources being returned.

        :returns: A generator of service provider instances.
            :class:`~openstack.identity.v3.service_provider.ServiceProvider`
        """
        return self._list(_service_provider.ServiceProvider, **query)

    def update_service_provider(
        self,
        service_provider: str | _service_provider.ServiceProvider,
        **attrs: Any,
    ) -> _service_provider.ServiceProvider:
        """Update a service provider

        :param service_provider: Either the ID of an service provider or a
            :class:`~openstack.identity.v3.service_provider.ServiceProvider`
            instance.
        :param attrs: The attributes to update on the service provider
            represented by ``service_provider``.

        :returns: The updated service provider.
        """
        return self._update(
            _service_provider.ServiceProvider, service_provider, **attrs
        )

    # ========== Utilities ==========

    def wait_for_status(
        self,
        res: resource.ResourceT,
        status: str,
        failures: list[str] | None = None,
        interval: int | float | None = 2,
        wait: int | None = None,
        attribute: str = 'status',
        callback: Callable[[int], None] | None = None,
    ) -> resource.ResourceT:
        """Wait for the resource to be in a particular status.

        :param session: The session to use for making this request.
        :param resource: The resource to wait on to reach the status. The
            resource must have a status attribute specified via ``attribute``.
        :param status: Desired status of the resource.
        :param failures: Statuses that would indicate the transition
            failed such as 'ERROR'. Defaults to ['ERROR'].
        :param interval: Number of seconds to wait between checks.
        :param wait: Maximum number of seconds to wait for transition.
            Set to ``None`` to wait forever.
        :param attribute: Name of the resource attribute that contains the
            status.
        :param callback: A callback function. This will be called with a single
            value, progress. This is API specific but is generally a percentage
            value from 0-100.

        :returns: The updated resource.
        :raises: :class:`~openstack.exceptions.ResourceTimeout` if the
            transition to status failed to occur in ``wait`` seconds.
        :raises: :class:`~openstack.exceptions.ResourceFailure` if the resource
            transitioned to one of the states in ``failures``.
        :raises: :class:`~AttributeError` if the resource does not have a
            ``status`` attribute
        """
        return resource.wait_for_status(
            self, res, status, failures, interval, wait, attribute, callback
        )

    def wait_for_delete(
        self,
        res: resource.ResourceT,
        interval: int | float | None = 2,
        wait: int | None = 120,
        callback: Callable[[int], None] | None = None,
    ) -> resource.ResourceT:
        """Wait for a resource to be deleted.

        :param res: The resource to wait on to be deleted.
        :param interval: Number of seconds to wait before to consecutive
            checks.
        :param wait: Maximum number of seconds to wait before the change.
        :param callback: A callback function. This will be called with a single
            value, progress, which is a percentage value from 0-100.

        :returns: The resource is returned on success.
        :raises: :class:`~openstack.exceptions.ResourceTimeout` if transition
            to delete failed to occur in the specified seconds.
        """
        return resource.wait_for_delete(self, res, interval, wait, callback)
