diff --git a/.vscode/settings.json b/.vscode/settings.json index 677fa8e..a1ba8a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,17 +1,13 @@ { - "python.linting.enabled": true, - "python.formatting.provider": "none", - "editor.formatOnSave": true, - "python.linting.mypyEnabled": true, "[python]": { + "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports.ruff": true }, "editor.defaultFormatter": "ms-python.black-formatter" }, - "ruff.organizeImports": false, - "ruff.fixAll": false, + "python.analysis.typeCheckingMode": "strict", + "mypy.runUsingActiveInterpreter": true, "ruff.importStrategy": "fromEnvironment", - "autoDocstring.docstringFormat": "sphinx-notypes", - "python.analysis.typeCheckingMode": "strict" + "autoDocstring.docstringFormat": "sphinx-notypes" } diff --git a/infisical/api/create_secret.py b/infisical/api/create_secret.py index 2f2ef7b..9fd3ba4 100644 --- a/infisical/api/create_secret.py +++ b/infisical/api/create_secret.py @@ -18,5 +18,5 @@ def create_secret_req(api_request: Session, options: CreateSecretDTO) -> SecretR "secretPath": options.path, }, ) - - return SecretResponse.parse_obj(response.json()) + + return SecretResponse.model_validate_json(response.text) diff --git a/infisical/api/delete_secret.py b/infisical/api/delete_secret.py index 6e9ac7d..dd0019b 100644 --- a/infisical/api/delete_secret.py +++ b/infisical/api/delete_secret.py @@ -13,4 +13,4 @@ def delete_secret_req(api_request: Session, options: DeleteSecretDTO) -> SecretR }, ) - return SecretResponse.parse_obj(response.json()) + return SecretResponse.model_validate_json(response.text) diff --git a/infisical/api/get_secret.py b/infisical/api/get_secret.py index f33d430..f21cf70 100644 --- a/infisical/api/get_secret.py +++ b/infisical/api/get_secret.py @@ -13,4 +13,4 @@ def get_secret_req(api_request: Session, options: GetSecretDTO) -> SecretRespons }, ) - return SecretResponse.parse_obj(response.json()) + return SecretResponse.model_validate_json(response.text) diff --git a/infisical/api/get_secrets.py b/infisical/api/get_secrets.py index 6edc505..72e788d 100644 --- a/infisical/api/get_secrets.py +++ b/infisical/api/get_secrets.py @@ -1,8 +1,13 @@ -from infisical.models.api import GetSecretsDTO, SecretsResponse +from typing import List, Tuple + +from infisical.models.api import GetSecretsDTO, SecretImport, SecretsResponse +from infisical.models.models import Secret from requests import Session -def get_secrets_req(api_request: Session, options: GetSecretsDTO) -> SecretsResponse: +def get_secrets_req( + api_request: Session, options: GetSecretsDTO +) -> Tuple[List[Secret], List[SecretImport]]: """Send request again Infisical API to fetch secrets. See more information on https://infisical.com/docs/api-reference/endpoints/secrets/read @@ -18,10 +23,9 @@ def get_secrets_req(api_request: Session, options: GetSecretsDTO) -> SecretsResp "environment": options.environment, "workspaceId": options.workspace_id, "secretPath": options.path, - "include_imports": str(options.include_imports).lower() + "include_imports": str(options.include_imports).lower(), }, ) - data = SecretsResponse.parse_obj(response.json()) + data = SecretsResponse.model_validate_json(response.text) return (data.secrets if data.secrets else [], data.imports if data.imports else []) - diff --git a/infisical/api/get_service_token_data.py b/infisical/api/get_service_token_data.py index b2cd0d0..d85781f 100644 --- a/infisical/api/get_service_token_data.py +++ b/infisical/api/get_service_token_data.py @@ -13,4 +13,4 @@ def get_service_token_data_req( """ response = api_request.get("/api/v2/service-token") - return GetServiceTokenDetailsResponse.parse_obj(response.json()) + return GetServiceTokenDetailsResponse.model_validate_json(response.text) diff --git a/infisical/api/get_service_token_data_key.py b/infisical/api/get_service_token_data_key.py index b5bcc2e..a08b83a 100644 --- a/infisical/api/get_service_token_data_key.py +++ b/infisical/api/get_service_token_data_key.py @@ -12,4 +12,4 @@ def get_service_token_data_key_req( """ response = api_request.get("/api/v3/service-token/me/key") - return GetServiceTokenKeyResponse.parse_obj(response.json()) + return GetServiceTokenKeyResponse.model_validate_json(response.text) diff --git a/infisical/api/models.py b/infisical/api/models.py index 1d72cbf..d9ceb97 100644 --- a/infisical/api/models.py +++ b/infisical/api/models.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional +from typing import Optional from pydantic import BaseModel, Field @@ -27,6 +27,7 @@ class GetServiceTokenDetailsResponse(BaseModel): updated_at: datetime = Field(..., alias="updatedAt") v: int = Field(..., alias="__v") + class KeyData(BaseModel): id: str = Field(..., alias="_id") workspace: str @@ -34,5 +35,6 @@ class KeyData(BaseModel): public_key: str = Field(..., alias="publicKey") nonce: str + class GetServiceTokenKeyResponse(BaseModel): - key: KeyData \ No newline at end of file + key: KeyData diff --git a/infisical/api/update_secret.py b/infisical/api/update_secret.py index 51794e2..46b210c 100644 --- a/infisical/api/update_secret.py +++ b/infisical/api/update_secret.py @@ -16,4 +16,4 @@ def update_secret_req(api_request: Session, options: UpdateSecretDTO) -> SecretR }, ) - return SecretResponse.parse_obj(response.json()) + return SecretResponse.model_validate_json(response.text) diff --git a/infisical/client/infisicalclient.py b/infisical/client/infisicalclient.py index a593e41..ae3be9c 100644 --- a/infisical/client/infisicalclient.py +++ b/infisical/client/infisicalclient.py @@ -1,5 +1,5 @@ import json -from typing import Dict, Optional +from typing import Dict, List, Optional, Tuple from infisical.api import create_api_request_with_auth from infisical.constants import ( @@ -17,8 +17,13 @@ update_secret_helper, ) from infisical.models.models import SecretBundle -from infisical.models.secret_service import ClientConfig +from infisical.models.secret_service import ( + ClientConfig, + ServiceTokenCredentials, + ServiceTokenV3Credentials, +) from infisical.utils.crypto import ( + Base64String, create_symmetric_key_helper, decrypt_symmetric_helper, encrypt_symmetric_helper, @@ -49,37 +54,45 @@ def __init__( self.client_config = ClientConfig( auth_mode=AUTH_MODE_SERVICE_TOKEN, - credentials={"service_token_key": service_token_key}, + credentials=ServiceTokenCredentials( + service_token_key=service_token_key + ), + workspace_config=None, cache_ttl=cache_ttl, ) self.api_request = create_api_request_with_auth(site_url, service_token) - + if token_json and token_json != "": token_dict = json.loads(token_json) - + self.client_config = ClientConfig( auth_mode=AUTH_MODE_SERVICE_TOKEN_V3, - credentials={ - "public_key": token_dict["publicKey"], - "private_key": token_dict["privateKey"] - }, - cache_ttl=cache_ttl + credentials=ServiceTokenV3Credentials( + public_key=token_dict["publicKey"], + private_key=token_dict["privateKey"], + ), + workspace_config=None, + cache_ttl=cache_ttl, ) - self.api_request = create_api_request_with_auth(site_url, token_dict["serviceToken"]) + self.api_request = create_api_request_with_auth( + site_url, token_dict["serviceToken"] + ) self.debug = debug def get_all_secrets( - self, - environment: str = "dev", - path: str = "/", + self, + environment: str = "dev", + path: str = "/", include_imports: bool = True, - attach_to_os_environ: bool = False - ): + attach_to_os_environ: bool = False, + ) -> List[SecretBundle]: """Return all the secrets accessible by the instance of Infisical""" - return get_all_secrets_helper(self, environment, path, include_imports, attach_to_os_environ) + return get_all_secrets_helper( + self, environment, path, include_imports, attach_to_os_environ + ) def get_secret( self, @@ -140,7 +153,7 @@ def delete_secret( type: Literal["shared", "personal"] = "shared", environment: str = "dev", path: str = "/", - ): + ) -> SecretBundle: """Delete secret with name `secret_name` :param secret_name: Name of secret to delete @@ -153,10 +166,18 @@ def create_symmetric_key(self) -> str: """Create a base64-encoded, 256-bit symmetric key""" return create_symmetric_key_helper() - def encrypt_symmetric(self, plaintext: str, key: str): + def encrypt_symmetric( + self, plaintext: str, key: Base64String + ) -> Tuple[Base64String, Base64String, Base64String]: """Encrypt the plaintext `plaintext` with the (base64) 256-bit secret key `key`""" return encrypt_symmetric_helper(plaintext, key) - def decrypt_symmetric(self, ciphertext: str, key: str, iv: str, tag: str): + def decrypt_symmetric( + self, + ciphertext: Base64String, + key: Base64String, + iv: Base64String, + tag: Base64String, + ) -> str: """Decrypt the ciphertext `ciphertext` with the (base64) 256-bit secret key `key`, provided `iv` and `tag`""" return decrypt_symmetric_helper(ciphertext, key, iv, tag) diff --git a/infisical/helpers/client.py b/infisical/helpers/client.py index d9290d2..bd576c8 100644 --- a/infisical/helpers/client.py +++ b/infisical/helpers/client.py @@ -1,6 +1,6 @@ import os from datetime import datetime, timedelta -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, List, Union from typing_extensions import Literal @@ -12,7 +12,13 @@ from infisical.services.secret_service import SecretService -def get_all_secrets_helper(instance: "InfisicalClient", environment: str, path: str, include_imports: bool, attach_to_os_environ: bool): +def get_all_secrets_helper( + instance: "InfisicalClient", + environment: str, + path: str, + include_imports: bool, + attach_to_os_environ: bool, +) -> List[SecretBundle]: try: if not instance.client_config: raise Exception("Failed to find client config") @@ -31,14 +37,14 @@ def get_all_secrets_helper(instance: "InfisicalClient", environment: str, path: environment=environment, path=path, workspace_key=instance.client_config.workspace_config.workspace_key, - include_imports=include_imports + include_imports=include_imports, ) for secret_bundle in secret_bundles: cache_key = f"{secret_bundle.type}-{secret_bundle.secret_name}" instance.cache[cache_key] = secret_bundle if attach_to_os_environ: - os.environ[secret_bundle.secret_name] = secret_bundle.secret_value + os.environ[secret_bundle.secret_name] = secret_bundle.secret_value or "" return secret_bundles except Exception as exc: @@ -54,7 +60,7 @@ def get_secret_helper( type: Literal["shared", "personal"], environment: str, path: str, -): +) -> SecretBundle: cache_key = f"{type}-{secret_name}" cached_secret: Union[SecretBundle, None] = None try: @@ -117,7 +123,7 @@ def create_secret_helper( type: Literal["shared", "personal"], environment: str, path: str, -): +) -> SecretBundle: try: if not instance.client_config: raise Exception("Failed to find client config") @@ -159,7 +165,7 @@ def update_secret_helper( type: Literal["shared", "personal"], environment: str, path: str, -): +) -> SecretBundle: try: if not instance.client_config: raise Exception("Failed to find client config") @@ -200,7 +206,7 @@ def delete_secret_helper( type: Literal["shared", "personal"], environment: str, path: str, -): +) -> SecretBundle: try: if not instance.client_config: raise Exception("Failed to find client config") diff --git a/infisical/helpers/secrets.py b/infisical/helpers/secrets.py index 3a99620..d69ae87 100644 --- a/infisical/helpers/secrets.py +++ b/infisical/helpers/secrets.py @@ -1,5 +1,3 @@ -from datetime import datetime - from infisical.models.models import Secret, SecretBundle @@ -16,5 +14,4 @@ def transform_secret_to_secret_bundle( updated_at=secret.updated_at, created_at=secret.created_at, is_fallback=False, - last_fetched_at=datetime.now(), ) diff --git a/infisical/models/api.py b/infisical/models/api.py index 31bd049..95c54d2 100644 --- a/infisical/models/api.py +++ b/infisical/models/api.py @@ -59,6 +59,7 @@ class SecretImport(BaseModel): environment: str secrets: List[Secret] + class SecretsResponse(BaseModel): secrets: List[Secret] imports: Optional[List[SecretImport]] diff --git a/infisical/models/models.py b/infisical/models/models.py index b36c865..93eba3b 100644 --- a/infisical/models/models.py +++ b/infisical/models/models.py @@ -9,7 +9,6 @@ class Secret(BaseModel): id: str = Field(..., alias="_id") version: int workspace: str - user: Optional[str] type: Literal["shared", "personal"] environment: str secret_key_ciphertext: str = Field(..., alias="secretKeyCiphertext") @@ -20,19 +19,20 @@ class Secret(BaseModel): secret_value_tag: str = Field(..., alias="secretValueTag") created_at: datetime = Field(..., alias="createdAt") updated_at: datetime = Field(..., alias="updatedAt") + user: Optional[str] = None class SecretBundle(BaseModel): secret_name: str - secret_value: Optional[str] - version: Optional[int] - workspace: Optional[str] - environment: Optional[str] - type: Optional[Literal["shared", "personal"]] + secret_value: Optional[str] = None + version: Optional[int] = None + workspace: Optional[str] = None + environment: Optional[str] = None + type: Optional[Literal["shared", "personal"]] = None created_at: Optional[datetime] = Field(None, alias="createdAt") updated_at: Optional[datetime] = Field(None, alias="updatedAt") - is_fallback: bool - last_fetched_at: datetime + is_fallback: bool = False + last_fetched_at: datetime = Field(default_factory=datetime.now) class ServiceTokenData(BaseModel): diff --git a/infisical/models/secret_service.py b/infisical/models/secret_service.py index a9ee04e..7899b1b 100644 --- a/infisical/models/secret_service.py +++ b/infisical/models/secret_service.py @@ -1,20 +1,23 @@ -from typing import Dict, Optional, Union +from typing import Optional, Union from pydantic import BaseModel -from requests import Session from typing_extensions import Literal + class WorkspaceConfig(BaseModel): workspace_id: str workspace_key: str + class ServiceTokenCredentials(BaseModel): service_token_key: str + class ServiceTokenV3Credentials(BaseModel): public_key: str private_key: str + class ClientConfig(BaseModel): auth_mode: Literal["service_token", "service_token_v3"] credentials: Union[ServiceTokenCredentials, ServiceTokenV3Credentials] diff --git a/infisical/services/secret_service.py b/infisical/services/secret_service.py index ff5dac0..8717d57 100644 --- a/infisical/services/secret_service.py +++ b/infisical/services/secret_service.py @@ -1,5 +1,4 @@ import os -from datetime import datetime from typing import List from infisical.api.create_secret import create_secret_req @@ -18,13 +17,16 @@ UpdateSecretDTO, ) from infisical.models.models import SecretBundle -from infisical.models.secret_service import ClientConfig, WorkspaceConfig - -# from infisical.utils.crypto import decrypt_symmetric, encrypt_symmetric +from infisical.models.secret_service import ( + ClientConfig, + ServiceTokenCredentials, + ServiceTokenV3Credentials, + WorkspaceConfig, +) from infisical.utils.crypto import ( + decrypt_asymmetric, decrypt_symmetric_128_bit_hex_key_utf8, encrypt_symmetric_128_bit_hex_key_utf8, - decrypt_asymmetric ) from requests import Session from typing_extensions import Literal @@ -35,7 +37,9 @@ class SecretService: def populate_client_config( api_request: Session, client_config: ClientConfig ) -> WorkspaceConfig: - if client_config.auth_mode == "service_token": + if client_config.auth_mode == "service_token" and isinstance( + client_config.credentials, ServiceTokenCredentials + ): service_token_details = get_service_token_data_req(api_request) workspace_key = decrypt_symmetric_128_bit_hex_key_utf8( ciphertext=service_token_details.encrypted_key, @@ -48,20 +52,23 @@ def populate_client_config( workspace_id=service_token_details.workspace, workspace_key=workspace_key, ) - - if client_config.auth_mode == "service_token_v3": + + if client_config.auth_mode == "service_token_v3" and isinstance( + client_config.credentials, ServiceTokenV3Credentials + ): service_token_key_details = get_service_token_data_key_req(api_request) workspace_key = decrypt_asymmetric( ciphertext=service_token_key_details.key.encrypted_key, nonce=service_token_key_details.key.nonce, public_key=service_token_key_details.key.public_key, - private_key=client_config.credentials.private_key + private_key=client_config.credentials.private_key, ) - + return WorkspaceConfig( workspace_id=service_token_key_details.key.workspace, - workspace_key=workspace_key + workspace_key=workspace_key, ) + raise Exception("Failed to identify the auth mode!") @staticmethod def get_fallback_secret(secret_name: str) -> SecretBundle: @@ -69,7 +76,6 @@ def get_fallback_secret(secret_name: str) -> SecretBundle: secret_name=secret_name, secret_value=os.environ[secret_name], is_fallback=True, - last_fetched_at=datetime.now(), ) @staticmethod @@ -79,14 +85,17 @@ def get_decrypted_secrets( workspace_id: str, environment: str, path: str, - include_imports: bool + include_imports: bool, ) -> List[SecretBundle]: options = GetSecretsDTO( - workspace_id=workspace_id, environment=environment, path=path, include_imports=include_imports + workspace_id=workspace_id, + environment=environment, + path=path, + include_imports=include_imports, ) encrypted_secrets, secret_imports = get_secrets_req(api_request, options) - + secret_bundles: List[SecretBundle] = [] for encrypted_secret in encrypted_secrets: @@ -96,14 +105,14 @@ def get_decrypted_secrets( tag=encrypted_secret.secret_key_tag, key=workspace_key, ) - + secret_value = decrypt_symmetric_128_bit_hex_key_utf8( ciphertext=encrypted_secret.secret_value_ciphertext, iv=encrypted_secret.secret_value_iv, tag=encrypted_secret.secret_value_tag, key=workspace_key, ) - + secret_bundles.append( transform_secret_to_secret_bundle( secret=encrypted_secret, @@ -111,7 +120,7 @@ def get_decrypted_secrets( secret_value=secret_value, ) ) - + for secret_import in secret_imports: for encrypted_secret in secret_import.secrets: secret_name = decrypt_symmetric_128_bit_hex_key_utf8( @@ -120,7 +129,7 @@ def get_decrypted_secrets( tag=encrypted_secret.secret_key_tag, key=workspace_key, ) - + secret_value = decrypt_symmetric_128_bit_hex_key_utf8( ciphertext=encrypted_secret.secret_value_ciphertext, iv=encrypted_secret.secret_value_iv, @@ -147,7 +156,7 @@ def get_decrypted_secret( workspace_key: str, type: Literal["shared", "personal"], path: str, - ): + ) -> SecretBundle: options = GetSecretDTO( secret_name=secret_name, workspace_id=workspace_id, @@ -184,7 +193,7 @@ def create_secret( secret_name: str, secret_value: str, path: str, - ): + ) -> SecretBundle: ( secret_key_ciphertext, secret_key_iv, @@ -233,7 +242,7 @@ def update_secret( secret_name: str, secret_value: str, path: str, - ): + ) -> SecretBundle: ( secret_value_ciphertext, secret_value_iv, @@ -269,7 +278,7 @@ def delete_secret( type: Literal["shared", "personal"], path: str, secret_name: str, - ): + ) -> SecretBundle: options = DeleteSecretDTO( secret_name=secret_name, workspace_id=workspace_id, diff --git a/infisical/utils/crypto.py b/infisical/utils/crypto.py index 85c5968..1763f7d 100644 --- a/infisical/utils/crypto.py +++ b/infisical/utils/crypto.py @@ -7,7 +7,6 @@ Base64String = str Buffer = Union[bytes, bytearray, memoryview] -import binascii def encrypt_asymmetric( @@ -111,13 +110,15 @@ def decrypt_asymmetric( return plaintext.decode("utf-8") -def create_symmetric_key_helper(): +def create_symmetric_key_helper() -> str: return b64encode(get_random_bytes(32)).decode("utf-8") -def encrypt_symmetric_helper(plaintext: str, key: str): +def encrypt_symmetric_helper( + plaintext: str, key: Base64String +) -> Tuple[Base64String, Base64String, Base64String]: IV_BYTES_SIZE = 12 - iv = get_random_bytes(12) + iv = get_random_bytes(IV_BYTES_SIZE) cipher = AES.new(b64decode(key), AES.MODE_GCM, nonce=iv) @@ -130,7 +131,9 @@ def encrypt_symmetric_helper(plaintext: str, key: str): ) -def decrypt_symmetric_helper(ciphertext: str, key: str, iv: str, tag: str): +def decrypt_symmetric_helper( + ciphertext: Base64String, key: Base64String, iv: Base64String, tag: Base64String +) -> str: cipher = AES.new(b64decode(key), AES.MODE_GCM, nonce=b64decode(iv)) plaintext = cipher.decrypt_and_verify(b64decode(ciphertext), b64decode(tag)) @@ -166,7 +169,7 @@ def encrypt_symmetric_128_bit_hex_key_utf8( def decrypt_symmetric_128_bit_hex_key_utf8( - key: str, ciphertext: str, tag: str, iv: str + key: str, ciphertext: Base64String, tag: Base64String, iv: Base64String ) -> str: """Decrypts the ``ciphertext`` with aes-256-gcm using ``iv``, ``tag`` and ``key``. @@ -182,14 +185,14 @@ def decrypt_symmetric_128_bit_hex_key_utf8( raise ValueError("One of the given parameter is empty!") try: - key = bytes(key, "utf-8") - iv = b64decode(iv) - tag = b64decode(tag) - ciphertext = b64decode(ciphertext) + key_buffer = bytes(key, "utf-8") + iv_decoded = b64decode(iv) + tag_decoded = b64decode(tag) + ciphertext_decoded = b64decode(ciphertext) - cipher = AES.new(key, AES.MODE_GCM, nonce=iv) - plaintext = cipher.decrypt_and_verify(ciphertext, tag) + cipher = AES.new(key_buffer, AES.MODE_GCM, nonce=iv_decoded) + plaintext = cipher.decrypt_and_verify(ciphertext_decoded, tag_decoded) return plaintext.decode("utf-8") - except ValueError: - raise ValueError("Incorrect decryption or MAC check failed") + except ValueError as err: + raise ValueError("Incorrect decryption or MAC check failed") from err diff --git a/pyproject.toml b/pyproject.toml index 1184d36..c0164b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,12 +6,12 @@ build-backend = "hatchling.build" name = "infisical" description = 'Official Infisical SDK for Python' readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license = "MIT" authors = [] maintainers = [ { name = "Yohann MARTIN", email = "contact@codexus.fr" }, - { name = "Tony Dang", email = "tony@infisical.com"} + { name = "Tony Dang", email = "tony@infisical.com" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -24,7 +24,6 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -33,10 +32,10 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "requests ==2.31.0", - "pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0", - "pycryptodomex >=3.17,<4.0.0", - "pynacl >=1.5.0,<2.0.0" + "requests==2.31.0", + "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", + "pycryptodomex>=3.17,<4.0.0", + "pynacl>=1.5.0,<2.0.0", ] dynamic = ["version"] @@ -47,68 +46,55 @@ Source = "https://github.com/Infisical/infisical-python" [project.optional-dependencies] test = [ - "pytest >=7.1.3,<8.0.0", - "coverage[toml] >= 6.5.0,< 8.0", - "responses ==0.23.1" + "pytest>=7.1.3,<8.0.0", + "coverage[toml]>=6.5.0,< 8.0", + "responses==0.23.3", + "python-dotenv==1.0.0" ] dev = [ - "mypy ==1.1.1", - "ruff ==0.0.261", - "black ==23.3.0", - "isort >=5.0.6,<6.0.0", - "devtools[pygments] ==0.11.0", + "mypy==1.5.1", + "ruff==0.0.292", + "black==23.9.1", + "devtools[pygments]==0.12.2", - "types-requests ==2.28.11.17" + "types-requests==2.31.0.7", ] [tool.hatch.version] path = "infisical/__version__.py" [tool.hatch.build.targets.sdist] -exclude = [ - "/.github", - "/.vscode", -] - -[tool.isort] -profile = "black" -known_third_party = ["infisical", "pydantic", "Cryptodome", "nacl", "responses"] +exclude = ["/.github", "/.vscode"] [tool.mypy] strict = true +plugins = ["pydantic.mypy"] [tool.coverage.run] parallel = true -source = [ - "tests", - "infisical" -] +source = ["tests", "infisical"] [tool.coverage.report] -exclude_lines = [ - "no cov", - "if __name__ == .__main__.:", - "if TYPE_CHECKING:", -] +exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] [tool.ruff] -target-version = "py37" +target-version = "py38" select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - # "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear ] ignore = [ - "E501", # line too long, handled by black - "B008", # do not perform function calls in argument defaults - "C901", # too complex + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "C901", # too complex ] [tool.ruff.per-file-ignores] "__init__.py" = ["F401"] [tool.ruff.isort] -known-third-party = ["infisical"] +known-third-party = ["infisical", "pydantic", "Cryptodome", "nacl", "responses"] diff --git a/scripts/format.sh b/scripts/format.sh index e85e1c0..e75370e 100644 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -3,4 +3,3 @@ set -x ruff check infisical tests --fix black infisical tests -isort infisical tests diff --git a/scripts/lint.sh b/scripts/lint.sh index bdb3454..6269ec1 100644 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -6,4 +6,3 @@ set -x mypy infisical ruff check infisical tests black infisical tests --check -isort infisical tests --check-only diff --git a/tests/test_client/test_infisical_client.py b/tests/test_client/test_infisical_client.py index 6c81d0a..9c526d1 100644 --- a/tests/test_client/test_infisical_client.py +++ b/tests/test_client/test_infisical_client.py @@ -1,13 +1,17 @@ import os +from typing import Any, Generator import pytest from infisical import InfisicalClient @pytest.fixture(scope="module") -def client(): +def client() -> Generator[InfisicalClient, Any, None]: infisical_client = InfisicalClient( - token=os.environ.get("INFISICAL_TOKEN"), token_json=os.environ.get("INFISICAL_TOKEN_JSON"), site_url=os.environ.get("SITE_URL"), debug=True + token=os.environ.get("INFISICAL_TOKEN"), + token_json=os.environ.get("INFISICAL_TOKEN_JSON"), + site_url=os.environ.get("SITE_URL", ""), + debug=True, ) infisical_client.create_secret("KEY_ONE", "KEY_ONE_VAL") @@ -22,35 +26,36 @@ def client(): infisical_client.delete_secret("KEY_TWO") infisical_client.delete_secret("KEY_THREE") -def test_get_overriden_personal_secret(client: InfisicalClient): + +def test_get_overriden_personal_secret(client: InfisicalClient) -> None: secret = client.get_secret("KEY_ONE") assert secret.secret_name == "KEY_ONE" assert secret.secret_value == "KEY_ONE_VAL_PERSONAL" assert secret.type == "personal" -def test_get_shared_secret_specified(client: InfisicalClient): +def test_get_shared_secret_specified(client: InfisicalClient) -> None: secret = client.get_secret("KEY_ONE", type="shared") assert secret.secret_name == "KEY_ONE" assert secret.secret_value == "KEY_ONE_VAL" assert secret.type == "shared" -def test_get_shared_secret(client: InfisicalClient): +def test_get_shared_secret(client: InfisicalClient) -> None: secret = client.get_secret("KEY_TWO") assert secret.secret_name == "KEY_TWO" assert secret.secret_value == "KEY_TWO_VAL" assert secret.type == "shared" -def test_create_shared_secret(client: InfisicalClient): +def test_create_shared_secret(client: InfisicalClient) -> None: secret = client.create_secret("KEY_THREE", "KEY_THREE_VAL") assert secret.secret_name == "KEY_THREE" assert secret.secret_value == "KEY_THREE_VAL" assert secret.type == "shared" -def test_create_personal_secret(client: InfisicalClient): +def test_create_personal_secret(client: InfisicalClient) -> None: client.create_secret("KEY_FOUR", "KEY_FOUR_VAL") personal_secret = client.create_secret( "KEY_FOUR", "KEY_FOUR_VAL_PERSONAL", type="personal" @@ -61,7 +66,7 @@ def test_create_personal_secret(client: InfisicalClient): assert personal_secret.type == "personal" -def test_update_shared_secret(client: InfisicalClient): +def test_update_shared_secret(client: InfisicalClient) -> None: secret = client.update_secret("KEY_THREE", "FOO") assert secret.secret_name == "KEY_THREE" @@ -69,28 +74,28 @@ def test_update_shared_secret(client: InfisicalClient): assert secret.type == "shared" -def test_update_personal_secret(client: InfisicalClient): +def test_update_personal_secret(client: InfisicalClient) -> None: secret = client.update_secret("KEY_FOUR", "BAR", type="personal") assert secret.secret_name == "KEY_FOUR" assert secret.secret_value == "BAR" assert secret.type == "personal" -def test_delete_personal_secret(client: InfisicalClient): +def test_delete_personal_secret(client: InfisicalClient) -> None: secret = client.delete_secret("KEY_FOUR", type="personal") assert secret.secret_name == "KEY_FOUR" assert secret.secret_value == "BAR" assert secret.type == "personal" -def test_delete_shared_secret(client: InfisicalClient): +def test_delete_shared_secret(client: InfisicalClient) -> None: secret = client.delete_secret("KEY_FOUR") assert secret.secret_name == "KEY_FOUR" assert secret.secret_value == "KEY_FOUR_VAL" assert secret.type == "shared" -def test_encrypt_decrypt_symmetric(client: InfisicalClient): +def test_encrypt_decrypt_symmetric(client: InfisicalClient) -> None: plaintext = "The quick brown fox jumps over the lazy dog" key = client.create_symmetric_key()