diff --git a/openstack/message/v2/_base.py b/openstack/message/v2/_base.py new file mode 100644 index 000000000..018bcbfe9 --- /dev/null +++ b/openstack/message/v2/_base.py @@ -0,0 +1,129 @@ +# 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. + +import typing as ty +import uuid + +from keystoneauth1 import adapter +import typing_extensions as ty_ext + +from openstack import resource + + +class MessageResource(resource.Resource): + # FIXME(anyone): The name string of `location` field of Zaqar API response + # is lower case. That is inconsistent with the guide from API-WG. This is + # a workaround for this issue. + location = resource.Header("location") + + #: The ID to identify the client accessing Zaqar API. Must be specified + #: in header for each API request. + client_id = resource.Header("Client-ID") + #: The ID to identify the project. Must be provided when keystone + #: authentication is not enabled in Zaqar service. + project_id = resource.Header("X-PROJECT-ID") + + @classmethod + def list( + cls, + session: adapter.Adapter, + paginated: bool = True, + base_path: str | None = None, + allow_unknown_params: bool = False, + *, + microversion: str | None = None, + headers: dict[str, str] | None = None, + max_items: int | None = None, + **params: ty.Any, + ) -> ty.Generator[ty_ext.Self, None, None]: + """This method is a generator which yields resource objects. + + This is almost the copy of list method of resource.Resource class. + The only difference is the request header now includes `Client-ID` + and `X-PROJECT-ID` fields which are required by Zaqar v2 API. + """ + more_data = True + + if base_path is None: + base_path = cls.base_path + + uri = base_path % params + + project_id = params.get('project_id', None) or session.get_project_id() + assert project_id is not None + + headers = { + "Client-ID": params.get('client_id', None) or str(uuid.uuid4()), + "X-PROJECT-ID": project_id, + } + + query_params = cls._query_mapping._transpose(params, cls) + while more_data: + resp = session.get( + uri, headers=headers, params=query_params + ).json()[cls.resources_key] + + if not resp: + more_data = False + + yielded = 0 + new_marker = None + for data in resp: + value = cls.existing(**data) + new_marker = value.id + yielded += 1 + yield value + + if not paginated: + return + if "limit" in query_params and yielded < query_params["limit"]: + return + query_params["limit"] = yielded + query_params["marker"] = new_marker + + def fetch( + self, + session, + requires_id=True, + base_path=None, + error_message=None, + skip_cache=False, + **kwargs, + ): + request = self._prepare_request( + requires_id=requires_id, base_path=base_path + ) + headers = { + "Client-ID": self.client_id or str(uuid.uuid4()), + "X-PROJECT-ID": self.project_id or session.get_project_id(), + } + request.headers.update(headers) + response = session.get( + request.url, headers=headers, skip_cache=skip_cache + ) + self._translate_response(response) + + return self + + def delete( + self, session, error_message=None, *, microversion=None, **kwargs + ): + request = self._prepare_request() + headers = { + "Client-ID": self.client_id or str(uuid.uuid4()), + "X-PROJECT-ID": self.project_id or session.get_project_id(), + } + request.headers.update(headers) + response = session.delete(request.url, headers=headers) + + self._translate_response(response, has_body=False) + return self diff --git a/openstack/message/v2/claim.py b/openstack/message/v2/claim.py index 2aedf57ef..0ee2fbf72 100644 --- a/openstack/message/v2/claim.py +++ b/openstack/message/v2/claim.py @@ -12,15 +12,11 @@ import uuid +from openstack.message.v2 import _base from openstack import resource -class Claim(resource.Resource): - # FIXME(anyone): The name string of `location` field of Zaqar API response - # is lower case. That is inconsistent with the guide from API-WG. This is - # a workaround for this issue. - location = resource.Header("location") - +class Claim(_base.MessageResource): resources_key = 'claims' base_path = '/queues/%(queue_name)s/claims' @@ -48,12 +44,6 @@ class Claim(resource.Resource): ttl = resource.Body("ttl") #: The name of queue to claim message from. queue_name = resource.URI("queue_name") - #: The ID to identify the client accessing Zaqar API. Must be specified - #: in header for each API request. - client_id = resource.Header("Client-ID") - #: The ID to identify the project. Must be provided when keystone - #: authentication is not enabled in Zaqar service. - project_id = resource.Header("X-PROJECT-ID") def _translate_response( self, @@ -63,6 +53,12 @@ class Claim(resource.Resource): *, resource_response_key=None, ): + # For case no message was claimed successfully, 204 No Content + # message will be returned. In other cases, we translate response + # body which has `messages` field(list) included. + if response.status_code == 204: + return + super()._translate_response( response, has_body, @@ -94,31 +90,6 @@ class Claim(resource.Resource): return self - def fetch( - self, - session, - requires_id=True, - base_path=None, - error_message=None, - skip_cache=False, - **kwargs, - ): - request = self._prepare_request( - requires_id=requires_id, base_path=base_path - ) - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - - request.headers.update(headers) - response = session.get( - request.url, headers=request.headers, skip_cache=False - ) - self._translate_response(response) - - return self - def commit( self, session, @@ -140,16 +111,3 @@ class Claim(resource.Resource): session.patch(request.url, json=request.body, headers=request.headers) return self - - def delete(self, session, *args, **kwargs): - request = self._prepare_request() - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - - request.headers.update(headers) - response = session.delete(request.url, headers=request.headers) - - self._translate_response(response, has_body=False) - return self diff --git a/openstack/message/v2/message.py b/openstack/message/v2/message.py index 325a08c52..091aefdc7 100644 --- a/openstack/message/v2/message.py +++ b/openstack/message/v2/message.py @@ -12,15 +12,11 @@ import uuid +from openstack.message.v2 import _base from openstack import resource -class Message(resource.Resource): - # FIXME(anyone): The name string of `location` field of Zaqar API response - # is lower case. That is inconsistent with the guide from API-WG. This is - # a workaround for this issue. - location = resource.Header("location") - +class Message(_base.MessageResource): resources_key = 'messages' base_path = '/queues/%(queue_name)s/messages' @@ -46,12 +42,6 @@ class Message(resource.Resource): ttl = resource.Body("ttl") #: The name of target queue message is post to or got from. queue_name = resource.URI("queue_name") - #: The ID to identify the client accessing Zaqar API. Must be specified - #: in header for each API request. - client_id = resource.Header("Client-ID") - #: The ID to identify the project accessing Zaqar API. Must be specified - #: in case keystone auth is not enabled in Zaqar service. - project_id = resource.Header("X-PROJECT-ID") # FIXME(stephenfin): This is actually a query arg but we need it for # deletions and resource.delete doesn't respect these currently @@ -71,72 +61,24 @@ class Message(resource.Resource): return response.json()['resources'] - @classmethod - def list(cls, session, paginated=True, base_path=None, **params): - """This method is a generator which yields message objects. - - This is almost the copy of list method of resource.Resource class. - The only difference is the request header now includes `Client-ID` - and `X-PROJECT-ID` fields which are required by Zaqar v2 API. - """ - more_data = True - - if base_path is None: - base_path = cls.base_path - - uri = base_path % params - headers = { - "Client-ID": params.get('client_id', None) or str(uuid.uuid4()), - "X-PROJECT-ID": params.get('project_id', None) - or session.get_project_id(), - } - - query_params = cls._query_mapping._transpose(params, cls) - while more_data: - resp = session.get(uri, headers=headers, params=query_params) - resp = resp.json() - resp = resp[cls.resources_key] - - if not resp: - more_data = False - - yielded = 0 - new_marker = None - for data in resp: - value = cls.existing(**data) - new_marker = value.id - yielded += 1 - yield value - - if not paginated: - return - if "limit" in query_params and yielded < query_params["limit"]: - return - query_params["limit"] = yielded - query_params["marker"] = new_marker - - def fetch( - self, - session, - requires_id=True, - base_path=None, - error_message=None, - skip_cache=False, - **kwargs, - ): + def create(self, session, prepend_key=False, base_path=None, **kwargs): request = self._prepare_request( - requires_id=requires_id, base_path=base_path + requires_id=False, prepend_key=prepend_key, base_path=base_path ) headers = { "Client-ID": self.client_id or str(uuid.uuid4()), "X-PROJECT-ID": self.project_id or session.get_project_id(), } - request.headers.update(headers) - response = session.get( - request.url, headers=headers, skip_cache=skip_cache + response = session.post( + request.url, json=request.body, headers=request.headers ) - self._translate_response(response) + + # For case no message was claimed successfully, 204 No Content + # message will be returned. In other cases, we translate response + # body which has `messages` field(list) included. + if response.status_code != 204: + self._translate_response(response) return self diff --git a/openstack/message/v2/queue.py b/openstack/message/v2/queue.py index 42b0b00c5..9e23e33cc 100644 --- a/openstack/message/v2/queue.py +++ b/openstack/message/v2/queue.py @@ -12,15 +12,11 @@ import uuid +from openstack.message.v2 import _base from openstack import resource -class Queue(resource.Resource): - # FIXME(anyone): The name string of `location` field of Zaqar API response - # is lower case. That is inconsistent with the guide from API-WG. This is - # a workaround for this issue. - location = resource.Header("location") - +class Queue(_base.MessageResource): resources_key = "queues" base_path = "/queues" @@ -43,12 +39,6 @@ class Queue(resource.Resource): #: must not exceed 64 bytes in length, and it is limited to US-ASCII #: letters, digits, underscores, and hyphens. name = resource.Body("name", alternate_id=True) - #: The ID to identify the client accessing Zaqar API. Must be specified - #: in header for each API request. - client_id = resource.Header("Client-ID") - #: The ID to identify the project accessing Zaqar API. Must be specified - #: in case keystone auth is not enabled in Zaqar service. - project_id = resource.Header("X-PROJECT-ID") def create(self, session, prepend_key=False, base_path=None, **kwargs): request = self._prepare_request( @@ -65,85 +55,3 @@ class Queue(resource.Resource): self._translate_response(response, has_body=False) return self - - @classmethod - def list(cls, session, paginated=False, base_path=None, **params): - """This method is a generator which yields queue objects. - - This is almost the copy of list method of resource.Resource class. - The only difference is the request header now includes `Client-ID` - and `X-PROJECT-ID` fields which are required by Zaqar v2 API. - """ - more_data = True - query_params = cls._query_mapping._transpose(params, cls) - - if base_path is None: - base_path = cls.base_path - - uri = base_path % params - headers = { - "Client-ID": params.get('client_id', None) or str(uuid.uuid4()), - "X-PROJECT-ID": params.get('project_id', None) - or session.get_project_id(), - } - - while more_data: - resp = session.get(uri, headers=headers, params=query_params) - resp = resp.json() - resp = resp[cls.resources_key] - - if not resp: - more_data = False - - yielded = 0 - new_marker = None - for data in resp: - value = cls.existing(**data) - new_marker = value.id - yielded += 1 - yield value - - if not paginated: - return - if "limit" in query_params and yielded < query_params["limit"]: - return - query_params["limit"] = yielded - query_params["marker"] = new_marker - - def fetch( - self, - session, - requires_id=True, - base_path=None, - error_message=None, - skip_cache=False, - **kwargs, - ): - request = self._prepare_request( - requires_id=requires_id, base_path=base_path - ) - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - request.headers.update(headers) - response = session.get( - request.url, headers=headers, skip_cache=skip_cache - ) - self._translate_response(response) - - return self - - def delete( - self, session, error_message=None, *, microversion=None, **kwargs - ): - request = self._prepare_request() - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - request.headers.update(headers) - response = session.delete(request.url, headers=headers) - - self._translate_response(response, has_body=False) - return self diff --git a/openstack/message/v2/subscription.py b/openstack/message/v2/subscription.py index 884c1ea83..5da496246 100644 --- a/openstack/message/v2/subscription.py +++ b/openstack/message/v2/subscription.py @@ -12,15 +12,11 @@ import uuid +from openstack.message.v2 import _base from openstack import resource -class Subscription(resource.Resource): - # FIXME(anyone): The name string of `location` field of Zaqar API response - # is lower case. That is inconsistent with the guide from API-WG. This is - # a workaround for this issue. - location = resource.Header("location") - +class Subscription(_base.MessageResource): resources_key = 'subscriptions' base_path = '/queues/%(queue_name)s/subscriptions' @@ -51,12 +47,6 @@ class Subscription(resource.Resource): ttl = resource.Body("ttl") #: The queue name which the subscription is registered on. queue_name = resource.URI("queue_name") - #: The ID to identify the client accessing Zaqar API. Must be specified - #: in header for each API request. - client_id = resource.Header("Client-ID") - #: The ID to identify the project. Must be provided when keystone - #: authentication is not enabled in Zaqar service. - project_id = resource.Header("X-PROJECT-ID") def create(self, session, prepend_key=False, base_path=None, **kwargs): request = self._prepare_request( @@ -73,87 +63,3 @@ class Subscription(resource.Resource): self._translate_response(response) return self - - @classmethod - def list(cls, session, paginated=True, base_path=None, **params): - """This method is a generator which yields subscription objects. - - This is almost the copy of list method of resource.Resource class. - The only difference is the request header now includes `Client-ID` - and `X-PROJECT-ID` fields which are required by Zaqar v2 API. - """ - more_data = True - - if base_path is None: - base_path = cls.base_path - - uri = base_path % params - headers = { - "Client-ID": params.get('client_id', None) or str(uuid.uuid4()), - "X-PROJECT-ID": params.get('project_id', None) - or session.get_project_id(), - } - - query_params = cls._query_mapping._transpose(params, cls) - while more_data: - resp = session.get(uri, headers=headers, params=query_params) - resp = resp.json() - resp = resp[cls.resources_key] - - if not resp: - more_data = False - - yielded = 0 - new_marker = None - for data in resp: - value = cls.existing(**data) - new_marker = value.id - yielded += 1 - yield value - - if not paginated: - return - if "limit" in query_params and yielded < query_params["limit"]: - return - query_params["limit"] = yielded - query_params["marker"] = new_marker - - def fetch( - self, - session, - requires_id=True, - base_path=None, - error_message=None, - skip_cache=False, - **kwargs, - ): - request = self._prepare_request( - requires_id=requires_id, base_path=base_path - ) - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - - request.headers.update(headers) - response = session.get( - request.url, headers=request.headers, skip_cache=skip_cache - ) - self._translate_response(response) - - return self - - def delete( - self, session, error_message=None, *, microversion=None, **kwargs - ): - request = self._prepare_request() - headers = { - "Client-ID": self.client_id or str(uuid.uuid4()), - "X-PROJECT-ID": self.project_id or session.get_project_id(), - } - - request.headers.update(headers) - response = session.delete(request.url, headers=request.headers) - - self._translate_response(response, has_body=False) - return self