diff --git a/perxis/channel.py b/perxis/channel.py new file mode 100644 index 0000000000000000000000000000000000000000..39b9660daf4c901787170415382621d1f8f1dc43 --- /dev/null +++ b/perxis/channel.py @@ -0,0 +1,57 @@ +from typing import Optional + +import grpc + +from perxis.auth import APIKeyPlugin +from perxis.models import PerxisEnviron + + +class GrpcChannel: + call_credentials: grpc.CallCredentials + channel_credentials: grpc.ChannelCredentials + composite_credentials: grpc.ChannelCredentials + channel: Optional[grpc.Channel] = None + + def __init__( + self, + target: str, + metadata_plugin: grpc.AuthMetadataPlugin, + project_name: str, + project_version: str, + call_credentials_name: str = "api-key", + ) -> None: + self.target = target + self.metadata_plugin = metadata_plugin + self.call_credentials_name = call_credentials_name + self._init_credentials() + self.connect(project_name, project_version) + + def _init_credentials(self) -> None: + self.call_credentials = grpc.metadata_call_credentials( + self.metadata_plugin, name=self.call_credentials_name + ) + self.channel_credentials = grpc.ssl_channel_credentials() + self.composite_credentials = grpc.composite_channel_credentials( + self.channel_credentials, self.call_credentials + ) + + def connect(self, project_name: str, project_version: str) -> None: + if not self.channel: + self.channel = grpc.aio.secure_channel( + self.target, self.composite_credentials, + options=( + ("grpc.primary_user_agent", f"{project_name}/{project_version}"), + ) + ) + + async def close(self) -> None: + await self.channel.close() + + +def get_grpc_channel(perxis_environ: PerxisEnviron, project_name: str, project_version: str) -> GrpcChannel: + return GrpcChannel( + target=perxis_environ.target, + metadata_plugin=APIKeyPlugin(token=perxis_environ.api_key), + project_name=project_name, + project_version=project_version + ) diff --git a/perxis/models.py b/perxis/models.py new file mode 100644 index 0000000000000000000000000000000000000000..340c735c3a0d50c6afb6ff3404e126bdae70d8a5 --- /dev/null +++ b/perxis/models.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass + + +@dataclass +class PerxisEnviron: + target: str + space_id: str + env_id: str + api_key: str + collection_id: str + + +@dataclass +class Reference: + id: str + collection_id: str + disabled: bool = False diff --git a/perxis/provider.py b/perxis/provider.py new file mode 100644 index 0000000000000000000000000000000000000000..bd3bf25c2e8de14c378167dd03142a2fc491dfa1 --- /dev/null +++ b/perxis/provider.py @@ -0,0 +1,211 @@ +from typing import Optional +from google.protobuf.struct_pb2 import Struct +from perxis.common import common_pb2 +from perxis.items import items_pb2, items_pb2_grpc +from perxis.references import references_pb2 +from perxis.references import references_pb2_grpc +from perxis.channel import GrpcChannel +from perxis.models import Reference + + +class PerxisDataProvider: + def __init__( + self, + channel: GrpcChannel, + space_id: str, + env_id: str, + collection_id: Optional[str] = None, + default_page_size: Optional[int] = 100, + ) -> None: + self.channel = channel + self.space_id = space_id + self.env_id = env_id + self.collection_id = collection_id + self.items_stub = items_pb2_grpc.ItemsStub(self.channel.channel) + self.references_stub = references_pb2_grpc.ReferencesStub(self.channel.channel) + self.default_page_size = default_page_size + + async def create( + self, data: Struct, collection_id: Optional[str] = None + ) -> items_pb2.CreateResponse: + """Сохранение данных формы в системе Perxis.""" + result = await self.items_stub.Create( + items_pb2.CreateRequest( + item=items_pb2.Item( + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + data=data, + ) + ) + ) + return result + + async def update( + self, item_id: str, data: Struct, collection_id: Optional[str] = None + ): + """Метод обновления записи в коллекции.""" + result = await self.items_stub.Update( + items_pb2.UpdateRequest( + item=items_pb2.Item( + id=item_id, + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + data=data, + ) + ) + ) + return result + + async def find( + self, + collection_id: Optional[str] = None, + filters: Optional[list[str]] = None, + sort_by: Optional[list[str]] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + ) -> items_pb2.FindResponse: + """Метод поиска записей в коллекции.""" + result = await self.items_stub.Find( + items_pb2.FindRequest( + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + filter=items_pb2.Filter(q=filters or []), + options=items_pb2.FindOptions( + options=common_pb2.FindOptions( + sort=sort_by, page_num=page_num, page_size=page_size + ) + ), + ) + ) + return result + + async def find_published( + self, + collection_id: Optional[str] = None, + filters: Optional[list[str]] = None, + fields: Optional[list[str]] = None, + sort_by: Optional[list[str]] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + ) -> items_pb2.FindResponse: + result = await self.items_stub.FindPublished( + items_pb2.FindPublishedRequest( + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + filter=items_pb2.Filter(q=filters or []), + options=items_pb2.FindPublishedOptions( + options=common_pb2.FindOptions( + sort=sort_by, + page_size=page_size, + fields=fields or [], + page_num=page_num, + ) + ), + ) + ) + return result + + async def fetch_all_published( + self, + collection_id: Optional[str] = None, + filters: Optional[list[str]] = None, + fields: Optional[list[str]] = None, + sort_by: Optional[list[str]] = None, + ) -> items_pb2.FindResponse: + """Метод поиска всех опубликованных записей в коллекции.""" + kwargs = { + "collection_id": collection_id, + "filters": filters, + "sort_by": sort_by, + "fields": fields, + "page_size": self.default_page_size, + } + message = await self.find_published(**kwargs) + yield message + + if pages := message.total // self.default_page_size: + if message.total % self.default_page_size: + pages += 1 + else: + pages = 1 + + for page_num in range(1, pages + 1): + yield await self.find_published(page_num=page_num, **kwargs) + + async def unpublish(self, item_id: str, collection_id: Optional[str] = None): + """Метод снятия с публикации записи в коллекции.""" + result = await self.items_stub.Unpublish( + items_pb2.UnpublishRequest( + item=items_pb2.Item( + id=item_id, + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + ) + ) + ) + return result + + async def publish(self, item_id: str, collection_id: Optional[str] = None): + """Метод публикации записи в коллекции.""" + result = await self.items_stub.Publish( + items_pb2.PublishRequest( + item=items_pb2.Item( + id=item_id, + space_id=self.space_id, + env_id=self.env_id, + collection_id=collection_id or self.collection_id, + ) + ) + ) + return result + + async def fetch_all( + self, + collection_id: Optional[str] = None, + filters: Optional[list[str]] = None, + sort_by: Optional[list[str]] = None, + ) -> items_pb2.FindResponse: + """Метод получения всех записей коллекции.""" + items = [] + storage_data = await self.find( + collection_id=collection_id or self.collection_id, + page_size=self.default_page_size, + filters=filters, + ) + items.extend(storage_data.items) + + if pages := storage_data.total // self.default_page_size: + if storage_data.total % self.default_page_size: + pages += 1 + else: + pages = 1 + + for page_num in range(1, pages + 1): + storage_data = await self.find( + collection_id=collection_id or self.collection_id, + filters=filters, + sort_by=sort_by, + page_num=page_num, + page_size=self.default_page_size, + ) + items.extend(storage_data.items) + return items_pb2.FindResponse(items=items) + + async def get_references(self, references: list[Reference]): + """Метод получения связей.""" + result = await self.references_stub.Get( + references_pb2.GetRequest( + space_id=self.space_id, + env_id=self.env_id, + references=[ + references_pb2.Reference(id=ref.id, collection_id=ref.collection_id) + for ref in references + ], + ) + ) + return result