diff --git a/README.md b/README.md index 1be8989..0fbf241 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This library is a simple client for working with **[Yandex Lockbox](https://cloud.yandex.ru/en/docs/lockbox/)** over [REST API](https://cloud.yandex.ru/en/docs/lockbox/api-ref/), simplifying work with secrets and allowing you to work with them in the OOP paradigm. +Supports two modes: synchronous and asynchronous. + **[Full library documentation link](https://akimrx.github.io/python-yc-lockbox/)** **Supported Python versions**: @@ -23,6 +25,11 @@ This library is a simple client for working with **[Yandex Lockbox](https://clou * [Requests](https://github.com/psf/requests) +**Extra dependencies:** + +* [aiohttp](https://github.com/aio-libs/aiohttp) + + **Currently, the following operations are not supported by the library:** * List secret access bindings @@ -34,7 +41,7 @@ This library is a simple client for working with **[Yandex Lockbox](https://clou **In the near future release:** - [x] Tests -- [ ] Async client implementation +- [x] Async client implementation - [ ] Implement access bindings methods and view operations - [ ] Ansible action and lookup plugins @@ -189,4 +196,51 @@ for secret in lockbox.list_secrets(folder_id="b1xxxxxxxxxx", iterator=True): version.schedule_version_destruction() version.cancel_version_destruction() -``` \ No newline at end of file +``` + +## Async mode + +The client supports asynchronous mode using the aiohttp library. The signature of the methods does not differ from the synchronous implementation. + + +Just import async client: + +```python + +from yc_lockbox import AsyncYandexLockboxClient + +lockbox = AsyncYandexLockboxClient("oauth_or_iam_token") +``` + +Alternative: + +```python + +from yc_lockbox import YandexLockboxFacade + +lockbox = YandexLockboxFacade("oauth_or_iam_token", enable_async=True).client +``` + +Example usage: + +```python +secret: Secret = await lockbox.get_secret("e6qxxxxxxxxxx") +payload = await secret.payload() +print(payload.entries) # list of SecretPayloadEntry objects + +# Direct access + +entry = payload["secret_entry_1"] # or payload.get("secret_entry_1") + +print(entry.text_value) # return MASKED value like *********** +print(entry.reveal_text_value()) # similar to entry.text_value.get_secret_value() + +# Async iterators + +secret_versions = await secret.list_versions(iterator=True) + +async for version in secret_versions: + if version.id != secret.current_version.id: + await version.schedule_version_destruction() + await version.cancel_version_destruction() +``` diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 46beab0..07e40fc 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -231,7 +231,7 @@

All modules for which code is available

- + diff --git a/docs/_modules/yc_lockbox/_auth.html b/docs/_modules/yc_lockbox/_auth.html index b3383ab..9105b36 100644 --- a/docs/_modules/yc_lockbox/_auth.html +++ b/docs/_modules/yc_lockbox/_auth.html @@ -309,7 +309,7 @@

Source code for yc_lockbox._auth

       
     
   
- + diff --git a/docs/_modules/yc_lockbox/_lockbox.html b/docs/_modules/yc_lockbox/_lockbox.html index eaa4a04..e0cc855 100644 --- a/docs/_modules/yc_lockbox/_lockbox.html +++ b/docs/_modules/yc_lockbox/_lockbox.html @@ -197,10 +197,10 @@

Source code for yc_lockbox._lockbox

 import logging
-from typing import Type, Optional, Callable, Iterator
+from typing import Any, AsyncGenerator, Coroutine, Type, Optional, Callable, Iterator
 
 from yc_lockbox._abc import AbstractYandexAuthClient, AbstractYandexLockboxClient, AbstractHTTPAdapter
-from yc_lockbox._adapters import HTTPAdapter
+from yc_lockbox._adapters import HTTPAdapter, AsyncHTTPAdapter
 from yc_lockbox._auth import YandexAuthClient
 from yc_lockbox._models import (
     Secret,
@@ -728,26 +728,587 @@ 

Source code for yc_lockbox._lockbox

 
 
 
-# TODO: implement
+
+[docs] class AsyncYandexLockboxClient(AbstractYandexLockboxClient): - """The same as :class:`YandexLockboxClient` but async.""" + """ + Yandex Lockbox secrets vault client. + The same as :class:`YandexLockboxClient` but async. + + :param credentials: Credentials for authenticate requests. + Allowed types: service account key, OAuth token, IAM token. + :param auth_client: Optional client implementation for authenticate requests. + Defaults to ``YandexAuthClient``. + :param adapter: HTTP adapter for communicate with Yandex Cloud API. + :param lockbox_base_url: Lockbox base URL without resource path. + :param payload_lockbox_base_url: Lockbox payload base URL without resource path. + :param auth_base_url: IAM base URL without resource path. + + .. note:: + + All the values of the secrets are masked, i.e. looks like ``***********``. + To get the real value of the secret, you need to call the injected methods + :func:`reveal_text_value()` or :func:`reveal_binary_value()`. + + Usage:: + + from yc_lockbox import AsyncYandexLockboxClient, Secret + + lockbox = AsyncYandexLockboxClient("y0_AgAEXXXXXXXXXXXXXXXXXXXXXXXXX") # OAuth or IAM token + + secret: Secret = await lockbox.get_secret("e6xxxxxxxxxxxxxxxx") + print(secret.name, secret.status, secret.description) + + secret_versions = await secret.list_versions() + async for version in secret_versions: + print(version) + if version.id != secret.current_version.id: + await version.schedule_version_destruction() + + payload = await secret.payload() + + try: + value = payload["mykey"] + print(value.reveal_text_value()) + except KeyError: + print("Invalid key!") + + print(payload.get("foo")) # None if not exists without raising exception + entry = payload[0] # similar to payload.entries[0] + + Authenticate via service account key:: + + import json + + # generate json key for your SA + # yc iam key create --service-account-name my-sa --output key.json + + with open("./key.json", "r") as infile: + credentials = json.load(infile) + + lockbox = AsyncYandexLockboxClient(credentials) + + """ + + enable_async = True + + def __init__( + self, + credentials, + *, + auth_client: Optional[Type[AbstractYandexAuthClient]] = YandexAuthClient, + adapter: Optional[Type[AbstractHTTPAdapter]] = AsyncHTTPAdapter, + lockbox_base_url: str | None = None, + payload_lockbox_base_url: str | None = None, + ) -> None: + super().__init__( + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + self.auth: AbstractYandexAuthClient = auth_client(credentials) + self.adapter: AbstractHTTPAdapter = adapter + + @property + def auth_headers(self) -> dict[str, str]: + """Returns headers for authenticate.""" + return self.auth.get_auth_headers() + + def _inject_client_to_items(self, items: list[T]) -> list[T]: + """Inject this client to each response model.""" + return list(map(lambda item: item.inject_client(self), items)) + + async def _seekable_response( + self, func: Callable[..., BasePaginatedResponse], entrypoint: str, *args, **kwargs + ) -> AsyncGenerator[Any, T]: + """ + Requests all data from the API using the generators, instead of a page-by-page response. + This method does not works with dict. Be careful. + Returns list of objects from model entrypoint. + + :param func: Adapter func to get data from API. + :param entrypoint: Response attribute that contains list of useful data items. + :param args: Arguments for func. Will be passed when called inside. + :param kwargs: Keyword arguments for func. Similar to ``args``. + """ + + if kwargs.get("params") is None: + raise TypeError( + "This method works only with query string parameters. Check keyword arguments to resolve it." + ) + + next_token = "" # nosec B105 + + while next_token is not None: + # There could potentially be a problem if some request doesn't have a 'pageToken' in query string params. + # However, this is already a question for a non-consistent API, I think. + # An unlikely story, but worth keeping in mind. + kwargs["params"]["pageToken"] = next_token + + response = await func(*args, **kwargs) + next_token = response.next_page_token # None or a new token from the API + + if not hasattr(response, entrypoint): + raise AttributeError(f"Entrypoint {entrypoint} not exists in response model.") + + for item in getattr(response, entrypoint): + if not hasattr(item, "inject_client"): + raise AttributeError( + f"Incorrect item. Method 'inject_client' is not exists in {item.__class__} {type(item)}" + ) + item.inject_client(self) + yield item + +
+[docs] + async def activate_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Activates the specified secret. + + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:activate" + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def add_secret_version( + self, secret_id: str, version: INewSecretVersion, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Adds new version based on a previous one. + + :param secret_id: Secret indentifier. + :param version: A new version object. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:addVersion" + payload = version.model_dump_json(by_alias=True, exclude_none=True) + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def create_secret( + self, secret: INewSecret, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Creates a secret in the specified folder. + + :param secret: A new secret object. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets" + payload = secret.model_dump_json(by_alias=True, exclude_none=True) + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def cancel_secret_version_destruction( + self, secret_id: str, version_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Cancels previously scheduled version destruction, if the version hasn't been destroyed yet. + + :param secret_id: Secret indentifier. + :param version_id: Secret version id to cancel destruction. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:cancelVersionDestruction" + payload = {"versionId": version_id} + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + json=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def deactivate_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Deactivate a secret. + + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:deactivate" + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
- def __init__(self, *args, **kwargs) -> None: - raise NotImplementedError # pragma: no cover +
+[docs] + async def delete_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Deletes the specified secret. -# TODO: implement -class YandexLockbox: + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + response = await self.adapter.request( + "DELETE", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def get_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Secret | YandexCloudError]: + """ + Get lockbox secret by ID. + + :param secret_id: Secret identifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + response = await self.adapter.request( + "GET", + url, + headers=self.auth_headers, + response_model=Secret, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + +
+[docs] + async def get_secret_payload( + self, + secret_id: str, + version_id: str | None = None, + raise_for_status: bool = True, + ) -> Coroutine[Any, Any, SecretPayload | YandexCloudError]: + """ + Get lockbox secret payload by ID and optional version. + + :param secret_id: Secret identifier. + :param version_id: Secret version. Optional. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.payload_lockbox_base_url}/secrets/{secret_id}/payload" + params = {"version_id": version_id} if version_id else None + return await self.adapter.request( + "GET", + url, + headers=self.auth_headers, + response_model=SecretPayload, + raise_for_status=raise_for_status, + params=params, + )
+ + +
+[docs] + async def list_secrets( + self, + folder_id: str, + page_size: int = 100, + page_token: str | None = None, + raise_for_status: bool = True, + iterator: bool = False, + ) -> Coroutine[Any, Any, SecretsList | YandexCloudError] | AsyncGenerator[Any, Secret]: + """ + Retrieves the list of secrets in the specified folder. + + :param folder_id: ID of the folder to list secrets in. + :param page_size: The maximum number of results per page to return. + If the number of available results is larger than ``page_size``, + the service returns a ``next_page_token`` that can be used to get + the next page of results in subsequent list requests. + Default value: ``100``. + The maximum value is ``1000``. + :param page_token: Page token. To get the next page of results, set ``page_token`` + to the ``next_page_token`` returned by a previous list request. + :param iterator: Returns all data as iterator (generator) instead paginated result. + """ + args = ( + "GET", + f"{self.lockbox_base_url}/secrets", + ) + kwargs = { + "headers": self.auth_headers, + "params": {"folderId": folder_id, "pageSize": page_size, "pageToken": page_token}, + "response_model": SecretsList, + "raise_for_status": raise_for_status, + } + + if iterator: + return self._seekable_response(self.adapter.request, "secrets", *args, **kwargs) + + response = await self.adapter.request(*args, **kwargs) + self._inject_client_to_items(response.secrets) + return response
+ + + # TODO: implement +
+[docs] + async def list_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError
+ + + # TODO: implement +
+[docs] + async def list_secret_operations(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError
+ + +
+[docs] + async def list_secret_versions( + self, + secret_id: str, + page_size: int = 100, + page_token: str | None = None, + raise_for_status: bool = True, + iterator: bool = False, + ) -> Coroutine[Any, Any, SecretVersionsList | YandexCloudError] | AsyncGenerator[Any, SecretVersion]: + """ + Retrieves the list of versions of the specified secret. + + :param secret_id: Secret identifier. + :param page_size: The maximum number of results per page to return. + If the number of available results is larger than ``page_size``, + the service returns a ``next_page_token`` that can be used to get + the next page of results in subsequent list requests. + Default value: ``100``. + The maximum value is ``1000``. + :param page_token: Page token. To get the next page of results, set ``page_token`` + to the ``next_page_token`` returned by a previous list request. + :param iterator: Returns all data as iterator (generator) instead paginated result. + """ + args = ( + "GET", + f"{self.lockbox_base_url}/secrets/{secret_id}/versions", + ) + kwargs = { + "headers": self.auth_headers, + "params": {"pageSize": page_size, "pageToken": page_token}, + "response_model": SecretVersionsList, + "raise_for_status": raise_for_status, + } + + if iterator: + print("IMA ITERATOR IMA ITERATOR IMA ITERATOR IMA ITERATOR") + return self._seekable_response(self.adapter.request, "versions", *args, **kwargs) + + response = await self.adapter.request(*args, **kwargs) + self._inject_client_to_items(response.versions) + return response
+ + +
+[docs] + async def schedule_secret_version_destruction( + self, secret_id: str, version_id: str, pending_period: int = 604800, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Schedules the specified version for destruction. + Scheduled destruction can be cancelled with the :func:`cancel_secret_version_destruction()` method. + + :param secret_id: Secret indentifier. + :param version_id: ID of the version to be destroyed. + :param pending_period: Time interval in seconds between the version destruction request and actual destruction. + Default value: ``604800`` (i.e. 7 days). + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + if isinstance(pending_period, int): + if pending_period <= 0: + raise ValueError("The ``pending_period`` value must be greater than 0.") + # protobuf duration compat + # https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/duration.proto + pending_period = str(pending_period) + "s" + else: + raise ValueError("The ``pending_period`` value must be integer.") + + url = f"{self.lockbox_base_url}/secrets/{secret_id}:scheduleVersionDestruction" + payload = {"versionId": version_id, "pendingPeriod": pending_period} + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + json=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + + # TODO: implement +
+[docs] + async def set_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError
+ + +
+[docs] + async def update_secret( + self, secret_id: str, data: IUpdateSecret, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Updates the specified secret. + + :param secret_id: Secret identifier. + :param data: A new data for the secret as object. + Important. Field mask that specifies which attributes of the secret are going to be updated. + A comma-separated names off ALL fields to be updated. Only the specified fields will be changed. + The others will be left untouched. If the field is specified in updateMask and no value for + that field was sent in the request, the field's value will be reset to the default. + The default value for most fields is null or 0. + If ``updateMask`` is not sent in the request, all fields values will be updated. + Fields specified in the request will be updated to provided values. The rest of the fields will be reset to the default. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + payload = data.model_dump_json(by_alias=True) + response = await self.adapter.request( + "PATCH", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response
+ + + # TODO: implement +
+[docs] + async def update_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError
+
+ + + +
+[docs] +class YandexLockboxFacade: # pragma: no cover """ A facade for encapsulating the logic of synchronous and asynchronous client operations, providing uniform methods. """ - def __init__(self) -> None: - raise NotImplementedError # pragma: no cover + _client: AbstractYandexLockboxClient + + def __init__( + self, + credentials, + *, + auth_client: Optional[Type[AbstractYandexAuthClient]] = YandexAuthClient, + lockbox_base_url: str | None = None, + payload_lockbox_base_url: str | None = None, + enable_async: bool = False, + ) -> None: + if enable_async: + self._client = AsyncYandexLockboxClient( + credentials, + auth_client=auth_client, + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + else: + self._client = YandexLockboxClient( + credentials, + auth_client=auth_client, + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + + @property + def client(self) -> AbstractYandexLockboxClient: + """Returns initialized Lockbox client.""" + return self._client + + def __getattr__(self, name): + """Dynamically delegate method calls to the appropriate client.""" + + if name == "_client": + return self._client + + if not hasattr(self._client, name): + raise AttributeError + + return getattr(self._client, name)
+ -__all__ = ["AsyncYandexLockboxClient", "YandexLockboxClient", "YandexLockbox"] +__all__ = ["AsyncYandexLockboxClient", "YandexLockboxClient", "YandexLockboxFacade"]
@@ -780,7 +1341,7 @@

Source code for yc_lockbox._lockbox

       
     
   
- + diff --git a/docs/_modules/yc_lockbox/_models.html b/docs/_modules/yc_lockbox/_models.html index e826453..7a9f814 100644 --- a/docs/_modules/yc_lockbox/_models.html +++ b/docs/_modules/yc_lockbox/_models.html @@ -197,13 +197,13 @@

Source code for yc_lockbox._models

 import logging
-from typing import Any, Iterator, Union
+from typing import Any, AsyncGenerator, Iterator, Union
 from datetime import datetime
 from pydantic import BaseModel, ConfigDict, Field, SecretStr, SecretBytes, computed_field
 
 from yc_lockbox._constants import RpcError
 from yc_lockbox._abc import AbstractYandexLockboxClient
-from yc_lockbox._types import T
+from yc_lockbox._types import T, SecretVersionsResponse
 from yc_lockbox._exceptions import LockboxError
 
 
@@ -500,18 +500,32 @@ 

Source code for yc_lockbox._models

         return self.client.delete_secret(self.id, **kwargs)
+ async def _async_refresh(self, **kwargs) -> "Secret": + data = await self.client.get_secret(self.id, **kwargs) + self._update_attributes(data) + return self + + def _sync_refresh(self, **kwargs) -> "Secret": + data = self.client.get_secret(self.id, **kwargs) + self._update_attributes(data) + return self + + def _update_attributes(self, data) -> None: + """Method for update model attributes after refresh.""" + for attr, value in data.model_dump().items(): + if value != getattr(self, attr, None): + setattr(self, attr, value) +
[docs] def refresh(self, **kwargs) -> "Secret": """Shortcut for refresh attributes for this secret.""" self._raise_when_empty_client() - data = self.client.get_secret(self.id, **kwargs) - for attr, value in data.model_dump().items(): - if value != getattr(self, attr): - setattr(self, attr, value) + if hasattr(self.client, "enable_async") and self.client.enable_async: + return self._async_refresh(**kwargs) - return data
+ return self._sync_refresh(**kwargs)
@@ -525,7 +539,7 @@

Source code for yc_lockbox._models

 [docs]
     def list_versions(
         self, page_size: int = 100, page_token: str | None = None, iterator: bool = False, **kwargs
-    ) -> Union["SecretVersionsList", Iterator["SecretVersion"], "YandexCloudError"]:
+    ) -> SecretVersionsResponse:
         """Shortcut for list all available versions of the current secret."""
         self._raise_when_empty_client()
         return self.client.list_secret_versions(
@@ -664,7 +678,7 @@ 

Source code for yc_lockbox._models

       
     
   
-
+
diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index b874b1c..4a5b30f 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -225,6 +225,56 @@ Other operations with secret +Async mode +---------- + +The client supports asynchronous mode using the aiohttp library. The signature of the methods does not differ from the synchronous implementation. + + +Just import async client: + +.. code-block:: python + + from yc_lockbox import AsyncYandexLockboxClient + + lockbox = AsyncYandexLockboxClient("oauth_or_iam_token") + + + +Alternative: + +.. code-block:: python + + from yc_lockbox import YandexLockboxFacade + + lockbox = YandexLockboxFacade("oauth_or_iam_token", enable_async=True).client + + +Example usage: + +.. code-block:: python + + secret: Secret = await lockbox.get_secret("e6qxxxxxxxxxx") + payload = await secret.payload() + print(payload.entries) # list of SecretPayloadEntry objects + + # Direct access + + entry = payload["secret_entry_1"] # or payload.get("secret_entry_1") + + print(entry.text_value) # return MASKED value like *********** + print(entry.reveal_text_value()) # similar to entry.text_value.get_secret_value() + + # Async iterators + + secret_versions = await secret.list_versions(iterator=True) + + async for version in secret_versions: + if version.id != secret.current_version.id: + await version.schedule_version_destruction() + await version.cancel_version_destruction() + + Modules ------- diff --git a/docs/_sources/pages/clients.rst.txt b/docs/_sources/pages/clients.rst.txt index 0d6b192..7081091 100644 --- a/docs/_sources/pages/clients.rst.txt +++ b/docs/_sources/pages/clients.rst.txt @@ -1,12 +1,22 @@ Client ====== +.. autoclass:: yc_lockbox.YandexLockboxFacade + :members: + :undoc-members: + :show-inheritance: + .. autoclass:: yc_lockbox.YandexLockboxClient :members: :undoc-members: :show-inheritance: +.. autoclass:: yc_lockbox.AsyncYandexLockboxClient + :members: + :undoc-members: + :show-inheritance: + .. autoclass:: yc_lockbox._auth.YandexAuthClient :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 2f0ebda..b16db03 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.1.3', + VERSION: '0.2.0', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/genindex.html b/docs/genindex.html index bf627d6..7ebbfed 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -205,18 +205,32 @@

A

@@ -243,20 +257,30 @@

B

C

+ - @@ -511,8 +569,12 @@

R

S

-
  • list_secret_access_bindings() (yc_lockbox.YandexLockboxClient method) +
  • list_secret_access_bindings() (yc_lockbox.AsyncYandexLockboxClient method) + +
  • +
  • list_secret_operations() (yc_lockbox.AsyncYandexLockboxClient method) + +
  • @@ -600,6 +674,8 @@

    Y

    @@ -637,7 +713,7 @@

    Y

    - + diff --git a/docs/index.html b/docs/index.html index a355a27..ea79914 100644 --- a/docs/index.html +++ b/docs/index.html @@ -199,7 +199,7 @@

    Yandex Lockbox Python client documentation#

    -

    Release v0.1.3

    +

    Release v0.2.0

    https://img.shields.io/pypi/pyversions/yc-lockbox.svg https://img.shields.io/pypi/v/yc-lockbox.svg https://codecov.io/gh/akimrx/python-yc-lockbox/branch/master/graph/badge.svg @@ -370,12 +370,54 @@

    Other operations with secret +

    Async mode#

    +

    The client supports asynchronous mode using the aiohttp library. The signature of the methods does not differ from the synchronous implementation.

    +

    Just import async client:

    +
    from yc_lockbox import AsyncYandexLockboxClient
    +
    +lockbox = AsyncYandexLockboxClient("oauth_or_iam_token")
    +
    +
    +

    Alternative:

    +
    from yc_lockbox import YandexLockboxFacade
    +
    +lockbox = YandexLockboxFacade("oauth_or_iam_token", enable_async=True).client
    +
    +
    +

    Example usage:

    +
    secret: Secret = await lockbox.get_secret("e6qxxxxxxxxxx")
    +payload = await secret.payload()
    +print(payload.entries)  # list of SecretPayloadEntry objects
    +
    +# Direct access
    +
    +entry = payload["secret_entry_1"]  # or payload.get("secret_entry_1")
    +
    +print(entry.text_value)  # return MASKED value like ***********
    +print(entry.reveal_text_value())  # similar to entry.text_value.get_secret_value()
    +
    +# Async iterators
    +
    +secret_versions = await secret.list_versions(iterator=True)
    +
    +async for version in secret_versions:
    +   if version.id != secret.current_version.id:
    +      await version.schedule_version_destruction()
    +      await version.cancel_version_destruction()
    +
    +
    +

    Modules#

    diff --git a/docs/objects.inv b/docs/objects.inv index a6b7741..9942d95 100644 Binary files a/docs/objects.inv and b/docs/objects.inv differ diff --git a/docs/pages/abstracts.html b/docs/pages/abstracts.html index 1166aeb..07872d0 100644 --- a/docs/pages/abstracts.html +++ b/docs/pages/abstracts.html @@ -243,7 +243,7 @@

    Abstracts + diff --git a/docs/pages/adapters.html b/docs/pages/adapters.html index 047f04e..e419d5e 100644 --- a/docs/pages/adapters.html +++ b/docs/pages/adapters.html @@ -251,7 +251,7 @@

    Adapters - + diff --git a/docs/pages/clients.html b/docs/pages/clients.html index 124a88b..cf4c51d 100644 --- a/docs/pages/clients.html +++ b/docs/pages/clients.html @@ -199,6 +199,20 @@

    Client#

    +
    +
    +class yc_lockbox.YandexLockboxFacade(credentials, *, auth_client=<class 'yc_lockbox._auth.YandexAuthClient'>, lockbox_base_url=None, payload_lockbox_base_url=None, enable_async=False)[source]#
    +

    Bases: object

    +

    A facade for encapsulating the logic of synchronous and asynchronous client operations, +providing uniform methods.

    +
    +
    +property client: AbstractYandexLockboxClient#
    +

    Returns initialized Lockbox client.

    +
    + +
    +
    class yc_lockbox.YandexLockboxClient(credentials, *, auth_client=<class 'yc_lockbox._auth.YandexAuthClient'>, adapter=<class 'yc_lockbox._adapters.HTTPAdapter'>, lockbox_base_url=None, payload_lockbox_base_url=None)[source]#
    @@ -533,6 +547,352 @@

    Client#

    +
    +
    +class yc_lockbox.AsyncYandexLockboxClient(credentials, *, auth_client=<class 'yc_lockbox._auth.YandexAuthClient'>, adapter=<class 'yc_lockbox._adapters.AsyncHTTPAdapter'>, lockbox_base_url=None, payload_lockbox_base_url=None)[source]#
    +

    Bases: AbstractYandexLockboxClient

    +

    Yandex Lockbox secrets vault client. +The same as YandexLockboxClient but async.

    +
    +
    Parameters:
    +
      +
    • credentials – Credentials for authenticate requests. +Allowed types: service account key, OAuth token, IAM token.

    • +
    • auth_client (Optional[Type[AbstractYandexAuthClient]]) – Optional client implementation for authenticate requests. +Defaults to YandexAuthClient.

    • +
    • adapter (Optional[Type[AbstractHTTPAdapter]]) – HTTP adapter for communicate with Yandex Cloud API.

    • +
    • lockbox_base_url (Optional[str]) – Lockbox base URL without resource path.

    • +
    • payload_lockbox_base_url (Optional[str]) – Lockbox payload base URL without resource path.

    • +
    • auth_base_url – IAM base URL without resource path.

    • +
    +
    +
    +
    +

    Note

    +

    All the values of the secrets are masked, i.e. looks like ***********. +To get the real value of the secret, you need to call the injected methods +reveal_text_value() or reveal_binary_value().

    +
    +

    Usage:

    +
    from yc_lockbox import AsyncYandexLockboxClient, Secret
    +
    +lockbox = AsyncYandexLockboxClient("y0_AgAEXXXXXXXXXXXXXXXXXXXXXXXXX")  # OAuth or IAM token
    +
    +secret: Secret = await lockbox.get_secret("e6xxxxxxxxxxxxxxxx")
    +print(secret.name, secret.status, secret.description)
    +
    +secret_versions = await secret.list_versions()
    +async for version in secret_versions:
    +    print(version)
    +    if version.id != secret.current_version.id:
    +        await version.schedule_version_destruction()
    +
    +payload = await secret.payload()
    +
    +try:
    +    value = payload["mykey"]
    +    print(value.reveal_text_value())
    +except KeyError:
    +    print("Invalid key!")
    +
    +print(payload.get("foo"))  # None if not exists without raising exception
    +entry = payload[0]  # similar to payload.entries[0]
    +
    +
    +

    Authenticate via service account key:

    +
    import json
    +
    +# generate json key for your SA
    +# yc iam key create --service-account-name my-sa --output key.json
    +
    +with open("./key.json", "r") as infile:
    +    credentials = json.load(infile)
    +
    +lockbox = AsyncYandexLockboxClient(credentials)
    +
    +
    +
    +
    +async activate_secret(secret_id, raise_for_status=True)[source]#
    +

    Activates the specified secret.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async add_secret_version(secret_id, version, raise_for_status=True)[source]#
    +

    Adds new version based on a previous one.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • version (INewSecretVersion) – A new version object.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +property auth_headers: dict[str, str]#
    +

    Returns headers for authenticate.

    +
    + +
    +
    +async cancel_secret_version_destruction(secret_id, version_id, raise_for_status=True)[source]#
    +

    Cancels previously scheduled version destruction, if the version hasn’t been destroyed yet.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • version_id (str) – Secret version id to cancel destruction.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async create_secret(secret, raise_for_status=True)[source]#
    +

    Creates a secret in the specified folder.

    +
    +
    Parameters:
    +
      +
    • secret (INewSecret) – A new secret object.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async deactivate_secret(secret_id, raise_for_status=True)[source]#
    +

    Deactivate a secret.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async delete_secret(secret_id, raise_for_status=True)[source]#
    +

    Deletes the specified secret.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +enable_async = True#
    +
    + +
    +
    +async get_secret(secret_id, raise_for_status=True)[source]#
    +

    Get lockbox secret by ID.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret identifier.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Secret | YandexCloudError]

    +
    +
    +
    + +
    +
    +async get_secret_payload(secret_id, version_id=None, raise_for_status=True)[source]#
    +

    Get lockbox secret payload by ID and optional version.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret identifier.

    • +
    • version_id (Optional[str]) – Secret version. Optional.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, SecretPayload | YandexCloudError]

    +
    +
    +
    + +
    +
    +async list_secret_access_bindings(*args, **kwargs)[source]#
    +

    Not ready yet.

    +
    + +
    +
    +async list_secret_operations(*args, **kwargs)[source]#
    +

    Not ready yet.

    +
    + +
    +
    +async list_secret_versions(secret_id, page_size=100, page_token=None, raise_for_status=True, iterator=False)[source]#
    +

    Retrieves the list of versions of the specified secret.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret identifier.

    • +
    • page_size (int) – The maximum number of results per page to return. +If the number of available results is larger than page_size, +the service returns a next_page_token that can be used to get +the next page of results in subsequent list requests. +Default value: 100. +The maximum value is 1000.

    • +
    • page_token (Optional[str]) – Page token. To get the next page of results, set page_token +to the next_page_token returned by a previous list request.

    • +
    • iterator (bool) – Returns all data as iterator (generator) instead paginated result.

    • +
    +
    +
    Return type:
    +

    Union[Coroutine[Any, Any, SecretVersionsList | YandexCloudError], AsyncGenerator[Any, SecretVersion]]

    +
    +
    +
    + +
    +
    +async list_secrets(folder_id, page_size=100, page_token=None, raise_for_status=True, iterator=False)[source]#
    +

    Retrieves the list of secrets in the specified folder.

    +
    +
    Parameters:
    +
      +
    • folder_id (str) – ID of the folder to list secrets in.

    • +
    • page_size (int) – The maximum number of results per page to return. +If the number of available results is larger than page_size, +the service returns a next_page_token that can be used to get +the next page of results in subsequent list requests. +Default value: 100. +The maximum value is 1000.

    • +
    • page_token (Optional[str]) – Page token. To get the next page of results, set page_token +to the next_page_token returned by a previous list request.

    • +
    • iterator (bool) – Returns all data as iterator (generator) instead paginated result.

    • +
    +
    +
    Return type:
    +

    Union[Coroutine[Any, Any, SecretsList | YandexCloudError], AsyncGenerator[Any, Secret]]

    +
    +
    +
    + +
    +
    +async schedule_secret_version_destruction(secret_id, version_id, pending_period=604800, raise_for_status=True)[source]#
    +

    Schedules the specified version for destruction. +Scheduled destruction can be cancelled with the cancel_secret_version_destruction() method.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret indentifier.

    • +
    • version_id (str) – ID of the version to be destroyed.

    • +
    • pending_period (int) – Time interval in seconds between the version destruction request and actual destruction. +Default value: 604800 (i.e. 7 days).

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async set_secret_access_bindings(*args, **kwargs)[source]#
    +

    Not ready yet.

    +
    + +
    +
    +async update_secret(secret_id, data, raise_for_status=True)[source]#
    +

    Updates the specified secret.

    +
    +
    Parameters:
    +
      +
    • secret_id (str) – Secret identifier.

    • +
    • data (IUpdateSecret) – A new data for the secret as object. +Important. Field mask that specifies which attributes of the secret are going to be updated. +A comma-separated names off ALL fields to be updated. Only the specified fields will be changed. +The others will be left untouched. If the field is specified in updateMask and no value for +that field was sent in the request, the field’s value will be reset to the default. +The default value for most fields is null or 0. +If updateMask is not sent in the request, all fields values will be updated. +Fields specified in the request will be updated to provided values. The rest of the fields will be reset to the default.

    • +
    • raise_for_status (bool) – If set to False returns YandexCloudError instead throw exception. +Defaults to True.

    • +
    +
    +
    Return type:
    +

    Coroutine[Any, Any, Operation | YandexCloudError]

    +
    +
    +
    + +
    +
    +async update_secret_access_bindings(*args, **kwargs)[source]#
    +

    Not ready yet.

    +
    + +
    +
    class yc_lockbox._auth.YandexAuthClient(credentials, *, auth_base_url=None, **kwargs)[source]#
    @@ -632,6 +992,10 @@

    Client#

    @@ -1125,7 +1125,7 @@

    Common models + diff --git a/docs/search.html b/docs/search.html index 56e5c1b..7f70737 100644 --- a/docs/search.html +++ b/docs/search.html @@ -236,7 +236,7 @@ - + diff --git a/docs/searchindex.js b/docs/searchindex.js index 0a78a8e..ea7a417 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["index", "pages/abstracts", "pages/adapters", "pages/clients", "pages/exceptions", "pages/models"], "filenames": ["index.rst", "pages/abstracts.rst", "pages/adapters.rst", "pages/clients.rst", "pages/exceptions.rst", "pages/models.rst"], "titles": ["Yandex Lockbox Python client documentation", "Abstracts", "Adapters", "Client", "Exceptions", "Models & objects"], "terms": {"releas": 0, "v": 0, "0": [0, 3], "1": 0, "3": 0, "thi": [0, 3, 5], "librari": 0, "i": [0, 3, 5], "simpl": [0, 3], "work": [0, 3], "over": 0, "rest": [0, 3], "api": [0, 3], "simplifi": 0, "allow": [0, 3], "you": [0, 3], "them": 0, "oop": 0, "paradigm": 0, "support": 0, "10": 0, "11": 0, "12": 0, "depend": 0, "pydanticv2": 0, "crypthographi": 0, "pyjwt": 0, "request": [0, 3], "current": [0, 5], "follow": 0, "ar": [0, 3, 5], "list": [0, 3, 5], "access": 0, "bind": 0, "set": [0, 3], "updat": [0, 3, 5], "pip": 0, "yc": [0, 3], "also": [0, 5], "can": [0, 3], "sourc": [0, 3, 5], "git": 0, "clone": 0, "http": [0, 3], "github": 0, "com": 0, "akimrx": 0, "cd": 0, "make": [0, 3], "authent": [0, 3], "via": [0, 3], "your": [0, 3], "oauth": [0, 3], "token": [0, 3, 5], "yc_lockbox": [0, 3, 5], "import": [0, 3], "yandexlockboxcli": [0, 3], "y0_xxxxxxxxxxxx": 0, "iam": [0, 3], "If": [0, 3], "pass": [0, 3], "credenti": [0, 3], "need": [0, 3], "take": [0, 3], "care": [0, 3], "fresh": [0, 3], "yourself": [0, 3], "t1": 0, "xxxxxx": 0, "xxxxxxx": 0, "us": [0, 3, 5], "servic": [0, 3], "account": [0, 3], "kei": [0, 3, 5], "json": [0, 3], "open": [0, 3], "path": [0, 3], "r": [0, 3], "keyfil": 0, "read": 0, "inewsecret": [0, 3, 5], "inewsecretpayloadentri": [0, 5], "oauth_or_iam_token": 0, "create_secret_oper": 0, "create_secret": [0, 3], "folder_id": [0, 3, 5], "b1xxxxxxxxxxxxxx": 0, "name": [0, 3, 5], "my": [0, 3, 5], "version_payload_entri": [0, 5], "secret_entry_1": 0, "text_valu": [0, 5], "secret_entry_text_valu": 0, "secret_entry_2": 0, "binary_valu": [0, 5], "secret_entry_binary_valu": 0, "encod": 0, "done": [0, 5], "new_secret": 0, "resourc": [0, 3, 5], "print": [0, 3, 5], "id": [0, 3, 5], "deactiv": [0, 3, 5], "get_secret": [0, 3], "e6qxxxxxxxxxx": 0, "statu": [0, 3, 5], "payload": [0, 3, 5], "version_id": [0, 3, 5], "current_vers": [0, 5], "option": [0, 3, 5], "default": [0, 3, 5], "entri": [0, 3, 5], "secretpayloadentri": [0, 5], "object": [0, 3], "direct": 0, "return": [0, 3, 5], "mask": [0, 3, 5], "valu": [0, 3, 5], "like": [0, 3, 5], "reveal_text_valu": [0, 3, 5], "similar": [0, 3, 5], "get_secret_valu": 0, "inewsecretvers": [0, 3, 5], "e6qxxxxxxxxxxxx": 0, "add_vers": [0, 5], "descript": [0, 3, 5], "base_version_id": [0, 5], "payload_entri": [0, 5], "altern": 0, "add_secret_vers": [0, 3], "secret_id": [0, 3, 5], "list_secret": [0, 3], "b1xxxxxxxxxx": 0, "iter": [0, 3, 5], "true": [0, 3, 5], "activ": [0, 3, 5], "list_vers": [0, 5], "fals": [0, 3, 5], "pagin": [0, 3], "next_page_token": [0, 3, 5], "schedule_version_destruct": [0, 5], "cancel_version_destruct": [0, 5], "activate_secret": [0, 3], "auth_head": [0, 3], "cancel_secret_version_destruct": [0, 3], "deactivate_secret": [0, 3], "delete_secret": [0, 3], "get_secret_payload": [0, 3], "list_secret_access_bind": [0, 3], "list_secret_oper": [0, 3], "list_secret_vers": [0, 3], "schedule_secret_version_destruct": [0, 3], "set_secret_access_bind": [0, 3], "update_secret": [0, 3], "update_secret_access_bind": [0, 3], "yandexauthcli": [0, 3], "adapt": [0, 3], "get_iam_token": [0, 3], "model": 0, "domain": 0, "secretvers": [0, 3, 5], "secretpayload": [0, 3, 5], "upsert": 0, "iupdatesecret": [0, 3, 5], "common": 0, "yandexclouderror": [0, 3, 5], "iamtokenrespons": [0, 5], "except": [0, 3], "abstract": 0, "index": 0, "come": [1, 2, 4], "soon": [1, 2, 4], "class": 3, "auth_client": 3, "_auth": 3, "_adapt": 3, "httpadapt": 3, "lockbox_base_url": 3, "none": [3, 5], "payload_lockbox_base_url": 3, "base": 3, "abstractyandexlockboxcli": 3, "yandex": [3, 5], "lockbox": [3, 5], "secret": [3, 5], "vault": 3, "paramet": [3, 5], "type": [3, 5], "abstractyandexauthcli": 3, "implement": 3, "abstracthttpadapt": 3, "commun": 3, "cloud": [3, 5], "str": [3, 5], "url": 3, "without": 3, "auth_base_url": 3, "all": [3, 5], "e": 3, "look": 3, "To": 3, "get": [3, 5], "real": [3, 5], "call": [3, 5], "inject": 3, "method": [3, 5], "reveal_binary_valu": [3, 5], "usag": [3, 5], "from": [3, 5], "y0_agaexxxxxxxxxxxxxxxxxxxxxxxxx": 3, "e6xxxxxxxxxxxxxxxx": 3, "try": 3, "mykei": 3, "keyerror": 3, "invalid": 3, "foo": 3, "exist": [3, 5], "rais": 3, "gener": 3, "sa": 3, "creat": [3, 5], "output": 3, "infil": 3, "load": 3, "raise_for_statu": 3, "specifi": [3, 5], "indentifi": 3, "bool": [3, 5], "instead": 3, "throw": 3, "oper": [3, 5], "version": [3, 5], "add": [3, 5], "new": [3, 5], "previou": 3, "one": 3, "A": [3, 5], "properti": [3, 5], "dict": [3, 5], "header": 3, "cancel": [3, 5], "previous": 3, "schedul": [3, 5], "destruct": [3, 5], "hasn": 3, "t": [3, 5], "been": 3, "destroi": 3, "yet": 3, "folder": 3, "delet": [3, 5], "identifi": 3, "arg": 3, "kwarg": [3, 5], "Not": 3, "readi": 3, "page_s": [3, 5], "100": [3, 5], "page_token": [3, 5], "retriev": 3, "int": [3, 5], "The": [3, 5], "maximum": 3, "number": 3, "result": 3, "per": 3, "page": [3, 5], "avail": [3, 5], "larger": 3, "than": 3, "next": [3, 5], "subsequ": 3, "1000": 3, "data": [3, 5], "union": [3, 5], "secretversionslist": [3, 5], "secretslist": [3, 5], "pending_period": [3, 5], "604800": [3, 5], "time": 3, "interv": 3, "second": 3, "between": 3, "actual": 3, "7": 3, "dai": 3, "field": [3, 5], "which": 3, "attribut": [3, 5], "go": 3, "comma": [3, 5], "separ": [3, 5], "off": 3, "onli": 3, "chang": 3, "other": [3, 5], "left": 3, "untouch": 3, "updatemask": [3, 5], "wa": 3, "sent": 3, "": 3, "reset": 3, "most": 3, "null": 3, "provid": [3, 5], "an": [3, 5], "up": 3, "date": 3, "synchron": 3, "mode": 3, "backward": 3, "compat": 3, "cacheabl": 3, "memori": 3, "instanc": 3, "complet": 5, "inform": 5, "about": 5, "aggreg": 5, "have": 5, "command": 5, "manag": 5, "pydant": 5, "_model": 5, "root": 5, "repres": 5, "contain": 5, "manipul": 5, "basic": 5, "secret_payload": 5, "my_entri": 5, "show": 5, "old": 5, "new_data": 5, "update_mask": 5, "update_oper": 5, "refresh": 5, "created_at": 5, "datetim": 5, "deletion_protect": 5, "kms_key_id": 5, "label": 5, "requir": 5, "alia": 5, "createdat": 5, "currentvers": 5, "deletionprotect": 5, "folderid": 5, "kmskeyid": 5, "unknown": 5, "shortcut": 5, "destroy_at": 5, "payload_entry_kei": 5, "destroyat": 5, "payloadentrykei": 5, "secretid": 5, "descruct": 5, "versionid": 5, "ani": 5, "secretstr": 5, "secretbyt": 5, "binaryvalu": 5, "textvalu": 5, "reveal": 5, "binari": 5, "byte": 5, "text": 5, "when": 5, "point": 5, "interfac": 5, "design": 5, "insid": 5, "version_descript": 5, "versiondescript": 5, "versionpayloadentri": 5, "baseversionid": 5, "payloadentri": 5, "relat": 5, "created_bi": 5, "error": 5, "metadata": 5, "modified_at": 5, "respons": 5, "createdbi": 5, "modifiedat": 5, "possibl": 5, "otherwis": 5, "code": 5, "detail": 5, "messag": 5, "error_typ": 5, "rpcerror": 5, "expires_at": 5, "expiresat": 5, "iamtoken": 5}, "objects": {"yc_lockbox": [[3, 0, 1, "", "YandexLockboxClient"]], "yc_lockbox.YandexLockboxClient": [[3, 1, 1, "", "activate_secret"], [3, 1, 1, "", "add_secret_version"], [3, 2, 1, "", "auth_headers"], [3, 1, 1, "", "cancel_secret_version_destruction"], [3, 1, 1, "", "create_secret"], [3, 1, 1, "", "deactivate_secret"], [3, 1, 1, "", "delete_secret"], [3, 1, 1, "", "get_secret"], [3, 1, 1, "", "get_secret_payload"], [3, 1, 1, "", "list_secret_access_bindings"], [3, 1, 1, "", "list_secret_operations"], [3, 1, 1, "", "list_secret_versions"], [3, 1, 1, "", "list_secrets"], [3, 1, 1, "", "schedule_secret_version_destruction"], [3, 1, 1, "", "set_secret_access_bindings"], [3, 1, 1, "", "update_secret"], [3, 1, 1, "", "update_secret_access_bindings"]], "yc_lockbox._auth": [[3, 0, 1, "", "YandexAuthClient"]], "yc_lockbox._auth.YandexAuthClient": [[3, 2, 1, "", "adapter"], [3, 1, 1, "", "get_iam_token"]], "yc_lockbox._models": [[5, 3, 1, "", "INewSecret"], [5, 3, 1, "", "INewSecretPayloadEntry"], [5, 3, 1, "", "INewSecretVersion"], [5, 3, 1, "", "IUpdateSecret"], [5, 3, 1, "", "IamTokenResponse"], [5, 3, 1, "", "Operation"], [5, 3, 1, "", "Secret"], [5, 3, 1, "", "SecretPayload"], [5, 3, 1, "", "SecretPayloadEntry"], [5, 3, 1, "", "SecretVersion"], [5, 3, 1, "", "SecretVersionsList"], [5, 3, 1, "", "SecretsList"], [5, 3, 1, "", "YandexCloudError"]], "yc_lockbox._models.INewSecret": [[5, 4, 1, "", "deletion_protection"], [5, 4, 1, "", "description"], [5, 4, 1, "", "folder_id"], [5, 4, 1, "", "kms_key_id"], [5, 4, 1, "", "labels"], [5, 4, 1, "", "name"], [5, 4, 1, "", "version_description"], [5, 4, 1, "", "version_payload_entries"]], "yc_lockbox._models.INewSecretPayloadEntry": [[5, 4, 1, "", "binary_value"], [5, 4, 1, "", "key"], [5, 4, 1, "", "text_value"]], "yc_lockbox._models.INewSecretVersion": [[5, 4, 1, "", "base_version_id"], [5, 4, 1, "", "description"], [5, 4, 1, "", "payload_entries"]], "yc_lockbox._models.IUpdateSecret": [[5, 4, 1, "", "deletion_protection"], [5, 4, 1, "", "description"], [5, 4, 1, "", "labels"], [5, 4, 1, "", "name"], [5, 4, 1, "", "update_mask"]], "yc_lockbox._models.IamTokenResponse": [[5, 4, 1, "", "expires_at"], [5, 4, 1, "", "token"]], "yc_lockbox._models.Operation": [[5, 4, 1, "", "created_at"], [5, 4, 1, "", "created_by"], [5, 4, 1, "", "description"], [5, 4, 1, "", "done"], [5, 4, 1, "", "error"], [5, 4, 1, "", "id"], [5, 4, 1, "", "metadata"], [5, 4, 1, "", "modified_at"], [5, 2, 1, "", "resource"], [5, 4, 1, "", "response"]], "yc_lockbox._models.Secret": [[5, 1, 1, "", "activate"], [5, 1, 1, "", "add_version"], [5, 1, 1, "", "cancel_version_destruction"], [5, 4, 1, "", "created_at"], [5, 4, 1, "", "current_version"], [5, 1, 1, "", "deactivate"], [5, 1, 1, "", "delete"], [5, 4, 1, "", "deletion_protection"], [5, 4, 1, "", "description"], [5, 4, 1, "", "folder_id"], [5, 4, 1, "", "id"], [5, 4, 1, "", "kms_key_id"], [5, 4, 1, "", "labels"], [5, 1, 1, "", "list_versions"], [5, 4, 1, "", "name"], [5, 1, 1, "", "payload"], [5, 1, 1, "", "refresh"], [5, 1, 1, "", "schedule_version_destruction"], [5, 4, 1, "", "status"], [5, 1, 1, "", "update"]], "yc_lockbox._models.SecretPayload": [[5, 4, 1, "", "entries"], [5, 1, 1, "", "get"], [5, 4, 1, "", "version_id"]], "yc_lockbox._models.SecretPayloadEntry": [[5, 4, 1, "", "binary_value"], [5, 4, 1, "", "key"], [5, 1, 1, "", "reveal_binary_value"], [5, 1, 1, "", "reveal_text_value"], [5, 4, 1, "", "text_value"]], "yc_lockbox._models.SecretVersion": [[5, 1, 1, "", "cancel_version_destruction"], [5, 4, 1, "", "created_at"], [5, 4, 1, "", "description"], [5, 4, 1, "", "destroy_at"], [5, 4, 1, "", "id"], [5, 1, 1, "", "payload"], [5, 4, 1, "", "payload_entry_keys"], [5, 1, 1, "", "schedule_version_destruction"], [5, 4, 1, "", "secret_id"], [5, 4, 1, "", "status"]], "yc_lockbox._models.SecretVersionsList": [[5, 4, 1, "", "versions"]], "yc_lockbox._models.SecretsList": [[5, 4, 1, "", "secrets"]], "yc_lockbox._models.YandexCloudError": [[5, 4, 1, "", "code"], [5, 4, 1, "", "details"], [5, 2, 1, "", "error_type"], [5, 4, 1, "", "message"]]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:property", "3": "py:pydantic_model", "4": "py:pydantic_field"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "property", "Python property"], "3": ["py", "pydantic_model", "Python model"], "4": ["py", "pydantic_field", "Python field"]}, "titleterms": {"yandex": 0, "lockbox": 0, "python": 0, "client": [0, 3], "document": 0, "instal": 0, "quick": 0, "start": 0, "creat": 0, "new": 0, "secret": 0, "get": 0, "from": 0, "add": 0, "version": 0, "other": 0, "oper": 0, "modul": 0, "content": 0, "indic": 0, "tabl": 0, "abstract": 1, "adapt": 2, "except": 4, "model": 5, "object": 5, "domain": 5, "pagin": 5, "upsert": 5, "common": 5}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"Yandex Lockbox Python client documentation": [[0, "yandex-lockbox-python-client-documentation"]], "Installation": [[0, "installation"]], "Quick start": [[0, "quick-start"]], "Create a new secret": [[0, "create-a-new-secret"]], "Get secret from Lockbox": [[0, "get-secret-from-lockbox"]], "Add new version of secret": [[0, "add-new-version-of-secret"]], "Other operations with secret": [[0, "other-operations-with-secret"]], "Modules": [[0, "modules"]], "Content:": [[0, null]], "Indices and tables": [[0, "indices-and-tables"]], "Abstracts": [[1, "abstracts"]], "Adapters": [[2, "adapters"]], "Client": [[3, "client"]], "Exceptions": [[4, "exceptions"]], "Models & objects": [[5, "models-objects"]], "Domain models": [[5, "domain-models"]], "Paginated models": [[5, "paginated-models"]], "Upsert models": [[5, "upsert-models"]], "Common models": [[5, "common-models"]]}, "indexentries": {"yandexauthclient (class in yc_lockbox._auth)": [[3, "yc_lockbox._auth.YandexAuthClient"]], "yandexlockboxclient (class in yc_lockbox)": [[3, "yc_lockbox.YandexLockboxClient"]], "activate_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.activate_secret"]], "adapter (yc_lockbox._auth.yandexauthclient property)": [[3, "yc_lockbox._auth.YandexAuthClient.adapter"]], "add_secret_version() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.add_secret_version"]], "auth_headers (yc_lockbox.yandexlockboxclient property)": [[3, "yc_lockbox.YandexLockboxClient.auth_headers"]], "cancel_secret_version_destruction() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.cancel_secret_version_destruction"]], "create_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.create_secret"]], "deactivate_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.deactivate_secret"]], "delete_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.delete_secret"]], "get_iam_token() (yc_lockbox._auth.yandexauthclient method)": [[3, "yc_lockbox._auth.YandexAuthClient.get_iam_token"]], "get_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.get_secret"]], "get_secret_payload() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.get_secret_payload"]], "list_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_access_bindings"]], "list_secret_operations() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_operations"]], "list_secret_versions() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_versions"]], "list_secrets() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secrets"]], "schedule_secret_version_destruction() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.schedule_secret_version_destruction"]], "set_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.set_secret_access_bindings"]], "update_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.update_secret"]], "update_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.update_secret_access_bindings"]], "activate() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.activate"]], "add_version() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.add_version"]], "base_version_id (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.base_version_id"]], "binary_value (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.binary_value"]], "binary_value (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.binary_value"]], "cancel_version_destruction() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.cancel_version_destruction"]], "cancel_version_destruction() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.cancel_version_destruction"]], "code (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.code"]], "created_at (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.created_at"]], "created_at (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.created_at"]], "created_at (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.created_at"]], "created_by (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.created_by"]], "current_version (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.current_version"]], "deactivate() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.deactivate"]], "delete() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.delete"]], "deletion_protection (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.deletion_protection"]], "deletion_protection (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.deletion_protection"]], "deletion_protection (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.deletion_protection"]], "description (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.description"]], "description (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.description"]], "description (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.description"]], "description (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.description"]], "description (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.description"]], "description (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.description"]], "destroy_at (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.destroy_at"]], "details (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.details"]], "done (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.done"]], "entries (yc_lockbox._models.secretpayload attribute)": [[5, "yc_lockbox._models.SecretPayload.entries"]], "error (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.error"]], "error_type (yc_lockbox._models.yandexclouderror property)": [[5, "yc_lockbox._models.YandexCloudError.error_type"]], "expires_at (yc_lockbox._models.iamtokenresponse attribute)": [[5, "yc_lockbox._models.IamTokenResponse.expires_at"]], "folder_id (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.folder_id"]], "folder_id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.folder_id"]], "get() (yc_lockbox._models.secretpayload method)": [[5, "yc_lockbox._models.SecretPayload.get"]], "id (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.id"]], "id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.id"]], "id (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.id"]], "key (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.key"]], "key (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.key"]], "kms_key_id (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.kms_key_id"]], "kms_key_id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.kms_key_id"]], "labels (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.labels"]], "labels (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.labels"]], "labels (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.labels"]], "list_versions() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.list_versions"]], "message (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.message"]], "metadata (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.metadata"]], "modified_at (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.modified_at"]], "name (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.name"]], "name (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.name"]], "name (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.name"]], "payload() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.payload"]], "payload() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.payload"]], "payload_entries (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.payload_entries"]], "payload_entry_keys (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.payload_entry_keys"]], "refresh() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.refresh"]], "resource (yc_lockbox._models.operation property)": [[5, "yc_lockbox._models.Operation.resource"]], "response (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.response"]], "reveal_binary_value() (yc_lockbox._models.secretpayloadentry method)": [[5, "yc_lockbox._models.SecretPayloadEntry.reveal_binary_value"]], "reveal_text_value() (yc_lockbox._models.secretpayloadentry method)": [[5, "yc_lockbox._models.SecretPayloadEntry.reveal_text_value"]], "schedule_version_destruction() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.schedule_version_destruction"]], "schedule_version_destruction() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.schedule_version_destruction"]], "secret_id (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.secret_id"]], "secrets (yc_lockbox._models.secretslist attribute)": [[5, "yc_lockbox._models.SecretsList.secrets"]], "status (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.status"]], "status (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.status"]], "text_value (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.text_value"]], "text_value (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.text_value"]], "token (yc_lockbox._models.iamtokenresponse attribute)": [[5, "yc_lockbox._models.IamTokenResponse.token"]], "update() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.update"]], "update_mask (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.update_mask"]], "version_description (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.version_description"]], "version_id (yc_lockbox._models.secretpayload attribute)": [[5, "yc_lockbox._models.SecretPayload.version_id"]], "version_payload_entries (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.version_payload_entries"]], "versions (yc_lockbox._models.secretversionslist attribute)": [[5, "yc_lockbox._models.SecretVersionsList.versions"]]}}) \ No newline at end of file +Search.setIndex({"docnames": ["index", "pages/abstracts", "pages/adapters", "pages/clients", "pages/exceptions", "pages/models"], "filenames": ["index.rst", "pages/abstracts.rst", "pages/adapters.rst", "pages/clients.rst", "pages/exceptions.rst", "pages/models.rst"], "titles": ["Yandex Lockbox Python client documentation", "Abstracts", "Adapters", "Client", "Exceptions", "Models & objects"], "terms": {"releas": 0, "v": 0, "0": [0, 3], "2": 0, "thi": [0, 3, 5], "librari": 0, "i": [0, 3, 5], "simpl": [0, 3], "work": [0, 3], "over": 0, "rest": [0, 3], "api": [0, 3], "simplifi": 0, "allow": [0, 3], "you": [0, 3], "them": 0, "oop": 0, "paradigm": 0, "support": 0, "3": 0, "10": 0, "11": 0, "12": 0, "depend": 0, "pydanticv2": 0, "crypthographi": 0, "pyjwt": 0, "request": [0, 3], "current": [0, 5], "follow": 0, "ar": [0, 3, 5], "list": [0, 3, 5], "access": 0, "bind": 0, "set": [0, 3], "updat": [0, 3, 5], "pip": 0, "yc": [0, 3], "also": [0, 5], "can": [0, 3], "sourc": [0, 3, 5], "git": 0, "clone": 0, "http": [0, 3], "github": 0, "com": 0, "akimrx": 0, "cd": 0, "make": [0, 3], "authent": [0, 3], "via": [0, 3], "your": [0, 3], "oauth": [0, 3], "token": [0, 3, 5], "yc_lockbox": [0, 3, 5], "import": [0, 3], "yandexlockboxcli": [0, 3], "y0_xxxxxxxxxxxx": 0, "iam": [0, 3], "If": [0, 3], "pass": [0, 3], "credenti": [0, 3], "need": [0, 3], "take": [0, 3], "care": [0, 3], "fresh": [0, 3], "yourself": [0, 3], "t1": 0, "xxxxxx": 0, "xxxxxxx": 0, "us": [0, 3, 5], "servic": [0, 3], "account": [0, 3], "kei": [0, 3, 5], "json": [0, 3], "open": [0, 3], "path": [0, 3], "r": [0, 3], "keyfil": 0, "read": 0, "inewsecret": [0, 3, 5], "inewsecretpayloadentri": [0, 5], "oauth_or_iam_token": 0, "create_secret_oper": 0, "create_secret": [0, 3], "folder_id": [0, 3, 5], "b1xxxxxxxxxxxxxx": 0, "name": [0, 3, 5], "my": [0, 3, 5], "version_payload_entri": [0, 5], "secret_entry_1": 0, "text_valu": [0, 5], "secret_entry_text_valu": 0, "secret_entry_2": 0, "binary_valu": [0, 5], "secret_entry_binary_valu": 0, "encod": 0, "done": [0, 5], "new_secret": 0, "resourc": [0, 3, 5], "print": [0, 3, 5], "id": [0, 3, 5], "deactiv": [0, 3, 5], "get_secret": [0, 3], "e6qxxxxxxxxxx": 0, "statu": [0, 3, 5], "payload": [0, 3, 5], "version_id": [0, 3, 5], "current_vers": [0, 3, 5], "option": [0, 3, 5], "default": [0, 3, 5], "entri": [0, 3, 5], "secretpayloadentri": [0, 5], "object": [0, 3], "direct": 0, "return": [0, 3, 5], "mask": [0, 3, 5], "valu": [0, 3, 5], "like": [0, 3, 5], "reveal_text_valu": [0, 3, 5], "similar": [0, 3, 5], "get_secret_valu": 0, "inewsecretvers": [0, 3, 5], "e6qxxxxxxxxxxxx": 0, "add_vers": [0, 5], "descript": [0, 3, 5], "base_version_id": [0, 5], "payload_entri": [0, 5], "altern": 0, "add_secret_vers": [0, 3], "secret_id": [0, 3, 5], "list_secret": [0, 3], "b1xxxxxxxxxx": 0, "iter": [0, 3, 5], "true": [0, 3, 5], "activ": [0, 3, 5], "list_vers": [0, 3, 5], "fals": [0, 3, 5], "pagin": [0, 3], "next_page_token": [0, 3, 5], "schedule_version_destruct": [0, 3, 5], "cancel_version_destruct": [0, 5], "The": [0, 3, 5], "asynchron": [0, 3], "aiohttp": 0, "signatur": 0, "method": [0, 3, 5], "doe": 0, "differ": 0, "synchron": [0, 3], "implement": [0, 3], "just": 0, "asyncyandexlockboxcli": [0, 3], "yandexlockboxfacad": [0, 3], "enable_async": [0, 3], "exampl": 0, "usag": [0, 3, 5], "await": [0, 3], "secret_vers": [0, 3], "activate_secret": [0, 3], "auth_head": [0, 3], "cancel_secret_version_destruct": [0, 3], "deactivate_secret": [0, 3], "delete_secret": [0, 3], "get_secret_payload": [0, 3], "list_secret_access_bind": [0, 3], "list_secret_oper": [0, 3], "list_secret_vers": [0, 3], "schedule_secret_version_destruct": [0, 3], "set_secret_access_bind": [0, 3], "update_secret": [0, 3], "update_secret_access_bind": [0, 3], "yandexauthcli": [0, 3], "adapt": [0, 3], "get_iam_token": [0, 3], "model": 0, "domain": 0, "secretvers": [0, 3, 5], "secretpayload": [0, 3, 5], "upsert": 0, "iupdatesecret": [0, 3, 5], "common": 0, "yandexclouderror": [0, 3, 5], "iamtokenrespons": [0, 5], "except": [0, 3], "abstract": 0, "index": 0, "come": [1, 2, 4], "soon": [1, 2, 4], "class": 3, "auth_client": 3, "_auth": 3, "lockbox_base_url": 3, "none": [3, 5], "payload_lockbox_base_url": 3, "base": 3, "A": [3, 5], "facad": 3, "encapsul": 3, "logic": 3, "oper": [3, 5], "provid": [3, 5], "uniform": 3, "properti": [3, 5], "abstractyandexlockboxcli": 3, "initi": 3, "lockbox": [3, 5], "_adapt": 3, "httpadapt": 3, "yandex": [3, 5], "secret": [3, 5], "vault": 3, "paramet": [3, 5], "type": [3, 5], "abstractyandexauthcli": 3, "abstracthttpadapt": 3, "commun": 3, "cloud": [3, 5], "str": [3, 5], "url": 3, "without": 3, "auth_base_url": 3, "all": [3, 5], "e": 3, "look": 3, "To": 3, "get": [3, 5], "real": [3, 5], "call": [3, 5], "inject": 3, "reveal_binary_valu": [3, 5], "from": [3, 5], "y0_agaexxxxxxxxxxxxxxxxxxxxxxxxx": 3, "e6xxxxxxxxxxxxxxxx": 3, "try": 3, "mykei": 3, "keyerror": 3, "invalid": 3, "foo": 3, "exist": [3, 5], "rais": 3, "gener": 3, "sa": 3, "creat": [3, 5], "output": 3, "infil": 3, "load": 3, "raise_for_statu": 3, "specifi": [3, 5], "indentifi": 3, "bool": [3, 5], "instead": 3, "throw": 3, "version": [3, 5], "add": [3, 5], "new": [3, 5], "previou": 3, "one": 3, "dict": [3, 5], "header": 3, "cancel": [3, 5], "previous": 3, "schedul": [3, 5], "destruct": [3, 5], "hasn": 3, "t": [3, 5], "been": 3, "destroi": 3, "yet": 3, "folder": 3, "delet": [3, 5], "identifi": 3, "arg": 3, "kwarg": [3, 5], "Not": 3, "readi": 3, "page_s": [3, 5], "100": [3, 5], "page_token": [3, 5], "retriev": 3, "int": [3, 5], "maximum": 3, "number": 3, "result": 3, "per": 3, "page": [3, 5], "avail": [3, 5], "larger": 3, "than": 3, "next": [3, 5], "subsequ": 3, "1000": 3, "data": [3, 5], "union": [3, 5], "secretversionslist": [3, 5], "secretslist": [3, 5], "pending_period": [3, 5], "604800": [3, 5], "time": 3, "interv": 3, "second": 3, "between": 3, "actual": 3, "7": 3, "dai": 3, "field": [3, 5], "which": 3, "attribut": [3, 5], "go": 3, "comma": [3, 5], "separ": [3, 5], "off": 3, "onli": 3, "chang": 3, "other": [3, 5], "left": 3, "untouch": 3, "updatemask": [3, 5], "wa": 3, "sent": 3, "": 3, "reset": 3, "most": 3, "null": 3, "asynchttpadapt": 3, "same": 3, "async": 3, "coroutin": 3, "ani": [3, 5], "asyncgener": [3, 5], "an": [3, 5], "up": 3, "date": 3, "mode": 3, "backward": 3, "compat": 3, "cacheabl": 3, "memori": 3, "instanc": 3, "complet": 5, "inform": 5, "about": 5, "aggreg": 5, "have": 5, "command": 5, "manag": 5, "pydant": 5, "_model": 5, "root": 5, "repres": 5, "contain": 5, "manipul": 5, "basic": 5, "secret_payload": 5, "my_entri": 5, "show": 5, "old": 5, "new_data": 5, "update_mask": 5, "update_oper": 5, "refresh": 5, "created_at": 5, "datetim": 5, "deletion_protect": 5, "kms_key_id": 5, "label": 5, "requir": 5, "alia": 5, "createdat": 5, "currentvers": 5, "deletionprotect": 5, "folderid": 5, "kmskeyid": 5, "unknown": 5, "shortcut": 5, "destroy_at": 5, "payload_entry_kei": 5, "destroyat": 5, "payloadentrykei": 5, "secretid": 5, "descruct": 5, "versionid": 5, "secretstr": 5, "secretbyt": 5, "binaryvalu": 5, "textvalu": 5, "reveal": 5, "binari": 5, "byte": 5, "text": 5, "when": 5, "point": 5, "interfac": 5, "design": 5, "insid": 5, "version_descript": 5, "versiondescript": 5, "versionpayloadentri": 5, "baseversionid": 5, "payloadentri": 5, "relat": 5, "created_bi": 5, "error": 5, "metadata": 5, "modified_at": 5, "respons": 5, "createdbi": 5, "modifiedat": 5, "possibl": 5, "otherwis": 5, "code": 5, "detail": 5, "messag": 5, "error_typ": 5, "rpcerror": 5, "expires_at": 5, "expiresat": 5, "iamtoken": 5}, "objects": {"yc_lockbox": [[3, 0, 1, "", "AsyncYandexLockboxClient"], [3, 0, 1, "", "YandexLockboxClient"], [3, 0, 1, "", "YandexLockboxFacade"]], "yc_lockbox.AsyncYandexLockboxClient": [[3, 1, 1, "", "activate_secret"], [3, 1, 1, "", "add_secret_version"], [3, 2, 1, "", "auth_headers"], [3, 1, 1, "", "cancel_secret_version_destruction"], [3, 1, 1, "", "create_secret"], [3, 1, 1, "", "deactivate_secret"], [3, 1, 1, "", "delete_secret"], [3, 3, 1, "", "enable_async"], [3, 1, 1, "", "get_secret"], [3, 1, 1, "", "get_secret_payload"], [3, 1, 1, "", "list_secret_access_bindings"], [3, 1, 1, "", "list_secret_operations"], [3, 1, 1, "", "list_secret_versions"], [3, 1, 1, "", "list_secrets"], [3, 1, 1, "", "schedule_secret_version_destruction"], [3, 1, 1, "", "set_secret_access_bindings"], [3, 1, 1, "", "update_secret"], [3, 1, 1, "", "update_secret_access_bindings"]], "yc_lockbox.YandexLockboxClient": [[3, 1, 1, "", "activate_secret"], [3, 1, 1, "", "add_secret_version"], [3, 2, 1, "", "auth_headers"], [3, 1, 1, "", "cancel_secret_version_destruction"], [3, 1, 1, "", "create_secret"], [3, 1, 1, "", "deactivate_secret"], [3, 1, 1, "", "delete_secret"], [3, 1, 1, "", "get_secret"], [3, 1, 1, "", "get_secret_payload"], [3, 1, 1, "", "list_secret_access_bindings"], [3, 1, 1, "", "list_secret_operations"], [3, 1, 1, "", "list_secret_versions"], [3, 1, 1, "", "list_secrets"], [3, 1, 1, "", "schedule_secret_version_destruction"], [3, 1, 1, "", "set_secret_access_bindings"], [3, 1, 1, "", "update_secret"], [3, 1, 1, "", "update_secret_access_bindings"]], "yc_lockbox.YandexLockboxFacade": [[3, 2, 1, "", "client"]], "yc_lockbox._auth": [[3, 0, 1, "", "YandexAuthClient"]], "yc_lockbox._auth.YandexAuthClient": [[3, 2, 1, "", "adapter"], [3, 1, 1, "", "get_iam_token"]], "yc_lockbox._models": [[5, 4, 1, "", "INewSecret"], [5, 4, 1, "", "INewSecretPayloadEntry"], [5, 4, 1, "", "INewSecretVersion"], [5, 4, 1, "", "IUpdateSecret"], [5, 4, 1, "", "IamTokenResponse"], [5, 4, 1, "", "Operation"], [5, 4, 1, "", "Secret"], [5, 4, 1, "", "SecretPayload"], [5, 4, 1, "", "SecretPayloadEntry"], [5, 4, 1, "", "SecretVersion"], [5, 4, 1, "", "SecretVersionsList"], [5, 4, 1, "", "SecretsList"], [5, 4, 1, "", "YandexCloudError"]], "yc_lockbox._models.INewSecret": [[5, 5, 1, "", "deletion_protection"], [5, 5, 1, "", "description"], [5, 5, 1, "", "folder_id"], [5, 5, 1, "", "kms_key_id"], [5, 5, 1, "", "labels"], [5, 5, 1, "", "name"], [5, 5, 1, "", "version_description"], [5, 5, 1, "", "version_payload_entries"]], "yc_lockbox._models.INewSecretPayloadEntry": [[5, 5, 1, "", "binary_value"], [5, 5, 1, "", "key"], [5, 5, 1, "", "text_value"]], "yc_lockbox._models.INewSecretVersion": [[5, 5, 1, "", "base_version_id"], [5, 5, 1, "", "description"], [5, 5, 1, "", "payload_entries"]], "yc_lockbox._models.IUpdateSecret": [[5, 5, 1, "", "deletion_protection"], [5, 5, 1, "", "description"], [5, 5, 1, "", "labels"], [5, 5, 1, "", "name"], [5, 5, 1, "", "update_mask"]], "yc_lockbox._models.IamTokenResponse": [[5, 5, 1, "", "expires_at"], [5, 5, 1, "", "token"]], "yc_lockbox._models.Operation": [[5, 5, 1, "", "created_at"], [5, 5, 1, "", "created_by"], [5, 5, 1, "", "description"], [5, 5, 1, "", "done"], [5, 5, 1, "", "error"], [5, 5, 1, "", "id"], [5, 5, 1, "", "metadata"], [5, 5, 1, "", "modified_at"], [5, 2, 1, "", "resource"], [5, 5, 1, "", "response"]], "yc_lockbox._models.Secret": [[5, 1, 1, "", "activate"], [5, 1, 1, "", "add_version"], [5, 1, 1, "", "cancel_version_destruction"], [5, 5, 1, "", "created_at"], [5, 5, 1, "", "current_version"], [5, 1, 1, "", "deactivate"], [5, 1, 1, "", "delete"], [5, 5, 1, "", "deletion_protection"], [5, 5, 1, "", "description"], [5, 5, 1, "", "folder_id"], [5, 5, 1, "", "id"], [5, 5, 1, "", "kms_key_id"], [5, 5, 1, "", "labels"], [5, 1, 1, "", "list_versions"], [5, 5, 1, "", "name"], [5, 1, 1, "", "payload"], [5, 1, 1, "", "refresh"], [5, 1, 1, "", "schedule_version_destruction"], [5, 5, 1, "", "status"], [5, 1, 1, "", "update"]], "yc_lockbox._models.SecretPayload": [[5, 5, 1, "", "entries"], [5, 1, 1, "", "get"], [5, 5, 1, "", "version_id"]], "yc_lockbox._models.SecretPayloadEntry": [[5, 5, 1, "", "binary_value"], [5, 5, 1, "", "key"], [5, 1, 1, "", "reveal_binary_value"], [5, 1, 1, "", "reveal_text_value"], [5, 5, 1, "", "text_value"]], "yc_lockbox._models.SecretVersion": [[5, 1, 1, "", "cancel_version_destruction"], [5, 5, 1, "", "created_at"], [5, 5, 1, "", "description"], [5, 5, 1, "", "destroy_at"], [5, 5, 1, "", "id"], [5, 1, 1, "", "payload"], [5, 5, 1, "", "payload_entry_keys"], [5, 1, 1, "", "schedule_version_destruction"], [5, 5, 1, "", "secret_id"], [5, 5, 1, "", "status"]], "yc_lockbox._models.SecretVersionsList": [[5, 5, 1, "", "versions"]], "yc_lockbox._models.SecretsList": [[5, 5, 1, "", "secrets"]], "yc_lockbox._models.YandexCloudError": [[5, 5, 1, "", "code"], [5, 5, 1, "", "details"], [5, 2, 1, "", "error_type"], [5, 5, 1, "", "message"]]}, "objtypes": {"0": "py:class", "1": "py:method", "2": "py:property", "3": "py:attribute", "4": "py:pydantic_model", "5": "py:pydantic_field"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "method", "Python method"], "2": ["py", "property", "Python property"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "pydantic_model", "Python model"], "5": ["py", "pydantic_field", "Python field"]}, "titleterms": {"yandex": 0, "lockbox": 0, "python": 0, "client": [0, 3], "document": 0, "instal": 0, "quick": 0, "start": 0, "creat": 0, "new": 0, "secret": 0, "get": 0, "from": 0, "add": 0, "version": 0, "other": 0, "oper": 0, "async": 0, "mode": 0, "modul": 0, "content": 0, "indic": 0, "tabl": 0, "abstract": 1, "adapt": 2, "except": 4, "model": 5, "object": 5, "domain": 5, "pagin": 5, "upsert": 5, "common": 5}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"Yandex Lockbox Python client documentation": [[0, "yandex-lockbox-python-client-documentation"]], "Installation": [[0, "installation"]], "Quick start": [[0, "quick-start"]], "Create a new secret": [[0, "create-a-new-secret"]], "Get secret from Lockbox": [[0, "get-secret-from-lockbox"]], "Add new version of secret": [[0, "add-new-version-of-secret"]], "Other operations with secret": [[0, "other-operations-with-secret"]], "Async mode": [[0, "async-mode"]], "Modules": [[0, "modules"]], "Content:": [[0, null]], "Indices and tables": [[0, "indices-and-tables"]], "Abstracts": [[1, "abstracts"]], "Adapters": [[2, "adapters"]], "Client": [[3, "client"]], "Exceptions": [[4, "exceptions"]], "Models & objects": [[5, "models-objects"]], "Domain models": [[5, "domain-models"]], "Paginated models": [[5, "paginated-models"]], "Upsert models": [[5, "upsert-models"]], "Common models": [[5, "common-models"]]}, "indexentries": {"asyncyandexlockboxclient (class in yc_lockbox)": [[3, "yc_lockbox.AsyncYandexLockboxClient"]], "yandexauthclient (class in yc_lockbox._auth)": [[3, "yc_lockbox._auth.YandexAuthClient"]], "yandexlockboxclient (class in yc_lockbox)": [[3, "yc_lockbox.YandexLockboxClient"]], "yandexlockboxfacade (class in yc_lockbox)": [[3, "yc_lockbox.YandexLockboxFacade"]], "activate_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.activate_secret"]], "activate_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.activate_secret"]], "adapter (yc_lockbox._auth.yandexauthclient property)": [[3, "yc_lockbox._auth.YandexAuthClient.adapter"]], "add_secret_version() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.add_secret_version"]], "add_secret_version() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.add_secret_version"]], "auth_headers (yc_lockbox.asyncyandexlockboxclient property)": [[3, "yc_lockbox.AsyncYandexLockboxClient.auth_headers"]], "auth_headers (yc_lockbox.yandexlockboxclient property)": [[3, "yc_lockbox.YandexLockboxClient.auth_headers"]], "cancel_secret_version_destruction() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.cancel_secret_version_destruction"]], "cancel_secret_version_destruction() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.cancel_secret_version_destruction"]], "client (yc_lockbox.yandexlockboxfacade property)": [[3, "yc_lockbox.YandexLockboxFacade.client"]], "create_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.create_secret"]], "create_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.create_secret"]], "deactivate_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.deactivate_secret"]], "deactivate_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.deactivate_secret"]], "delete_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.delete_secret"]], "delete_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.delete_secret"]], "enable_async (yc_lockbox.asyncyandexlockboxclient attribute)": [[3, "yc_lockbox.AsyncYandexLockboxClient.enable_async"]], "get_iam_token() (yc_lockbox._auth.yandexauthclient method)": [[3, "yc_lockbox._auth.YandexAuthClient.get_iam_token"]], "get_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.get_secret"]], "get_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.get_secret"]], "get_secret_payload() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.get_secret_payload"]], "get_secret_payload() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.get_secret_payload"]], "list_secret_access_bindings() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.list_secret_access_bindings"]], "list_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_access_bindings"]], "list_secret_operations() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.list_secret_operations"]], "list_secret_operations() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_operations"]], "list_secret_versions() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.list_secret_versions"]], "list_secret_versions() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secret_versions"]], "list_secrets() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.list_secrets"]], "list_secrets() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.list_secrets"]], "schedule_secret_version_destruction() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.schedule_secret_version_destruction"]], "schedule_secret_version_destruction() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.schedule_secret_version_destruction"]], "set_secret_access_bindings() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.set_secret_access_bindings"]], "set_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.set_secret_access_bindings"]], "update_secret() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.update_secret"]], "update_secret() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.update_secret"]], "update_secret_access_bindings() (yc_lockbox.asyncyandexlockboxclient method)": [[3, "yc_lockbox.AsyncYandexLockboxClient.update_secret_access_bindings"]], "update_secret_access_bindings() (yc_lockbox.yandexlockboxclient method)": [[3, "yc_lockbox.YandexLockboxClient.update_secret_access_bindings"]], "activate() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.activate"]], "add_version() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.add_version"]], "base_version_id (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.base_version_id"]], "binary_value (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.binary_value"]], "binary_value (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.binary_value"]], "cancel_version_destruction() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.cancel_version_destruction"]], "cancel_version_destruction() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.cancel_version_destruction"]], "code (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.code"]], "created_at (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.created_at"]], "created_at (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.created_at"]], "created_at (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.created_at"]], "created_by (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.created_by"]], "current_version (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.current_version"]], "deactivate() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.deactivate"]], "delete() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.delete"]], "deletion_protection (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.deletion_protection"]], "deletion_protection (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.deletion_protection"]], "deletion_protection (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.deletion_protection"]], "description (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.description"]], "description (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.description"]], "description (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.description"]], "description (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.description"]], "description (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.description"]], "description (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.description"]], "destroy_at (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.destroy_at"]], "details (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.details"]], "done (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.done"]], "entries (yc_lockbox._models.secretpayload attribute)": [[5, "yc_lockbox._models.SecretPayload.entries"]], "error (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.error"]], "error_type (yc_lockbox._models.yandexclouderror property)": [[5, "yc_lockbox._models.YandexCloudError.error_type"]], "expires_at (yc_lockbox._models.iamtokenresponse attribute)": [[5, "yc_lockbox._models.IamTokenResponse.expires_at"]], "folder_id (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.folder_id"]], "folder_id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.folder_id"]], "get() (yc_lockbox._models.secretpayload method)": [[5, "yc_lockbox._models.SecretPayload.get"]], "id (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.id"]], "id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.id"]], "id (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.id"]], "key (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.key"]], "key (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.key"]], "kms_key_id (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.kms_key_id"]], "kms_key_id (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.kms_key_id"]], "labels (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.labels"]], "labels (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.labels"]], "labels (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.labels"]], "list_versions() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.list_versions"]], "message (yc_lockbox._models.yandexclouderror attribute)": [[5, "yc_lockbox._models.YandexCloudError.message"]], "metadata (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.metadata"]], "modified_at (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.modified_at"]], "name (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.name"]], "name (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.name"]], "name (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.name"]], "payload() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.payload"]], "payload() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.payload"]], "payload_entries (yc_lockbox._models.inewsecretversion attribute)": [[5, "yc_lockbox._models.INewSecretVersion.payload_entries"]], "payload_entry_keys (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.payload_entry_keys"]], "refresh() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.refresh"]], "resource (yc_lockbox._models.operation property)": [[5, "yc_lockbox._models.Operation.resource"]], "response (yc_lockbox._models.operation attribute)": [[5, "yc_lockbox._models.Operation.response"]], "reveal_binary_value() (yc_lockbox._models.secretpayloadentry method)": [[5, "yc_lockbox._models.SecretPayloadEntry.reveal_binary_value"]], "reveal_text_value() (yc_lockbox._models.secretpayloadentry method)": [[5, "yc_lockbox._models.SecretPayloadEntry.reveal_text_value"]], "schedule_version_destruction() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.schedule_version_destruction"]], "schedule_version_destruction() (yc_lockbox._models.secretversion method)": [[5, "yc_lockbox._models.SecretVersion.schedule_version_destruction"]], "secret_id (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.secret_id"]], "secrets (yc_lockbox._models.secretslist attribute)": [[5, "yc_lockbox._models.SecretsList.secrets"]], "status (yc_lockbox._models.secret attribute)": [[5, "yc_lockbox._models.Secret.status"]], "status (yc_lockbox._models.secretversion attribute)": [[5, "yc_lockbox._models.SecretVersion.status"]], "text_value (yc_lockbox._models.inewsecretpayloadentry attribute)": [[5, "yc_lockbox._models.INewSecretPayloadEntry.text_value"]], "text_value (yc_lockbox._models.secretpayloadentry attribute)": [[5, "yc_lockbox._models.SecretPayloadEntry.text_value"]], "token (yc_lockbox._models.iamtokenresponse attribute)": [[5, "yc_lockbox._models.IamTokenResponse.token"]], "update() (yc_lockbox._models.secret method)": [[5, "yc_lockbox._models.Secret.update"]], "update_mask (yc_lockbox._models.iupdatesecret attribute)": [[5, "yc_lockbox._models.IUpdateSecret.update_mask"]], "version_description (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.version_description"]], "version_id (yc_lockbox._models.secretpayload attribute)": [[5, "yc_lockbox._models.SecretPayload.version_id"]], "version_payload_entries (yc_lockbox._models.inewsecret attribute)": [[5, "yc_lockbox._models.INewSecret.version_payload_entries"]], "versions (yc_lockbox._models.secretversionslist attribute)": [[5, "yc_lockbox._models.SecretVersionsList.versions"]]}}) \ No newline at end of file diff --git a/docs_src/source/index.rst b/docs_src/source/index.rst index b874b1c..4a5b30f 100644 --- a/docs_src/source/index.rst +++ b/docs_src/source/index.rst @@ -225,6 +225,56 @@ Other operations with secret +Async mode +---------- + +The client supports asynchronous mode using the aiohttp library. The signature of the methods does not differ from the synchronous implementation. + + +Just import async client: + +.. code-block:: python + + from yc_lockbox import AsyncYandexLockboxClient + + lockbox = AsyncYandexLockboxClient("oauth_or_iam_token") + + + +Alternative: + +.. code-block:: python + + from yc_lockbox import YandexLockboxFacade + + lockbox = YandexLockboxFacade("oauth_or_iam_token", enable_async=True).client + + +Example usage: + +.. code-block:: python + + secret: Secret = await lockbox.get_secret("e6qxxxxxxxxxx") + payload = await secret.payload() + print(payload.entries) # list of SecretPayloadEntry objects + + # Direct access + + entry = payload["secret_entry_1"] # or payload.get("secret_entry_1") + + print(entry.text_value) # return MASKED value like *********** + print(entry.reveal_text_value()) # similar to entry.text_value.get_secret_value() + + # Async iterators + + secret_versions = await secret.list_versions(iterator=True) + + async for version in secret_versions: + if version.id != secret.current_version.id: + await version.schedule_version_destruction() + await version.cancel_version_destruction() + + Modules ------- diff --git a/docs_src/source/pages/clients.rst b/docs_src/source/pages/clients.rst index 0d6b192..7081091 100644 --- a/docs_src/source/pages/clients.rst +++ b/docs_src/source/pages/clients.rst @@ -1,12 +1,22 @@ Client ====== +.. autoclass:: yc_lockbox.YandexLockboxFacade + :members: + :undoc-members: + :show-inheritance: + .. autoclass:: yc_lockbox.YandexLockboxClient :members: :undoc-members: :show-inheritance: +.. autoclass:: yc_lockbox.AsyncYandexLockboxClient + :members: + :undoc-members: + :show-inheritance: + .. autoclass:: yc_lockbox._auth.YandexAuthClient :members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/requirements.dev.txt b/requirements.dev.txt index 7ca4bdb..66298eb 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -1,3 +1,4 @@ +aioresponses bandit==1.7.* black==24.* Faker==23.* diff --git a/tests/integration/test_async/conftest.py b/tests/integration/test_async/conftest.py new file mode 100644 index 0000000..24f9151 --- /dev/null +++ b/tests/integration/test_async/conftest.py @@ -0,0 +1,17 @@ +import pytest +from aioresponses import aioresponses + +from yc_lockbox import AsyncYandexLockboxClient + + +@pytest.fixture +def lockbox_client() -> AsyncYandexLockboxClient: + return AsyncYandexLockboxClient( + "t1.9exxlZrHlpKalJKVkM-.IvgNiLOPTh7FkZC3n6oi_y2lIc27gOByJ4QfVZKtccYso8U6MKeIZxe4LIyRosTSKEwYiZdV28C8zAIMaKcsAA" # fake, don't get your hopes up + ) + + +@pytest.fixture +def aio_requests_mocker(): + with aioresponses() as m: + yield m diff --git a/tests/integration/test_async/test_mocked_async_lockbox_client.py b/tests/integration/test_async/test_mocked_async_lockbox_client.py new file mode 100644 index 0000000..778f9d5 --- /dev/null +++ b/tests/integration/test_async/test_mocked_async_lockbox_client.py @@ -0,0 +1,908 @@ +import re +import pytest + +from aioresponses import aioresponses +from typing import Any, AsyncGenerator, AsyncIterator +from requests_mock import Mocker + +from yc_lockbox._constants import YC_LOCKBOX_BASE_URL, YC_LOCKBOX_PAYLOAD_BASE_URL +from yc_lockbox._models import ( + Operation, + Secret, + SecretVersion, + SecretPayload, + SecretPayloadEntry, + SecretsList, + SecretVersionsList, + YandexCloudError, + INewSecret, + INewSecretVersion, + INewSecretPayloadEntry, + IUpdateSecret, +) +from yc_lockbox._lockbox import AsyncYandexLockboxClient + + +@pytest.mark.parametrize( + "secret_id, url, mock_response", + [ + ( + "e6qh3v0mgnmiq995h1v9", + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qh3v0mgnmiq995h1v9:activate", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.ActivateSecretMetadata", + "secretId": "e6qh3v0mgnmiq995h1v9", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Secret", + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qii1ovrs7suo5oroe8", + "secretId": "e6qh3v0mgnmiq995h1v9", + "createdAt": "2024-03-26T07:38:41.908Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6qh3v0mgnmiq995h1v9", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T07:38:41.908Z", + "name": "test-key", + "status": "ACTIVE", + }, + "id": "e6qdrk237rdpt8sibbng", + "description": "Activate secret", + "createdAt": "2024-03-26T07:38:42.189024661Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T07:38:42.189056725Z", + }, + ) + ], +) +async def test_mocked_activate_secret( + secret_id: str, + url: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.activate_secret(secret_id) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + + assert isinstance(result.resource, Secret) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.id == secret_id + + +@pytest.mark.parametrize( + "secret_id, url, mock_response, version", + [ + ( + "e6qgq6gaei7ejteotjge", + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qgq6gaei7ejteotjge:addVersion", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.AddVersionMetadata", + "secretId": "e6qgq6gaei7ejteotjge", + "versionId": "e6qetl2mu52s8gvq0ccj", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Version", + "payloadEntryKeys": ["key1", "key2", "test_key"], + "id": "e6qetl2mu52s8gvq0ccj", + "secretId": "e6qgq6gaei7ejteotjge", + "createdAt": "2024-03-26T07:59:23.172Z", + "status": "ACTIVE", + }, + "id": "e6q67tu8pet2328ktemr", + "description": "Add version", + "createdAt": "2024-03-26T07:59:23.172687854Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T07:59:23.172723443Z", + }, + INewSecretVersion(payloadEntries=[INewSecretPayloadEntry(key="test_key", textValue="test_value")]), + ) + ], +) +async def test_mocked_add_secret_version( + secret_id: str, + url: str, + mock_response: dict[str, Any], + version: INewSecretVersion, + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.add_secret_version(secret_id, version) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + + assert isinstance(result.resource, SecretVersion) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.secret_id == secret_id + + +@pytest.mark.parametrize( + "url, mock_response, secret", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets", + { + "done": False, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.CreateSecretMetadata", + "secretId": "e6qgq6gaei7ejteotjge", + "versionId": "e6qfcrpioij83qbka9lo", + }, + "id": "e6qe3dc7j2d0555pt9ak", + "description": "Create secret", + "createdAt": "2024-03-26T07:59:22.709848334Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T07:59:22.709848334Z", + }, + INewSecret( + folder_id="b1gjpj7bq22qqqqqq7t6", + name="test-key", + version_payload_entries=[ + INewSecretPayloadEntry(key="key1", textValue="value1"), + INewSecretPayloadEntry(key="key2", binaryValue="value2".encode()), + ], + ), + ) + ], +) +async def test_mocked_create_secret( + url: str, + mock_response: dict[str, Any], + secret: INewSecret, + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.create_secret(secret) + + assert isinstance(result, Operation) + assert not result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] is not None + + assert result.resource is None + + +@pytest.mark.parametrize( + "url, secret_id, version_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qqg8aq7jum59ivv560:cancelVersionDestruction", + "e6qqg8aq7jum59ivv560", + "e6qo0aqmflbl0o00mlmd", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.CancelVersionDestructionMetadata", + "secretId": "e6qqg8aq7jum59ivv560", + "versionId": "e6qo0aqmflbl0o00mlmd", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Version", + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qo0aqmflbl0o00mlmd", + "secretId": "e6qqg8aq7jum59ivv560", + "createdAt": "2024-03-26T08:33:33.625Z", + "status": "ACTIVE", + }, + "id": "e6qe0uqinjvu5j2pmj33", + "description": "Cancel version destruction", + "createdAt": "2024-03-26T08:33:34.912533615Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T08:33:34.912558579Z", + }, + ) + ], +) +async def test_mocked_cancel_version_descruction( + url: str, + secret_id: str, + version_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.cancel_secret_version_destruction( + secret_id, version_id + ) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + assert result.metadata["versionId"] == version_id + + assert isinstance(result.resource, SecretVersion) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.id == version_id + assert result.resource.secret_id == secret_id + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6q4d0nd2nto737ak1f0:deactivate", + "e6q4d0nd2nto737ak1f0", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.DeactivateSecretMetadata", + "secretId": "e6q4d0nd2nto737ak1f0", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Secret", + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qcc4gft2aik6bmg65m", + "secretId": "e6q4d0nd2nto737ak1f0", + "createdAt": "2024-03-26T08:45:51.825Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6q4d0nd2nto737ak1f0", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T08:45:51.825Z", + "name": "test-key", + "status": "INACTIVE", + }, + "id": "e6qsntgq23adl058gmh9", + "description": "Deactivate secret", + "createdAt": "2024-03-26T08:45:52.049278552Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T08:45:52.049314259Z", + }, + ) + ], +) +async def test_mocked_deactivate_secret( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.deactivate_secret(secret_id) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + + assert isinstance(result.resource, Secret) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.id == secret_id + assert result.resource.status == "INACTIVE" + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6q8ccgvcp685ef0o85m", + "e6q8ccgvcp685ef0o85m", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.DeleteSecretMetadata", + "secretId": "e6q8ccgvcp685ef0o85m", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Secret", + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qa0570t64ao8gbjr5g", + "secretId": "e6q8ccgvcp685ef0o85m", + "createdAt": "2024-03-26T09:02:32.090Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6q8ccgvcp685ef0o85m", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:02:32.090Z", + "name": "test-key", + }, + "id": "e6qtmcjfshj46tup03qh", + "description": "Delete secret", + "createdAt": "2024-03-26T09:02:32.811030090Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T09:02:32.811066746Z", + }, + ) + ], +) +async def test_mocked_delete_secret( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.delete( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.delete_secret(secret_id) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + + assert isinstance(result.resource, Secret) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.id == secret_id + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qj7gpvimsi1igs228r", + "e6qj7gpvimsi1igs228r", + { + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qfnqdkb105a4seprsn", + "secretId": "e6qj7gpvimsi1igs228r", + "createdAt": "2024-03-26T09:07:50.981Z", + "status": "ACTIVE", + }, + "deletionProtection": True, + "id": "e6qj7gpvimsi1igs228r", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:07:50.981Z", + "name": "test-key", + "status": "ACTIVE", + }, + ) + ], +) +async def test_mocked_get_secret( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Secret = await lockbox_client.get_secret(secret_id) + + assert isinstance(result, Secret) + assert isinstance(result.current_version, SecretVersion) + assert isinstance(result.client, AsyncYandexLockboxClient) + assert result.id == secret_id + assert result.status == "ACTIVE" + assert result.deletion_protection + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_PAYLOAD_BASE_URL}/secrets/e6qj7gpvimsi1igs228r/payload", + "e6qj7gpvimsi1igs228r", + { + "entries": [{"key": "key1", "textValue": "value1"}, {"key": "key2", "binaryValue": "value2"}], + "versionId": "e6qck9fs0ue4dotveirb", + }, + ) + ], +) +async def test_mocked_get_secret_payload( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: SecretPayload = await lockbox_client.get_secret_payload(secret_id) + + assert isinstance(result, SecretPayload) + assert isinstance(result.entries, list) + assert isinstance(result.entries[0], SecretPayloadEntry) + assert result.client is None + + assert str(result[0].text_value) == "**********" + assert result[0].reveal_text_value() == "value1" + assert result[0].reveal_binary_value() is None + + assert str(result.get("key1").text_value) == "**********" + assert result["key1"].reveal_text_value() == "value1" + assert result["key1"].reveal_binary_value() is None + + assert str(result.get("key2").binary_value) == "**********" + assert result["key2"].reveal_text_value() is None + assert result["key2"].reveal_binary_value() == "value2" + + with pytest.raises(KeyError) as key_excinfo: + result["key3"] + assert "not exists" in str(key_excinfo.value) + + with pytest.raises(IndexError) as index_excinfo: + result[10] + assert "out of range" in str(index_excinfo.value) + + assert result.get("key5") is None + assert result.get("key6", default="foo") == "foo" + + +@pytest.mark.parametrize( + "url, folder_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets", + "b1gjpj7bq52xxxxxx7t6", + { + "secrets": [ + { + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qs8nc5427jv25l98i6", + "secretId": "e6qlkppt9rc0saulbfjh", + "createdAt": "2024-03-26T09:28:19.259Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6qlkppt9rc0saulbfjh", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:28:19.259Z", + "name": "test-secret-1", + "status": "ACTIVE", + }, + { + "currentVersion": { + "payloadEntryKeys": ["test-key"], + "id": "e6q1kclhp4jdnfms9bal", + "secretId": "e6qndtc3gtcnti2jb0iq", + "createdAt": "2024-03-26T09:23:50.004Z", + "status": "INACTIVE", + }, + "deletionProtection": True, + "id": "e6qndtc3gtcnti2jb0iq", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:23:50.004Z", + "name": "test-secret-2", + "status": "ACTIVE", + }, + ], + "nextPageToken": "e6qndtc4gtcnti3jb0ix", + }, + ) + ], +) +async def test_mocked_paginated_list_secrets( + url: str, + folder_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + re.compile(f"{re.escape(url)}\\?.*"), # aiohttp is sensitive to query params + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: SecretsList = await lockbox_client.list_secrets(folder_id, page_size=2) + + assert isinstance(result, SecretsList) + assert hasattr(result, "secrets") + assert isinstance(result.secrets, list) + assert result.next_page_token is not None + + for secret in result.secrets: + assert isinstance(secret, Secret) + assert isinstance(secret.current_version, SecretVersion) + assert isinstance(secret.client, AsyncYandexLockboxClient) + + +@pytest.mark.parametrize( + "url, folder_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets", + "b1gjpj7bq52xxxxxx7t6", + { + "secrets": [ + { + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qs8nc5427jv25l98i6", + "secretId": "e6qlkppt9rc0saulbfjh", + "createdAt": "2024-03-26T09:28:19.259Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6qlkppt9rc0saulbfjh", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:28:19.259Z", + "name": "test-secret-1", + "status": "ACTIVE", + }, + { + "currentVersion": { + "payloadEntryKeys": ["test-key"], + "id": "e6q1kclhp4jdnfms9bal", + "secretId": "e6qndtc3gtcnti2jb0iq", + "createdAt": "2024-03-26T09:23:50.004Z", + "status": "INACTIVE", + }, + "deletionProtection": True, + "id": "e6qndtc3gtcnti2jb0iq", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T09:23:50.004Z", + "name": "test-secret-2", + "status": "ACTIVE", + }, + ], + "nextPageToken": None, + }, + ) + ], +) +async def test_mocked_iterable_list_secrets( + url: str, + folder_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + re.compile(f"{re.escape(url)}\\?.*"), # aiohttp is sensitive to query params + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result = await lockbox_client.list_secrets(folder_id, iterator=True) + + assert isinstance(result, AsyncGenerator) or isinstance(result, AsyncIterator) + assert not hasattr(result, "next_page_token") + + async for secret in result: + assert isinstance(secret, Secret) + assert isinstance(secret.current_version, SecretVersion) + assert isinstance(secret.client, AsyncYandexLockboxClient) + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qr4ra9qh9thdnhrh7s/versions", + "e6qr4ra9qh9thdnhrh7s", + { + "versions": [ + { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6q2fbgrmuc3tmh8yyy3", + "secretId": "e6qr4ra9qh9thdnhrh7s", + "createdAt": "2024-03-26T10:15:37.400Z", + "status": "ACTIVE", + }, + { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6q2fbgrmuc3tmh8xxx3", + "secretId": "e6qr4ra9qh9thdnhrh7s", + "createdAt": "2024-03-26T10:15:37.400Z", + "status": "ACTIVE", + }, + ], + "nextPageToken": "e6q2fbgrmuc3tmh8yyy3", + }, + ) + ], +) +async def test_mocked_paginated_list_secret_versions( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + re.compile(f"{re.escape(url)}\\?.*"), # aiohttp is sensitive to query params + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: SecretsList = await lockbox_client.list_secret_versions(secret_id, page_size=2) + + assert isinstance(result, SecretVersionsList) + assert hasattr(result, "versions") + assert isinstance(result.versions, list) + assert result.next_page_token is not None + + for version in result.versions: + assert isinstance(version, SecretVersion) + assert isinstance(version.client, AsyncYandexLockboxClient) + + +@pytest.mark.parametrize( + "url, secret_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qr4ra9qh9thdnhrh7s/versions", + "e6qr4ra9qh9thdnhrh7s", + { + "versions": [ + { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6q2fbgrmuc3tmh8yyy3", + "secretId": "e6qr4ra9qh9thdnhrh7s", + "createdAt": "2024-03-26T10:15:37.400Z", + "status": "ACTIVE", + }, + { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6q2fbgrmuc3tmh8xxx3", + "secretId": "e6qr4ra9qh9thdnhrh7s", + "createdAt": "2024-03-26T10:15:37.400Z", + "status": "ACTIVE", + }, + ], + }, + ) + ], +) +async def test_mocked_iterable_list_secret_versions( + url: str, + secret_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.get( + re.compile(f"{re.escape(url)}\\?.*"), # aiohttp is sensitive to query params + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result = await lockbox_client.list_secret_versions(secret_id, iterator=True) + + assert isinstance(result, AsyncGenerator) or isinstance(result, AsyncIterator) + assert not hasattr(result, "next_page_token") + + async for version in result: + assert isinstance(version, SecretVersion) + assert isinstance(version.client, AsyncYandexLockboxClient) + + +@pytest.mark.parametrize( + "url, secret_id, version_id, mock_response", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qqg8aq7jum59ivv560:scheduleVersionDestruction", + "e6qqg8aq7jum59ivv560", + "e6qo0aqmflbl0o00mlmd", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.ScheduleVersionDestructionMetadata", + "secretId": "e6qqg8aq7jum59ivv560", + "versionId": "e6qo0aqmflbl0o00mlmd", + "destroyAt": "2024-04-02T08:33:34.641Z", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Version", + "payloadEntryKeys": ["mykey", "everybody"], + "id": "e6qo0aqmflbl0o00mlmd", + "secretId": "e6qqg8aq7jum59ivv560", + "createdAt": "2024-03-26T08:33:33.625Z", + "destroyAt": "2024-04-02T08:33:34.641Z", + "status": "SCHEDULED_FOR_DESTRUCTION", + }, + "id": "e6qdb9sm2tut6econ95c", + "description": "Schedule version destruction", + "createdAt": "2024-03-26T08:33:34.654233229Z", + "createdBy": "aje884de7xxxxxxq3joj", + "modifiedAt": "2024-03-26T08:33:34.654258874Z", + }, + ) + ], +) +async def test_mocked_schedule_secret_version_destruction( + url: str, + secret_id: str, + version_id: str, + mock_response: dict[str, Any], + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.post( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.schedule_secret_version_destruction( + secret_id, version_id + ) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] == secret_id + assert result.metadata["versionId"] == version_id + + assert isinstance(result.resource, SecretVersion) + assert isinstance(result.resource.client, AsyncYandexLockboxClient) + assert result.resource.id == version_id + assert result.resource.secret_id == secret_id + assert result.resource.status == "SCHEDULED_FOR_DESTRUCTION" + + +@pytest.mark.parametrize( + "url, secret_id, mock_response, data", + [ + ( + f"{YC_LOCKBOX_BASE_URL}/secrets/e6qq26njrboiglh9nfkq", + "e6qq26njrboiglh9nfkq", + { + "done": True, + "metadata": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.UpdateSecretMetadata", + "secretId": "e6qq26njrboiglh9nfkq", + }, + "response": { + "@type": "type.googleapis.com/yandex.cloud.lockbox.v1.Secret", + "currentVersion": { + "payloadEntryKeys": ["key1", "key2"], + "id": "e6qsmqo0svqdcgjlted7", + "secretId": "e6qq26njrboiglh9nfkq", + "createdAt": "2024-03-26T10:31:56.563Z", + "status": "ACTIVE", + }, + "deletionProtection": False, + "id": "e6qq26njrboiglh9nfkq", + "folderId": "b1gjpj7bq52xxxxxx7t6", + "createdAt": "2024-03-26T10:31:56.563Z", + "name": "updated-secret", + "description": "has been updated", + "status": "ACTIVE", + }, + "id": "e6qhnt68r89usttisc14", + "description": "Update secret", + "createdAt": "2024-03-26T10:31:56.934095604Z", + "createdBy": "aje884de4fxxxxxx3joj", + "modifiedAt": "2024-03-26T10:31:56.934138749Z", + }, + IUpdateSecret(updateMask="name,description", name="updated-secret", description="has been updated"), + ) + ], +) +async def test_mocked_update_secret( + url: str, + secret_id: str, + mock_response: dict[str, Any], + data: IUpdateSecret, + aio_requests_mocker: aioresponses, + lockbox_client: AsyncYandexLockboxClient, +) -> None: + + aio_requests_mocker.patch( + url, + headers={"Content-Type": "application/json"}, + payload=mock_response, + status=200, + ) + + result: Operation | YandexCloudError = await lockbox_client.update_secret(secret_id, data) + + assert isinstance(result, Operation) + assert result.done + assert result.id == mock_response["id"] + assert result.metadata["secretId"] is not None + + assert isinstance(result.resource, Secret) + assert isinstance(result.resource.current_version, SecretVersion) + assert isinstance(result.client, AsyncYandexLockboxClient) + + +# Not implemented methods test + + +async def test_mocked_list_secret_access_bindings(lockbox_client) -> None: + with pytest.raises(NotImplementedError) as excinfo: + await lockbox_client.list_secret_access_bindings() + assert str(excinfo.value) == "" + + +async def test_mocked_list_secret_operations(lockbox_client) -> None: + with pytest.raises(NotImplementedError) as excinfo: + await lockbox_client.list_secret_operations() + assert str(excinfo.value) == "" + + +async def test_mocked_set_secret_access_bindings(lockbox_client) -> None: + with pytest.raises(NotImplementedError) as excinfo: + await lockbox_client.set_secret_access_bindings() + assert str(excinfo.value) == "" + + +async def test_mocked_update_secret_access_bindings(lockbox_client) -> None: + with pytest.raises(NotImplementedError) as excinfo: + await lockbox_client.update_secret_access_bindings() + assert str(excinfo.value) == "" diff --git a/tests/integration/conftest.py b/tests/integration/test_sync/conftest.py similarity index 100% rename from tests/integration/conftest.py rename to tests/integration/test_sync/conftest.py diff --git a/tests/integration/test_mocked_lockbox_client.py b/tests/integration/test_sync/test_mocked_lockbox_client.py similarity index 100% rename from tests/integration/test_mocked_lockbox_client.py rename to tests/integration/test_sync/test_mocked_lockbox_client.py diff --git a/yc_lockbox/__init__.py b/yc_lockbox/__init__.py index 3eb0c60..644aa51 100644 --- a/yc_lockbox/__init__.py +++ b/yc_lockbox/__init__.py @@ -24,7 +24,7 @@ SOFTWARE. """ -from yc_lockbox._lockbox import YandexLockboxClient +from yc_lockbox._lockbox import AsyncYandexLockboxClient, YandexLockboxClient, YandexLockboxFacade from yc_lockbox._models import ( Secret, INewSecretPayloadEntry, @@ -35,7 +35,7 @@ YandexCloudError, ) -__version__ = "0.1.3" +__version__ = "0.2.0" __author__ = "Akim Faskhutdinov" __author_email__ = "akimstrong@yandex.ru" __license__ = "MIT" @@ -47,7 +47,9 @@ "INewSecret", "INewSecretVersion", "IUpdateSecret", + "AsyncYandexLockboxClient", "YandexLockboxClient", + "YandexLockboxFacade", "Operation", "YandexCloudError", ] diff --git a/yc_lockbox/_adapters.py b/yc_lockbox/_adapters.py index 370594f..ff96027 100644 --- a/yc_lockbox/_adapters.py +++ b/yc_lockbox/_adapters.py @@ -149,6 +149,9 @@ async def request( "or use ``pip install yc-lockbox[aio]`` for resolve it." ) + if params is not None: + params = {k: v for k, v in params.items() if v is not None} # aiohttp don't like NoneType + async with aiohttp.ClientSession() as session: async with session.request( method=method, url=url, data=data, json=json, headers=headers, params=params, **kwargs diff --git a/yc_lockbox/_lockbox.py b/yc_lockbox/_lockbox.py index a5f6a49..e54fc3b 100644 --- a/yc_lockbox/_lockbox.py +++ b/yc_lockbox/_lockbox.py @@ -1,8 +1,8 @@ import logging -from typing import Type, Optional, Callable, Iterator +from typing import Any, AsyncGenerator, Coroutine, Type, Optional, Callable, Iterator from yc_lockbox._abc import AbstractYandexAuthClient, AbstractYandexLockboxClient, AbstractHTTPAdapter -from yc_lockbox._adapters import HTTPAdapter +from yc_lockbox._adapters import HTTPAdapter, AsyncHTTPAdapter from yc_lockbox._auth import YandexAuthClient from yc_lockbox._models import ( Secret, @@ -479,23 +479,530 @@ def update_secret_access_bindings(self, *args, **kwargs): raise NotImplementedError -# TODO: implement class AsyncYandexLockboxClient(AbstractYandexLockboxClient): - """The same as :class:`YandexLockboxClient` but async.""" + """ + Yandex Lockbox secrets vault client. + The same as :class:`YandexLockboxClient` but async. + + :param credentials: Credentials for authenticate requests. + Allowed types: service account key, OAuth token, IAM token. + :param auth_client: Optional client implementation for authenticate requests. + Defaults to ``YandexAuthClient``. + :param adapter: HTTP adapter for communicate with Yandex Cloud API. + :param lockbox_base_url: Lockbox base URL without resource path. + :param payload_lockbox_base_url: Lockbox payload base URL without resource path. + :param auth_base_url: IAM base URL without resource path. + + .. note:: + + All the values of the secrets are masked, i.e. looks like ``***********``. + To get the real value of the secret, you need to call the injected methods + :func:`reveal_text_value()` or :func:`reveal_binary_value()`. + + Usage:: + + from yc_lockbox import AsyncYandexLockboxClient, Secret + + lockbox = AsyncYandexLockboxClient("y0_AgAEXXXXXXXXXXXXXXXXXXXXXXXXX") # OAuth or IAM token + + secret: Secret = await lockbox.get_secret("e6xxxxxxxxxxxxxxxx") + print(secret.name, secret.status, secret.description) + + secret_versions = await secret.list_versions() + async for version in secret_versions: + print(version) + if version.id != secret.current_version.id: + await version.schedule_version_destruction() + + payload = await secret.payload() + + try: + value = payload["mykey"] + print(value.reveal_text_value()) + except KeyError: + print("Invalid key!") + + print(payload.get("foo")) # None if not exists without raising exception + entry = payload[0] # similar to payload.entries[0] - def __init__(self, *args, **kwargs) -> None: - raise NotImplementedError # pragma: no cover + Authenticate via service account key:: + import json + + # generate json key for your SA + # yc iam key create --service-account-name my-sa --output key.json + + with open("./key.json", "r") as infile: + credentials = json.load(infile) + + lockbox = AsyncYandexLockboxClient(credentials) -# TODO: implement -class YandexLockbox: + """ + + enable_async = True + + def __init__( + self, + credentials, + *, + auth_client: Optional[Type[AbstractYandexAuthClient]] = YandexAuthClient, + adapter: Optional[Type[AbstractHTTPAdapter]] = AsyncHTTPAdapter, + lockbox_base_url: str | None = None, + payload_lockbox_base_url: str | None = None, + ) -> None: + super().__init__( + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + self.auth: AbstractYandexAuthClient = auth_client(credentials) + self.adapter: AbstractHTTPAdapter = adapter + + @property + def auth_headers(self) -> dict[str, str]: + """Returns headers for authenticate.""" + return self.auth.get_auth_headers() + + def _inject_client_to_items(self, items: list[T]) -> list[T]: + """Inject this client to each response model.""" + return list(map(lambda item: item.inject_client(self), items)) + + async def _seekable_response( + self, func: Callable[..., BasePaginatedResponse], entrypoint: str, *args, **kwargs + ) -> AsyncGenerator[Any, T]: + """ + Requests all data from the API using the generators, instead of a page-by-page response. + This method does not works with dict. Be careful. + Returns list of objects from model entrypoint. + + :param func: Adapter func to get data from API. + :param entrypoint: Response attribute that contains list of useful data items. + :param args: Arguments for func. Will be passed when called inside. + :param kwargs: Keyword arguments for func. Similar to ``args``. + """ + + if kwargs.get("params") is None: + raise TypeError( + "This method works only with query string parameters. Check keyword arguments to resolve it." + ) + + next_token = "" # nosec B105 + + while next_token is not None: + # There could potentially be a problem if some request doesn't have a 'pageToken' in query string params. + # However, this is already a question for a non-consistent API, I think. + # An unlikely story, but worth keeping in mind. + kwargs["params"]["pageToken"] = next_token + + response = await func(*args, **kwargs) + next_token = response.next_page_token # None or a new token from the API + + if not hasattr(response, entrypoint): + raise AttributeError(f"Entrypoint {entrypoint} not exists in response model.") + + for item in getattr(response, entrypoint): + if not hasattr(item, "inject_client"): + raise AttributeError( + f"Incorrect item. Method 'inject_client' is not exists in {item.__class__} {type(item)}" + ) + item.inject_client(self) + yield item + + async def activate_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Activates the specified secret. + + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:activate" + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def add_secret_version( + self, secret_id: str, version: INewSecretVersion, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Adds new version based on a previous one. + + :param secret_id: Secret indentifier. + :param version: A new version object. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:addVersion" + payload = version.model_dump_json(by_alias=True, exclude_none=True) + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def create_secret( + self, secret: INewSecret, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Creates a secret in the specified folder. + + :param secret: A new secret object. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets" + payload = secret.model_dump_json(by_alias=True, exclude_none=True) + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def cancel_secret_version_destruction( + self, secret_id: str, version_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Cancels previously scheduled version destruction, if the version hasn't been destroyed yet. + + :param secret_id: Secret indentifier. + :param version_id: Secret version id to cancel destruction. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:cancelVersionDestruction" + payload = {"versionId": version_id} + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + json=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def deactivate_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Deactivate a secret. + + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}:deactivate" + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def delete_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Deletes the specified secret. + + :param secret_id: Secret indentifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + response = await self.adapter.request( + "DELETE", + url, + headers=self.auth_headers, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def get_secret( + self, secret_id: str, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Secret | YandexCloudError]: + """ + Get lockbox secret by ID. + + :param secret_id: Secret identifier. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + response = await self.adapter.request( + "GET", + url, + headers=self.auth_headers, + response_model=Secret, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + async def get_secret_payload( + self, + secret_id: str, + version_id: str | None = None, + raise_for_status: bool = True, + ) -> Coroutine[Any, Any, SecretPayload | YandexCloudError]: + """ + Get lockbox secret payload by ID and optional version. + + :param secret_id: Secret identifier. + :param version_id: Secret version. Optional. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.payload_lockbox_base_url}/secrets/{secret_id}/payload" + params = {"version_id": version_id} if version_id else None + return await self.adapter.request( + "GET", + url, + headers=self.auth_headers, + response_model=SecretPayload, + raise_for_status=raise_for_status, + params=params, + ) + + async def list_secrets( + self, + folder_id: str, + page_size: int = 100, + page_token: str | None = None, + raise_for_status: bool = True, + iterator: bool = False, + ) -> Coroutine[Any, Any, SecretsList | YandexCloudError] | AsyncGenerator[Any, Secret]: + """ + Retrieves the list of secrets in the specified folder. + + :param folder_id: ID of the folder to list secrets in. + :param page_size: The maximum number of results per page to return. + If the number of available results is larger than ``page_size``, + the service returns a ``next_page_token`` that can be used to get + the next page of results in subsequent list requests. + Default value: ``100``. + The maximum value is ``1000``. + :param page_token: Page token. To get the next page of results, set ``page_token`` + to the ``next_page_token`` returned by a previous list request. + :param iterator: Returns all data as iterator (generator) instead paginated result. + """ + args = ( + "GET", + f"{self.lockbox_base_url}/secrets", + ) + kwargs = { + "headers": self.auth_headers, + "params": {"folderId": folder_id, "pageSize": page_size, "pageToken": page_token}, + "response_model": SecretsList, + "raise_for_status": raise_for_status, + } + + if iterator: + return self._seekable_response(self.adapter.request, "secrets", *args, **kwargs) + + response = await self.adapter.request(*args, **kwargs) + self._inject_client_to_items(response.secrets) + return response + + # TODO: implement + async def list_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError + + # TODO: implement + async def list_secret_operations(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError + + async def list_secret_versions( + self, + secret_id: str, + page_size: int = 100, + page_token: str | None = None, + raise_for_status: bool = True, + iterator: bool = False, + ) -> Coroutine[Any, Any, SecretVersionsList | YandexCloudError] | AsyncGenerator[Any, SecretVersion]: + """ + Retrieves the list of versions of the specified secret. + + :param secret_id: Secret identifier. + :param page_size: The maximum number of results per page to return. + If the number of available results is larger than ``page_size``, + the service returns a ``next_page_token`` that can be used to get + the next page of results in subsequent list requests. + Default value: ``100``. + The maximum value is ``1000``. + :param page_token: Page token. To get the next page of results, set ``page_token`` + to the ``next_page_token`` returned by a previous list request. + :param iterator: Returns all data as iterator (generator) instead paginated result. + """ + args = ( + "GET", + f"{self.lockbox_base_url}/secrets/{secret_id}/versions", + ) + kwargs = { + "headers": self.auth_headers, + "params": {"pageSize": page_size, "pageToken": page_token}, + "response_model": SecretVersionsList, + "raise_for_status": raise_for_status, + } + + if iterator: + print("IMA ITERATOR IMA ITERATOR IMA ITERATOR IMA ITERATOR") + return self._seekable_response(self.adapter.request, "versions", *args, **kwargs) + + response = await self.adapter.request(*args, **kwargs) + self._inject_client_to_items(response.versions) + return response + + async def schedule_secret_version_destruction( + self, secret_id: str, version_id: str, pending_period: int = 604800, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Schedules the specified version for destruction. + Scheduled destruction can be cancelled with the :func:`cancel_secret_version_destruction()` method. + + :param secret_id: Secret indentifier. + :param version_id: ID of the version to be destroyed. + :param pending_period: Time interval in seconds between the version destruction request and actual destruction. + Default value: ``604800`` (i.e. 7 days). + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + if isinstance(pending_period, int): + if pending_period <= 0: + raise ValueError("The ``pending_period`` value must be greater than 0.") + # protobuf duration compat + # https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/duration.proto + pending_period = str(pending_period) + "s" + else: + raise ValueError("The ``pending_period`` value must be integer.") + + url = f"{self.lockbox_base_url}/secrets/{secret_id}:scheduleVersionDestruction" + payload = {"versionId": version_id, "pendingPeriod": pending_period} + response = await self.adapter.request( + "POST", + url, + headers=self.auth_headers, + json=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + # TODO: implement + async def set_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError + + async def update_secret( + self, secret_id: str, data: IUpdateSecret, raise_for_status: bool = True + ) -> Coroutine[Any, Any, Operation | YandexCloudError]: + """ + Updates the specified secret. + + :param secret_id: Secret identifier. + :param data: A new data for the secret as object. + Important. Field mask that specifies which attributes of the secret are going to be updated. + A comma-separated names off ALL fields to be updated. Only the specified fields will be changed. + The others will be left untouched. If the field is specified in updateMask and no value for + that field was sent in the request, the field's value will be reset to the default. + The default value for most fields is null or 0. + If ``updateMask`` is not sent in the request, all fields values will be updated. + Fields specified in the request will be updated to provided values. The rest of the fields will be reset to the default. + :param raise_for_status: If set to ``False`` returns :class:`YandexCloudError` instead throw exception. + Defaults to ``True``. + """ + url = f"{self.lockbox_base_url}/secrets/{secret_id}" + payload = data.model_dump_json(by_alias=True) + response = await self.adapter.request( + "PATCH", + url, + headers=self.auth_headers, + data=payload, + response_model=Operation, + raise_for_status=raise_for_status, + ) + response.inject_client(self) + return response + + # TODO: implement + async def update_secret_access_bindings(self, *args, **kwargs): + """Not ready yet.""" + raise NotImplementedError + + +class YandexLockboxFacade: # pragma: no cover """ A facade for encapsulating the logic of synchronous and asynchronous client operations, providing uniform methods. """ - def __init__(self) -> None: - raise NotImplementedError # pragma: no cover + _client: AbstractYandexLockboxClient + + def __init__( + self, + credentials, + *, + auth_client: Optional[Type[AbstractYandexAuthClient]] = YandexAuthClient, + lockbox_base_url: str | None = None, + payload_lockbox_base_url: str | None = None, + enable_async: bool = False, + ) -> None: + if enable_async: + self._client = AsyncYandexLockboxClient( + credentials, + auth_client=auth_client, + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + else: + self._client = YandexLockboxClient( + credentials, + auth_client=auth_client, + lockbox_base_url=lockbox_base_url, + payload_lockbox_base_url=payload_lockbox_base_url, + ) + + @property + def client(self) -> AbstractYandexLockboxClient: + """Returns initialized Lockbox client.""" + return self._client + + def __getattr__(self, name): + """Dynamically delegate method calls to the appropriate client.""" + + if name == "_client": + return self._client + + if not hasattr(self._client, name): + raise AttributeError + + return getattr(self._client, name) -__all__ = ["AsyncYandexLockboxClient", "YandexLockboxClient", "YandexLockbox"] +__all__ = ["AsyncYandexLockboxClient", "YandexLockboxClient", "YandexLockboxFacade"] diff --git a/yc_lockbox/_models.py b/yc_lockbox/_models.py index ca64317..b77fdce 100644 --- a/yc_lockbox/_models.py +++ b/yc_lockbox/_models.py @@ -1,11 +1,11 @@ import logging -from typing import Any, Iterator, Union +from typing import Any, AsyncGenerator, Iterator, Union from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, SecretStr, SecretBytes, computed_field from yc_lockbox._constants import RpcError from yc_lockbox._abc import AbstractYandexLockboxClient -from yc_lockbox._types import T +from yc_lockbox._types import T, SecretVersionsResponse from yc_lockbox._exceptions import LockboxError @@ -243,16 +243,30 @@ def delete(self, **kwargs) -> Union["Operation", "YandexCloudError"]: self._raise_when_empty_client() return self.client.delete_secret(self.id, **kwargs) - def refresh(self, **kwargs) -> "Secret": - """Shortcut for refresh attributes for this secret.""" - self._raise_when_empty_client() + async def _async_refresh(self, **kwargs) -> "Secret": + data = await self.client.get_secret(self.id, **kwargs) + self._update_attributes(data) + return self + + def _sync_refresh(self, **kwargs) -> "Secret": data = self.client.get_secret(self.id, **kwargs) + self._update_attributes(data) + return self + def _update_attributes(self, data) -> None: + """Method for update model attributes after refresh.""" for attr, value in data.model_dump().items(): - if value != getattr(self, attr): + if value != getattr(self, attr, None): setattr(self, attr, value) - return data + def refresh(self, **kwargs) -> "Secret": + """Shortcut for refresh attributes for this secret.""" + self._raise_when_empty_client() + + if hasattr(self.client, "enable_async") and self.client.enable_async: + return self._async_refresh(**kwargs) + + return self._sync_refresh(**kwargs) def payload(self, version_id: str | None = None, **kwargs) -> Union["Operation", "YandexCloudError"]: self._raise_when_empty_client() @@ -260,7 +274,7 @@ def payload(self, version_id: str | None = None, **kwargs) -> Union["Operation", def list_versions( self, page_size: int = 100, page_token: str | None = None, iterator: bool = False, **kwargs - ) -> Union["SecretVersionsList", Iterator["SecretVersion"], "YandexCloudError"]: + ) -> SecretVersionsResponse: """Shortcut for list all available versions of the current secret.""" self._raise_when_empty_client() return self.client.list_secret_versions( diff --git a/yc_lockbox/_types.py b/yc_lockbox/_types.py index 6c31d1d..9a4e873 100644 --- a/yc_lockbox/_types.py +++ b/yc_lockbox/_types.py @@ -1,11 +1,16 @@ -from typing import Any, Coroutine, TypeAlias, TypeVar, TYPE_CHECKING, Union +from typing import Any, AsyncGenerator, Coroutine, Iterator, TypeAlias, TypeVar, TYPE_CHECKING, Union from pydantic import BaseModel if TYPE_CHECKING: # pragma: no cover - from yc_lockbox._models import Operation, YandexCloudError + from yc_lockbox._models import Operation, SecretVersion, SecretVersionsList, YandexCloudError T = TypeVar("T", bound=BaseModel) YandexCloudGenericResponse: TypeAlias = ( - Coroutine[Any, Any, Union["Operation", "YandexCloudError"]] | Union["Operation", "YandexCloudError"] + Union["Operation", "YandexCloudError"] | Coroutine[Any, Any, Union["Operation", "YandexCloudError"]] ) # pragma: no cover + + +SecretVersionsResponse = Union[ + "SecretVersionsList", Iterator["SecretVersion"], AsyncGenerator[Any, "SecretVersion"], "YandexCloudError" +] # pragma: no cover