diff --git a/README.md b/README.md index 65344bb..9a393a5 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,7 @@ DataCrunch's Public API documentation [is available here](https://api.datacrunch datacrunch = DataCrunchClient(CLIENT_ID, CLIENT_SECRET) # Get all SSH keys - ssh_keys = datacrunch.ssh_keys.get() - ssh_keys = list(map(lambda key: key.id, ssh_keys)) + ssh_keys = [key.id for key in datacrunch.ssh_keys.get()] # Create a new instance instance = datacrunch.instances.create(instance_type='1V100.6V', diff --git a/datacrunch/InferenceClient/inference_client.py b/datacrunch/InferenceClient/inference_client.py index ec808e2..2338303 100644 --- a/datacrunch/InferenceClient/inference_client.py +++ b/datacrunch/InferenceClient/inference_client.py @@ -1,10 +1,12 @@ +from collections.abc import Generator from dataclasses import dataclass -from dataclasses_json import dataclass_json, Undefined # type: ignore +from enum import Enum +from typing import Any +from urllib.parse import urlparse + import requests +from dataclasses_json import Undefined, dataclass_json # type: ignore from requests.structures import CaseInsensitiveDict -from typing import Optional, Dict, Any, Union, Generator -from urllib.parse import urlparse -from enum import Enum class InferenceClientError(Exception): @@ -14,6 +16,8 @@ class InferenceClientError(Exception): class AsyncStatus(str, Enum): + """Async status.""" + Initialized = 'Initialized' Queue = 'Queue' Inference = 'Inference' @@ -23,6 +27,8 @@ class AsyncStatus(str, Enum): @dataclass_json(undefined=Undefined.EXCLUDE) @dataclass class InferenceResponse: + """Inference response.""" + headers: CaseInsensitiveDict[str] status_code: int status_text: str @@ -64,6 +70,7 @@ def _is_stream_response(self, headers: CaseInsensitiveDict[str]) -> bool: ) def output(self, is_text: bool = False) -> Any: + """Get response output as a string or object.""" try: if is_text: return self._original_response.text @@ -73,8 +80,8 @@ def output(self, is_text: bool = False) -> Any: if self._is_stream_response(self._original_response.headers): raise InferenceClientError( 'Response might be a stream, use the stream method instead' - ) - raise InferenceClientError(f'Failed to parse response as JSON: {str(e)}') + ) from e + raise InferenceClientError(f'Failed to parse response as JSON: {e!s}') from e def stream(self, chunk_size: int = 512, as_text: bool = True) -> Generator[Any, None, None]: """Stream the response content. @@ -97,11 +104,12 @@ def stream(self, chunk_size: int = 512, as_text: bool = True) -> Generator[Any, class InferenceClient: + """Inference client.""" + def __init__( self, inference_key: str, endpoint_base_url: str, timeout_seconds: int = 60 * 5 ) -> None: - """ - Initialize the InferenceClient. + """Initialize the InferenceClient. Args: inference_key: The authentication key for the API @@ -136,9 +144,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._session.close() @property - def global_headers(self) -> Dict[str, str]: - """ - Get the current global headers that will be used for all requests. + def global_headers(self) -> dict[str, str]: + """Get the current global headers that will be used for all requests. Returns: Dictionary of current global headers @@ -146,8 +153,7 @@ def global_headers(self) -> Dict[str, str]: return self._global_headers.copy() def set_global_header(self, key: str, value: str) -> None: - """ - Set or update a global header that will be used for all requests. + """Set or update a global header that will be used for all requests. Args: key: Header name @@ -155,9 +161,8 @@ def set_global_header(self, key: str, value: str) -> None: """ self._global_headers[key] = value - def set_global_headers(self, headers: Dict[str, str]) -> None: - """ - Set multiple global headers at once that will be used for all requests. + def set_global_headers(self, headers: dict[str, str]) -> None: + """Set multiple global headers at once that will be used for all requests. Args: headers: Dictionary of headers to set globally @@ -165,8 +170,7 @@ def set_global_headers(self, headers: Dict[str, str]) -> None: self._global_headers.update(headers) def remove_global_header(self, key: str) -> None: - """ - Remove a global header. + """Remove a global header. Args: key: Header name to remove from global headers @@ -179,10 +183,9 @@ def _build_url(self, path: str) -> str: return f'{self.endpoint_base_url}/{path.lstrip("/")}' def _build_request_headers( - self, request_headers: Optional[Dict[str, str]] = None - ) -> Dict[str, str]: - """ - Build the final headers by merging global headers with request-specific headers. + self, request_headers: dict[str, str] | None = None + ) -> dict[str, str]: + """Build the final headers by merging global headers with request-specific headers. Args: request_headers: Optional headers specific to this request @@ -196,8 +199,7 @@ def _build_request_headers( return headers def _make_request(self, method: str, path: str, **kwargs) -> requests.Response: - """ - Make an HTTP request with error handling. + """Make an HTTP request with error handling. Args: method: HTTP method to use @@ -221,17 +223,19 @@ def _make_request(self, method: str, path: str, **kwargs) -> requests.Response: ) response.raise_for_status() return response - except requests.exceptions.Timeout: - raise InferenceClientError(f'Request to {path} timed out after {timeout} seconds') + except requests.exceptions.Timeout as e: + raise InferenceClientError( + f'Request to {path} timed out after {timeout} seconds' + ) from e except requests.exceptions.RequestException as e: - raise InferenceClientError(f'Request to {path} failed: {str(e)}') + raise InferenceClientError(f'Request to {path} failed: {e!s}') from e def run_sync( self, - data: Dict[str, Any], + data: dict[str, Any], path: str = '', timeout_seconds: int = 60 * 5, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, http_method: str = 'POST', stream: bool = False, ): @@ -269,10 +273,10 @@ def run_sync( def run( self, - data: Dict[str, Any], + data: dict[str, Any], path: str = '', timeout_seconds: int = 60 * 5, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, http_method: str = 'POST', no_response: bool = False, ): @@ -325,10 +329,11 @@ def run( def get( self, path: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make GET request.""" return self._make_request( 'GET', path, params=params, headers=headers, timeout_seconds=timeout_seconds ) @@ -336,12 +341,13 @@ def get( def post( self, path: str, - json: Optional[Dict[str, Any]] = None, - data: Optional[Union[str, Dict[str, Any]]] = None, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + json: dict[str, Any] | None = None, + data: str | dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make POST request.""" return self._make_request( 'POST', path, @@ -355,12 +361,13 @@ def post( def put( self, path: str, - json: Optional[Dict[str, Any]] = None, - data: Optional[Union[str, Dict[str, Any]]] = None, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + json: dict[str, Any] | None = None, + data: str | dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make PUT request.""" return self._make_request( 'PUT', path, @@ -374,10 +381,11 @@ def put( def delete( self, path: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make DELETE request.""" return self._make_request( 'DELETE', path, @@ -389,12 +397,13 @@ def delete( def patch( self, path: str, - json: Optional[Dict[str, Any]] = None, - data: Optional[Union[str, Dict[str, Any]]] = None, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + json: dict[str, Any] | None = None, + data: str | dict[str, Any] | None = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make PATCH request.""" return self._make_request( 'PATCH', path, @@ -408,10 +417,11 @@ def patch( def head( self, path: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make HEAD request.""" return self._make_request( 'HEAD', path, @@ -423,10 +433,11 @@ def head( def options( self, path: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, str]] = None, - timeout_seconds: Optional[int] = None, + params: dict[str, Any] | None = None, + headers: dict[str, str] | None = None, + timeout_seconds: int | None = None, ) -> requests.Response: + """Make OPTIONS request.""" return self._make_request( 'OPTIONS', path, @@ -436,8 +447,7 @@ def options( ) def health(self, healthcheck_path: str = '/health') -> requests.Response: - """ - Check the health status of the API. + """Check the health status of the API. Returns: requests.Response: The response from the health check @@ -448,31 +458,32 @@ def health(self, healthcheck_path: str = '/health') -> requests.Response: try: return self.get(healthcheck_path) except InferenceClientError as e: - raise InferenceClientError(f'Health check failed: {str(e)}') + raise InferenceClientError(f'Health check failed: {e!s}') from e @dataclass_json(undefined=Undefined.EXCLUDE) @dataclass class AsyncInferenceExecution: + """Async inference execution.""" + _inference_client: 'InferenceClient' id: str _status: AsyncStatus INFERENCE_ID_HEADER = 'X-Inference-Id' def status(self) -> AsyncStatus: - """Get the current stored status of the async inference execution. Only the status value type + """Get the current stored status of the async inference execution. Only the status value type. Returns: AsyncStatus: The status object """ - return self._status - def status_json(self) -> Dict[str, Any]: - """Get the current status of the async inference execution. Return the status json + def status_json(self) -> dict[str, Any]: + """Get the current status of the async inference execution. Return the status json. Returns: - Dict[str, Any]: The status response containing the execution status and other metadata + dict[str, Any]: The status response containing the execution status and other metadata """ url = ( f'{self._inference_client.base_domain}/status/{self._inference_client.deployment_name}' @@ -489,11 +500,11 @@ def status_json(self) -> Dict[str, Any]: return response_json - def result(self) -> Dict[str, Any]: + def result(self) -> dict[str, Any]: """Get the results of the async inference execution. Returns: - Dict[str, Any]: The results of the inference execution + dict[str, Any]: The results of the inference execution """ url = ( f'{self._inference_client.base_domain}/result/{self._inference_client.deployment_name}' diff --git a/datacrunch/__init__.py b/datacrunch/__init__.py index 2161129..fa76d9f 100644 --- a/datacrunch/__init__.py +++ b/datacrunch/__init__.py @@ -1,3 +1,2 @@ -from datacrunch.datacrunch import DataCrunchClient - from datacrunch._version import __version__ +from datacrunch.datacrunch import DataCrunchClient diff --git a/datacrunch/authentication/authentication.py b/datacrunch/authentication/authentication.py index 6a5e3cb..d8cefe1 100644 --- a/datacrunch/authentication/authentication.py +++ b/datacrunch/authentication/authentication.py @@ -1,6 +1,7 @@ -import requests import time +import requests + from datacrunch.http_client.http_client import handle_error TOKEN_ENDPOINT = '/oauth2/token' @@ -10,7 +11,7 @@ class AuthenticationService: - """A service for client authentication""" + """A service for client authentication.""" def __init__(self, client_id: str, client_secret: str, base_url: str) -> None: self._base_url = base_url @@ -18,7 +19,7 @@ def __init__(self, client_id: str, client_secret: str, base_url: str) -> None: self._client_secret = client_secret def authenticate(self) -> dict: - """Authenticate the client and store the access & refresh tokens + """Authenticate the client and store the access & refresh tokens. returns an authentication data dictionary with the following schema: { diff --git a/datacrunch/balance/balance.py b/datacrunch/balance/balance.py index 12aac25..8cdd984 100644 --- a/datacrunch/balance/balance.py +++ b/datacrunch/balance/balance.py @@ -2,10 +2,10 @@ class Balance: - """A balance model class""" + """A balance model class.""" def __init__(self, amount: float, currency: str) -> None: - """Initialize a new Balance object + """Initialize a new Balance object. :param amount: Balance amount :type amount: float @@ -17,7 +17,7 @@ def __init__(self, amount: float, currency: str) -> None: @property def amount(self) -> float: - """Get the balance amount + """Get the balance amount. :return: amount :rtype: float @@ -26,7 +26,7 @@ def amount(self) -> float: @property def currency(self) -> str: - """Get the currency code + """Get the currency code. :return: currency code :rtype: str @@ -35,13 +35,13 @@ def currency(self) -> str: class BalanceService: - """A service for interacting with the balance endpoint""" + """A service for interacting with the balance endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client def get(self) -> Balance: - """Get the client's current balance + """Get the client's current balance. :return: Balance object containing the amount and currency. :rtype: Balance diff --git a/datacrunch/constants.py b/datacrunch/constants.py index 6d24660..2370cff 100644 --- a/datacrunch/constants.py +++ b/datacrunch/constants.py @@ -1,4 +1,6 @@ class Actions: + """Instance actions.""" + START = 'start' SHUTDOWN = 'shutdown' DELETE = 'delete' @@ -10,6 +12,8 @@ def __init__(self): class VolumeActions: + """Storage volume actions.""" + ATTACH = 'attach' DETACH = 'detach' RENAME = 'rename' @@ -22,6 +26,8 @@ def __init__(self): class InstanceStatus: + """Instance status.""" + ORDERED = 'ordered' RUNNING = 'running' PROVISIONING = 'provisioning' @@ -36,6 +42,8 @@ def __init__(self): class VolumeStatus: + """Storage volume status.""" + ORDERED = 'ordered' CREATING = 'creating' ATTACHED = 'attached' @@ -49,6 +57,8 @@ def __init__(self): class VolumeTypes: + """Storage volume types.""" + NVMe = 'NVMe' HDD = 'HDD' @@ -57,6 +67,8 @@ def __init__(self): class Locations: + """Datacenter locations.""" + FIN_01: str = 'FIN-01' FIN_02: str = 'FIN-02' FIN_03: str = 'FIN-03' @@ -67,6 +79,8 @@ def __init__(self): class ErrorCodes: + """Error codes.""" + INVALID_REQUEST = 'invalid_request' UNAUTHORIZED_REQUEST = 'unauthorized_request' INSUFFICIENT_FUNDS = 'insufficient_funds' @@ -80,6 +94,8 @@ def __init__(self): class Constants: + """Constants.""" + def __init__(self, base_url, version): self.instance_actions: Actions = Actions() """Available actions to perform on an instance""" diff --git a/datacrunch/containers/__init__.py b/datacrunch/containers/__init__.py index dc7a4ab..9633c37 100644 --- a/datacrunch/containers/__init__.py +++ b/datacrunch/containers/__init__.py @@ -1,33 +1,33 @@ from .containers import ( - EnvVar, - EnvVarType, - ContainerRegistryType, - ContainerDeploymentStatus, - HealthcheckSettings, - EntrypointOverridesSettings, - VolumeMount, - SecretMount, - SharedFileSystemMount, - GeneralStorageMount, - VolumeMountType, + AWSECRCredentials, + BaseRegistryCredentials, + ComputeResource, Container, + ContainerDeploymentStatus, ContainerRegistryCredentials, ContainerRegistrySettings, - ComputeResource, - ScalingPolicy, - QueueLoadScalingTrigger, - UtilizationScalingTrigger, - ScalingTriggers, - ScalingOptions, - Deployment, - ReplicaInfo, - Secret, - RegistryCredential, + ContainerRegistryType, ContainersService, - BaseRegistryCredentials, + CustomRegistryCredentials, + Deployment, DockerHubCredentials, - GithubCredentials, + EntrypointOverridesSettings, + EnvVar, + EnvVarType, GCRCredentials, - AWSECRCredentials, - CustomRegistryCredentials, + GeneralStorageMount, + GithubCredentials, + HealthcheckSettings, + QueueLoadScalingTrigger, + RegistryCredential, + ReplicaInfo, + ScalingOptions, + ScalingPolicy, + ScalingTriggers, + Secret, + SecretMount, + SharedFileSystemMount, + UtilizationScalingTrigger, + VolumeMount, + VolumeMountType, ) diff --git a/datacrunch/containers/containers.py b/datacrunch/containers/containers.py index edc1b22..6254848 100644 --- a/datacrunch/containers/containers.py +++ b/datacrunch/containers/containers.py @@ -7,14 +7,14 @@ import base64 import os from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, Undefined # type: ignore -from typing import List, Optional, Dict, Any, Union from enum import Enum +from typing import Any + +from dataclasses_json import Undefined, dataclass_json # type: ignore from datacrunch.http_client.http_client import HTTPClient from datacrunch.InferenceClient import InferenceClient, InferenceResponse - # API endpoints CONTAINER_DEPLOYMENTS_ENDPOINT = '/container-deployments' SERVERLESS_COMPUTE_RESOURCES_ENDPOINT = '/serverless-compute-resources' @@ -81,8 +81,8 @@ class HealthcheckSettings: """ enabled: bool = True - port: Optional[int] = None - path: Optional[str] = None + port: int | None = None + path: str | None = None @dataclass_json @@ -97,8 +97,8 @@ class EntrypointOverridesSettings: """ enabled: bool = True - entrypoint: Optional[List[str]] = None - cmd: Optional[List[str]] = None + entrypoint: list[str] | None = None + cmd: list[str] | None = None @dataclass_json @@ -131,7 +131,7 @@ class VolumeMount: type: VolumeMountType mount_path: str # Deprecated: use MemoryMount for memory volumes instead. - size_in_mb: Optional[int] = field(default=None, kw_only=True) + size_in_mb: int | None = field(default=None, kw_only=True) @dataclass_json(undefined=Undefined.EXCLUDE) @@ -161,9 +161,9 @@ class SecretMount(VolumeMount): """ secret_name: str - file_names: Optional[List[str]] = None + file_names: list[str] | None = None - def __init__(self, mount_path: str, secret_name: str, file_names: Optional[List[str]] = None): + def __init__(self, mount_path: str, secret_name: str, file_names: list[str] | None = None): self.secret_name = secret_name self.file_names = file_names super().__init__(type=VolumeMountType.SECRET, mount_path=mount_path) @@ -218,13 +218,13 @@ class Container: volume_mounts: Optional list of volume mounts. """ - image: Union[str, dict] + image: str | dict exposed_port: int - name: Optional[str] = None - healthcheck: Optional[HealthcheckSettings] = None - entrypoint_overrides: Optional[EntrypointOverridesSettings] = None - env: Optional[List[EnvVar]] = None - volume_mounts: Optional[List[VolumeMount]] = None + name: str | None = None + healthcheck: HealthcheckSettings | None = None + entrypoint_overrides: EntrypointOverridesSettings | None = None + env: list[EnvVar] | None = None + volume_mounts: list[VolumeMount] | None = None @dataclass_json @@ -250,7 +250,7 @@ class ContainerRegistrySettings: """ is_private: bool - credentials: Optional[ContainerRegistryCredentials] = None + credentials: ContainerRegistryCredentials | None = None @dataclass_json @@ -267,7 +267,7 @@ class ComputeResource: name: str size: int # Made optional since it's only used in API responses - is_available: Optional[bool] = None + is_available: bool | None = None @dataclass_json @@ -305,7 +305,7 @@ class UtilizationScalingTrigger: """ enabled: bool - threshold: Optional[float] = None + threshold: float | None = None @dataclass_json @@ -319,9 +319,9 @@ class ScalingTriggers: gpu_utilization: Optional trigger based on GPU utilization. """ - queue_load: Optional[QueueLoadScalingTrigger] = None - cpu_utilization: Optional[UtilizationScalingTrigger] = None - gpu_utilization: Optional[UtilizationScalingTrigger] = None + queue_load: QueueLoadScalingTrigger | None = None + cpu_utilization: UtilizationScalingTrigger | None = None + gpu_utilization: UtilizationScalingTrigger | None = None @dataclass_json @@ -365,17 +365,17 @@ class Deployment: """ name: str - containers: List[Container] + containers: list[Container] compute: ComputeResource container_registry_settings: ContainerRegistrySettings = field( default_factory=lambda: ContainerRegistrySettings(is_private=False) ) is_spot: bool = False - endpoint_base_url: Optional[str] = None - scaling: Optional[ScalingOptions] = None - created_at: Optional[str] = None + endpoint_base_url: str | None = None + scaling: ScalingOptions | None = None + created_at: str | None = None - _inference_client: Optional[InferenceClient] = None + _inference_client: InferenceClient | None = None def __str__(self): """Returns a string representation of the deployment, excluding sensitive information. @@ -386,7 +386,7 @@ def __str__(self): # Get all attributes except _inference_client attrs = {k: v for k, v in self.__dict__.items() if k != '_inference_client'} # Format each attribute - attr_strs = [f'{k}={repr(v)}' for k, v in attrs.items()] + attr_strs = [f'{k}={v!r}' for k, v in attrs.items()] return f'Deployment({", ".join(attr_strs)})' def __repr__(self): @@ -399,7 +399,7 @@ def __repr__(self): @classmethod def from_dict_with_inference_key( - cls, data: Dict[str, Any], inference_key: str = None + cls, data: dict[str, Any], inference_key: str | None = None ) -> 'Deployment': """Creates a Deployment instance from a dictionary with an inference key. @@ -446,10 +446,10 @@ def _validate_inference_client(self) -> None: def run_sync( self, - data: Dict[str, Any], + data: dict[str, Any], path: str = '', timeout_seconds: int = 60 * 5, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, http_method: str = 'POST', stream: bool = False, ) -> InferenceResponse: @@ -476,10 +476,10 @@ def run_sync( def run( self, - data: Dict[str, Any], + data: dict[str, Any], path: str = '', timeout_seconds: int = 60 * 5, - headers: Optional[Dict[str, str]] = None, + headers: dict[str, str] | None = None, http_method: str = 'POST', stream: bool = False, ): @@ -731,7 +731,7 @@ class ContainersService: deployment API, including CRUD operations for deployments and related resources. """ - def __init__(self, http_client: HTTPClient, inference_key: str = None) -> None: + def __init__(self, http_client: HTTPClient, inference_key: str | None = None) -> None: """Initializes the containers service. Args: @@ -741,11 +741,11 @@ def __init__(self, http_client: HTTPClient, inference_key: str = None) -> None: self.client = http_client self._inference_key = inference_key - def get_deployments(self) -> List[Deployment]: + def get_deployments(self) -> list[Deployment]: """Retrieves all container deployments. Returns: - List[Deployment]: List of all deployments. + list[Deployment]: List of all deployments. """ response = self.client.get(CONTAINER_DEPLOYMENTS_ENDPOINT) return [ @@ -853,14 +853,14 @@ def update_deployment_scaling_options( ) return ScalingOptions.from_dict(response.json()) - def get_deployment_replicas(self, deployment_name: str) -> List[ReplicaInfo]: + def get_deployment_replicas(self, deployment_name: str) -> list[ReplicaInfo]: """Retrieves information about deployment replicas. Args: deployment_name: Name of the deployment. Returns: - List[ReplicaInfo]: List of replica information. + list[ReplicaInfo]: List of replica information. """ response = self.client.get(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/replicas') return [ReplicaInfo.from_dict(replica) for replica in response.json()['list']] @@ -889,14 +889,14 @@ def resume_deployment(self, deployment_name: str) -> None: """ self.client.post(f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/resume') - def get_deployment_environment_variables(self, deployment_name: str) -> Dict[str, List[EnvVar]]: + def get_deployment_environment_variables(self, deployment_name: str) -> dict[str, list[EnvVar]]: """Retrieves environment variables for a deployment. Args: deployment_name: Name of the deployment. Returns: - Dict[str, List[EnvVar]]: Dictionary mapping container names to their environment variables. + dict[str, list[EnvVar]]: Dictionary mapping container names to their environment variables. """ response = self.client.get( f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables' @@ -909,8 +909,8 @@ def get_deployment_environment_variables(self, deployment_name: str) -> Dict[str return result def add_deployment_environment_variables( - self, deployment_name: str, container_name: str, env_vars: List[EnvVar] - ) -> Dict[str, List[EnvVar]]: + self, deployment_name: str, container_name: str, env_vars: list[EnvVar] + ) -> dict[str, list[EnvVar]]: """Adds environment variables to a container in a deployment. Args: @@ -919,7 +919,7 @@ def add_deployment_environment_variables( env_vars: List of environment variables to add. Returns: - Dict[str, List[EnvVar]]: Updated environment variables for all containers. + dict[str, list[EnvVar]]: Updated environment variables for all containers. """ response = self.client.post( f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables', @@ -936,8 +936,8 @@ def add_deployment_environment_variables( return result def update_deployment_environment_variables( - self, deployment_name: str, container_name: str, env_vars: List[EnvVar] - ) -> Dict[str, List[EnvVar]]: + self, deployment_name: str, container_name: str, env_vars: list[EnvVar] + ) -> dict[str, list[EnvVar]]: """Updates environment variables for a container in a deployment. Args: @@ -946,7 +946,7 @@ def update_deployment_environment_variables( env_vars: List of updated environment variables. Returns: - Dict[str, List[EnvVar]]: Updated environment variables for all containers. + dict[str, list[EnvVar]]: Updated environment variables for all containers. """ response = self.client.patch( f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables', @@ -963,8 +963,8 @@ def update_deployment_environment_variables( return result def delete_deployment_environment_variables( - self, deployment_name: str, container_name: str, env_var_names: List[str] - ) -> Dict[str, List[EnvVar]]: + self, deployment_name: str, container_name: str, env_var_names: list[str] + ) -> dict[str, list[EnvVar]]: """Deletes environment variables from a container in a deployment. Args: @@ -973,7 +973,7 @@ def delete_deployment_environment_variables( env_var_names: List of environment variable names to delete. Returns: - Dict[str, List[EnvVar]]: Updated environment variables for all containers. + dict[str, list[EnvVar]]: Updated environment variables for all containers. """ response = self.client.delete( f'{CONTAINER_DEPLOYMENTS_ENDPOINT}/{deployment_name}/environment-variables', @@ -987,16 +987,16 @@ def delete_deployment_environment_variables( return result def get_compute_resources( - self, size: int = None, is_available: bool = None - ) -> List[ComputeResource]: + self, size: int | None = None, is_available: bool | None = None + ) -> list[ComputeResource]: """Retrieves compute resources, optionally filtered by size and availability. Args: size: Optional size to filter resources by (e.g. 8 for 8x GPUs) - available: Optional boolean to filter by availability status + is_available: Optional boolean to filter by availability status Returns: - List[ComputeResource]: List of compute resources matching the filters. + list[ComputeResource]: List of compute resources matching the filters. If no filters provided, returns all resources. """ response = self.client.get(SERVERLESS_COMPUTE_RESOURCES_ENDPOINT) @@ -1013,11 +1013,11 @@ def get_compute_resources( # Function alias get_gpus = get_compute_resources - def get_secrets(self) -> List[Secret]: + def get_secrets(self) -> list[Secret]: """Retrieves all secrets. Returns: - List[Secret]: List of all secrets. + list[Secret]: List of all secrets. """ response = self.client.get(SECRETS_ENDPOINT) return [Secret.from_dict(secret) for secret in response.json()] @@ -1042,11 +1042,11 @@ def delete_secret(self, secret_name: str, force: bool = False) -> None: f'{SECRETS_ENDPOINT}/{secret_name}', params={'force': str(force).lower()} ) - def get_registry_credentials(self) -> List[RegistryCredential]: + def get_registry_credentials(self) -> list[RegistryCredential]: """Retrieves all registry credentials. Returns: - List[RegistryCredential]: List of all registry credentials. + list[RegistryCredential]: List of all registry credentials. """ response = self.client.get(CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT) return [RegistryCredential.from_dict(credential) for credential in response.json()] @@ -1068,7 +1068,7 @@ def delete_registry_credentials(self, credentials_name: str) -> None: """ self.client.delete(f'{CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT}/{credentials_name}') - def get_fileset_secrets(self) -> List[Secret]: + def get_fileset_secrets(self) -> list[Secret]: """Retrieves all fileset secrets. Returns: @@ -1086,9 +1086,10 @@ def delete_fileset_secret(self, secret_name: str) -> None: self.client.delete(f'{FILESET_SECRETS_ENDPOINT}/{secret_name}') def create_fileset_secret_from_file_paths( - self, secret_name: str, file_paths: List[str] + self, secret_name: str, file_paths: list[str] ) -> None: """Creates a new fileset secret. + A fileset secret is a secret that contains several files, and can be used to mount a directory with the files in a container. diff --git a/datacrunch/datacrunch.py b/datacrunch/datacrunch.py index 6989272..d95872e 100644 --- a/datacrunch/datacrunch.py +++ b/datacrunch/datacrunch.py @@ -1,30 +1,30 @@ +from datacrunch._version import __version__ from datacrunch.authentication.authentication import AuthenticationService from datacrunch.balance.balance import BalanceService +from datacrunch.constants import Constants +from datacrunch.containers.containers import ContainersService from datacrunch.http_client.http_client import HTTPClient from datacrunch.images.images import ImagesService from datacrunch.instance_types.instance_types import InstanceTypesService from datacrunch.instances.instances import InstancesService +from datacrunch.locations.locations import LocationsService from datacrunch.ssh_keys.ssh_keys import SSHKeysService from datacrunch.startup_scripts.startup_scripts import StartupScriptsService from datacrunch.volume_types.volume_types import VolumeTypesService from datacrunch.volumes.volumes import VolumesService -from datacrunch.containers.containers import ContainersService -from datacrunch.constants import Constants -from datacrunch.locations.locations import LocationsService -from datacrunch._version import __version__ class DataCrunchClient: - """Client for interacting with DataCrunch's public API""" + """Client for interacting with DataCrunch's public API.""" def __init__( self, client_id: str, client_secret: str, base_url: str = 'https://api.datacrunch.io/v1', - inference_key: str = None, + inference_key: str | None = None, ) -> None: - """The DataCrunch client + """The DataCrunch client. :param client_id: client id :type client_id: str @@ -35,7 +35,6 @@ def __init__( :param inference_key: inference key, optional :type inference_key: str, optional """ - # Validate that client_id and client_secret are not empty if not client_id or not client_secret: raise ValueError('client_id and client_secret must be provided') diff --git a/datacrunch/exceptions.py b/datacrunch/exceptions.py index 5669284..e93bd26 100644 --- a/datacrunch/exceptions.py +++ b/datacrunch/exceptions.py @@ -1,12 +1,13 @@ class APIException(Exception): """This exception is raised if there was an error from datacrunch's API. + Could be an invalid input, token etc. Raised when an API HTTP call response has a status code >= 400 """ def __init__(self, code: str, message: str) -> None: - """ + """API Exception. :param code: error code :type code: str diff --git a/datacrunch/helpers.py b/datacrunch/helpers.py index a0641e8..8bb4ff7 100644 --- a/datacrunch/helpers.py +++ b/datacrunch/helpers.py @@ -1,9 +1,8 @@ -from typing import Type import json -def stringify_class_object_properties(class_object: Type) -> str: - """Generates a json string representation of a class object's properties and values +def stringify_class_object_properties(class_object: type) -> str: + """Generates a json string representation of a class object's properties and values. :param class_object: An instance of a class :type class_object: Type @@ -12,7 +11,7 @@ def stringify_class_object_properties(class_object: Type) -> str: """ class_properties = { property: getattr(class_object, property, '') - for property in class_object.__dir__() + for property in class_object.__dir__() # noqa: A001 if property[:1] != '_' and type(getattr(class_object, property, '')).__name__ != 'method' } return json.dumps(class_properties, indent=2) diff --git a/datacrunch/http_client/http_client.py b/datacrunch/http_client/http_client.py index 71aba53..322662e 100644 --- a/datacrunch/http_client/http_client.py +++ b/datacrunch/http_client/http_client.py @@ -1,12 +1,13 @@ -import requests import json -from datacrunch.exceptions import APIException +import requests + from datacrunch._version import __version__ +from datacrunch.exceptions import APIException def handle_error(response: requests.Response) -> None: - """checks for the response status code and raises an exception if it's 400 or higher. + """Checks for the response status code and raises an exception if it's 400 or higher. :param response: the API call response :raises APIException: an api exception with message and error type code @@ -32,7 +33,9 @@ def __init__(self, auth_service, base_url: str) -> None: self._auth_service = auth_service self._auth_service.authenticate() - def post(self, url: str, json: dict = None, params: dict = None, **kwargs) -> requests.Response: + def post( + self, url: str, json: dict | None = None, params: dict | None = None, **kwargs + ) -> requests.Response: """Sends a POST request. A wrapper for the requests.post method. @@ -61,7 +64,9 @@ def post(self, url: str, json: dict = None, params: dict = None, **kwargs) -> re return response - def put(self, url: str, json: dict = None, params: dict = None, **kwargs) -> requests.Response: + def put( + self, url: str, json: dict | None = None, params: dict | None = None, **kwargs + ) -> requests.Response: """Sends a PUT request. A wrapper for the requests.put method. @@ -90,7 +95,7 @@ def put(self, url: str, json: dict = None, params: dict = None, **kwargs) -> req return response - def get(self, url: str, params: dict = None, **kwargs) -> requests.Response: + def get(self, url: str, params: dict | None = None, **kwargs) -> requests.Response: """Sends a GET request. A wrapper for the requests.get method. @@ -118,7 +123,7 @@ def get(self, url: str, params: dict = None, **kwargs) -> requests.Response: return response def patch( - self, url: str, json: dict = None, params: dict = None, **kwargs + self, url: str, json: dict | None = None, params: dict | None = None, **kwargs ) -> requests.Response: """Sends a PATCH request. @@ -149,7 +154,7 @@ def patch( return response def delete( - self, url: str, json: dict = None, params: dict = None, **kwargs + self, url: str, json: dict | None = None, params: dict | None = None, **kwargs ) -> requests.Response: """Sends a DELETE request. @@ -180,7 +185,7 @@ def delete( return response def _refresh_token_if_expired(self) -> None: - """refreshes the access token if it expired. + """Refreshes the access token if it expired. Uses the refresh token to refresh, and if the refresh token is also expired, uses the client credentials. @@ -194,7 +199,7 @@ def _refresh_token_if_expired(self) -> None: self._auth_service.authenticate() def _generate_headers(self) -> dict: - """generate the default headers for every request + """Generate the default headers for every request. :return: dict with request headers :rtype: dict @@ -207,7 +212,7 @@ def _generate_headers(self) -> dict: return headers def _generate_bearer_header(self) -> str: - """generate the authorization header Bearer string + """Generate the authorization header Bearer string. :return: Authorization header Bearer string :rtype: str @@ -215,7 +220,7 @@ def _generate_bearer_header(self) -> str: return f'Bearer {self._auth_service._access_token}' def _generate_user_agent(self) -> str: - """generate the user agent string. + """Generate the user agent string. :return: user agent string :rtype: str @@ -226,9 +231,9 @@ def _generate_user_agent(self) -> str: return f'datacrunch-python-v{self._version}-{client_id_truncated}' def _add_base_url(self, url: str) -> str: - """Adds the base url to the relative url + """Adds the base url to the relative url. - example: + Example: if the relative url is '/balance' and the base url is 'https://api.datacrunch.io/v1' then this method will return 'https://api.datacrunch.io/v1/balance' diff --git a/datacrunch/images/images.py b/datacrunch/images/images.py index 6e85ee8..8442e9a 100644 --- a/datacrunch/images/images.py +++ b/datacrunch/images/images.py @@ -1,14 +1,13 @@ -from typing import List from datacrunch.helpers import stringify_class_object_properties IMAGES_ENDPOINT = '/images' class Image: - """An image model class""" + """An image model class.""" - def __init__(self, id: str, name: str, image_type: str, details: List[str]) -> None: - """Initialize an image object + def __init__(self, id: str, name: str, image_type: str, details: list[str]) -> None: + """Initialize an image object. :param id: image id :type id: str @@ -17,7 +16,7 @@ def __init__(self, id: str, name: str, image_type: str, details: List[str]) -> N :param image_type: image type, e.g. 'ubuntu-20.04-cuda-11.0' :type image_type: str :param details: image details - :type details: List[str] + :type details: list[str] """ self._id = id self._name = name @@ -26,7 +25,7 @@ def __init__(self, id: str, name: str, image_type: str, details: List[str]) -> N @property def id(self) -> str: - """Get the image id + """Get the image id. :return: image id :rtype: str @@ -35,7 +34,7 @@ def id(self) -> str: @property def name(self) -> str: - """Get the image name + """Get the image name. :return: image name :rtype: str @@ -44,7 +43,7 @@ def name(self) -> str: @property def image_type(self) -> str: - """Get the image type + """Get the image type. :return: image type :rtype: str @@ -52,16 +51,16 @@ def image_type(self) -> str: return self._image_type @property - def details(self) -> List[str]: - """Get the image details + def details(self) -> list[str]: + """Get the image details. :return: image details - :rtype: List[str] + :rtype: list[str] """ return self._details def __str__(self) -> str: - """Returns a string of the json representation of the image + """Returns a string of the json representation of the image. :return: json representation of the image :rtype: str @@ -70,24 +69,20 @@ def __str__(self) -> str: class ImagesService: - """A service for interacting with the images endpoint""" + """A service for interacting with the images endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[Image]: - """Get the available instance images + def get(self) -> list[Image]: + """Get the available instance images. :return: list of images objects - :rtype: List[Image] + :rtype: list[Image] """ images = self._http_client.get(IMAGES_ENDPOINT).json() - image_objects = list( - map( - lambda image: Image( - image['id'], image['name'], image['image_type'], image['details'] - ), - images, - ) - ) + image_objects = [ + Image(image['id'], image['name'], image['image_type'], image['details']) + for image in images + ] return image_objects diff --git a/datacrunch/instance_types/instance_types.py b/datacrunch/instance_types/instance_types.py index 447adbd..ba7ab03 100644 --- a/datacrunch/instance_types/instance_types.py +++ b/datacrunch/instance_types/instance_types.py @@ -1,9 +1,9 @@ -from typing import List - INSTANCE_TYPES_ENDPOINT = '/instance-types' class InstanceType: + """Instance type.""" + def __init__( self, id: str, @@ -17,7 +17,7 @@ def __init__( gpu_memory: dict, storage: dict, ) -> None: - """Initialize an instance type object + """Initialize an instance type object. :param id: instance type id :type id: str @@ -53,7 +53,7 @@ def __init__( @property def id(self) -> str: - """Get the instance type id + """Get the instance type id. :return: instance type id :rtype: str @@ -62,7 +62,7 @@ def id(self) -> str: @property def instance_type(self) -> str: - """Get the instance type + """Get the instance type. :return: instance type. e.g. '8V100.48M' :rtype: str @@ -71,7 +71,7 @@ def instance_type(self) -> str: @property def price_per_hour(self) -> float: - """Get the instance type price per hour + """Get the instance type price per hour. :return: price per hour :rtype: float @@ -80,7 +80,7 @@ def price_per_hour(self) -> float: @property def spot_price_per_hour(self) -> float: - """Get the instance spot price per hour + """Get the instance spot price per hour. :return: spot price per hour :rtype: float @@ -89,7 +89,7 @@ def spot_price_per_hour(self) -> float: @property def description(self) -> str: - """Get the instance type description + """Get the instance type description. :return: instance type description :rtype: str @@ -98,7 +98,7 @@ def description(self) -> str: @property def cpu(self) -> dict: - """Get the instance type cpu details + """Get the instance type cpu details. :return: cpu details :rtype: dict @@ -107,7 +107,7 @@ def cpu(self) -> dict: @property def gpu(self) -> dict: - """Get the instance type gpu details + """Get the instance type gpu details. :return: gpu details :rtype: dict @@ -116,7 +116,7 @@ def gpu(self) -> dict: @property def memory(self) -> dict: - """Get the instance type memory details + """Get the instance type memory details. :return: memory details :rtype: dict @@ -125,7 +125,7 @@ def memory(self) -> dict: @property def gpu_memory(self) -> dict: - """Get the instance type gpu_memory details + """Get the instance type gpu_memory details. :return: gpu_memory details :rtype: dict @@ -134,7 +134,7 @@ def gpu_memory(self) -> dict: @property def storage(self) -> dict: - """Get the instance type storage details + """Get the instance type storage details. :return: storage details :rtype: dict @@ -142,7 +142,7 @@ def storage(self) -> dict: return self._storage def __str__(self) -> str: - """Prints the instance type + """Prints the instance type. :return: instance type string representation :rtype: str @@ -162,34 +162,32 @@ def __str__(self) -> str: class InstanceTypesService: - """A service for interacting with the instance-types endpoint""" + """A service for interacting with the instance-types endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[InstanceType]: - """Get all instance types + def get(self) -> list[InstanceType]: + """Get all instance types. :return: list of instance type objects - :rtype: List[InstanceType] + :rtype: list[InstanceType] """ instance_types = self._http_client.get(INSTANCE_TYPES_ENDPOINT).json() - instance_type_objects = list( - map( - lambda instance_type: InstanceType( - id=instance_type['id'], - instance_type=instance_type['instance_type'], - price_per_hour=instance_type['price_per_hour'], - spot_price_per_hour=instance_type['spot_price'], - description=instance_type['description'], - cpu=instance_type['cpu'], - gpu=instance_type['gpu'], - memory=instance_type['memory'], - gpu_memory=instance_type['gpu_memory'], - storage=instance_type['storage'], - ), - instance_types, + instance_type_objects = [ + InstanceType( + id=instance_type['id'], + instance_type=instance_type['instance_type'], + price_per_hour=instance_type['price_per_hour'], + spot_price_per_hour=instance_type['spot_price'], + description=instance_type['description'], + cpu=instance_type['cpu'], + gpu=instance_type['gpu'], + memory=instance_type['memory'], + gpu_memory=instance_type['gpu_memory'], + storage=instance_type['storage'], ) - ) + for instance_type in instance_types + ] return instance_type_objects diff --git a/datacrunch/instances/instances.py b/datacrunch/instances/instances.py index b10e851..9bbf8a5 100644 --- a/datacrunch/instances/instances.py +++ b/datacrunch/instances/instances.py @@ -1,9 +1,11 @@ -import time import itertools -from typing import List, Union, Optional, Dict, Literal +import time from dataclasses import dataclass +from typing import Literal + from dataclasses_json import dataclass_json -from datacrunch.constants import Locations, InstanceStatus + +from datacrunch.constants import InstanceStatus, Locations INSTANCES_ENDPOINT = '/instances' @@ -47,22 +49,22 @@ class Instance: description: str status: str created_at: str - ssh_key_ids: List[str] + ssh_key_ids: list[str] cpu: dict gpu: dict memory: dict storage: dict gpu_memory: dict # Can be None if instance is still not provisioned - ip: Optional[str] = None + ip: str | None = None # Can be None if instance is still not provisioned - os_volume_id: Optional[str] = None + os_volume_id: str | None = None location: str = Locations.FIN_03 - image: Optional[str] = None - startup_script_id: Optional[str] = None + image: str | None = None + startup_script_id: str | None = None is_spot: bool = False - contract: Optional[Contract] = None - pricing: Optional[Pricing] = None + contract: Contract | None = None + pricing: Pricing | None = None class InstancesService: @@ -80,7 +82,7 @@ def __init__(self, http_client) -> None: """ self._http_client = http_client - def get(self, status: Optional[str] = None) -> List[Instance]: + def get(self, status: str | None = None) -> list[Instance]: """Retrieves all non-deleted instances or instances with specific status. Args: @@ -119,14 +121,14 @@ def create( description: str, ssh_key_ids: list = [], location: str = Locations.FIN_03, - startup_script_id: Optional[str] = None, - volumes: Optional[List[Dict]] = None, - existing_volumes: Optional[List[str]] = None, - os_volume: Optional[Dict] = None, + startup_script_id: str | None = None, + volumes: list[dict] | None = None, + existing_volumes: list[str] | None = None, + os_volume: dict | None = None, is_spot: bool = False, - contract: Optional[Contract] = None, - pricing: Optional[Pricing] = None, - coupon: Optional[str] = None, + contract: Contract | None = None, + pricing: Pricing | None = None, + coupon: str | None = None, *, max_wait_time: float = 180, initial_interval: float = 0.5, @@ -199,9 +201,9 @@ def create( def action( self, - id_list: Union[List[str], str], + id_list: list[str] | str, action: str, - volume_ids: Optional[List[str]] = None, + volume_ids: list[str] | None = None, ) -> None: """Performs an action on one or more instances. @@ -225,7 +227,7 @@ def is_available( self, instance_type: str, is_spot: bool = False, - location_code: Optional[str] = None, + location_code: str | None = None, ) -> bool: """Checks if a specific instance type is available for deployment. @@ -243,8 +245,8 @@ def is_available( return self._http_client.get(url, query_params).json() def get_availabilities( - self, is_spot: Optional[bool] = None, location_code: Optional[str] = None - ) -> List[Dict]: + self, is_spot: bool | None = None, location_code: str | None = None + ) -> list[dict]: """Retrieves a list of available instance types across locations. Args: diff --git a/datacrunch/locations/locations.py b/datacrunch/locations/locations.py index f980016..6ac792e 100644 --- a/datacrunch/locations/locations.py +++ b/datacrunch/locations/locations.py @@ -1,15 +1,13 @@ -from typing import List - LOCATIONS_ENDPOINT = '/locations' class LocationsService: - """A service for interacting with the locations endpoint""" + """A service for interacting with the locations endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[dict]: - """Get all locations""" + def get(self) -> list[dict]: + """Get all locations.""" locations = self._http_client.get(LOCATIONS_ENDPOINT).json() return locations diff --git a/datacrunch/ssh_keys/ssh_keys.py b/datacrunch/ssh_keys/ssh_keys.py index c009786..4640dc9 100644 --- a/datacrunch/ssh_keys/ssh_keys.py +++ b/datacrunch/ssh_keys/ssh_keys.py @@ -1,13 +1,11 @@ -from typing import List - SSHKEYS_ENDPOINT = '/sshkeys' class SSHKey: - """An SSH key model class""" + """An SSH key model class.""" def __init__(self, id: str, name: str, public_key: str) -> None: - """Initialize a new SSH key object + """Initialize a new SSH key object. :param id: SSH key id :type id: str @@ -22,7 +20,7 @@ def __init__(self, id: str, name: str, public_key: str) -> None: @property def id(self) -> str: - """Get the SSH key id + """Get the SSH key id. :return: SSH key id :rtype: str @@ -31,7 +29,7 @@ def id(self) -> str: @property def name(self) -> str: - """Get the SSH key name + """Get the SSH key name. :return: SSH key name :rtype: str @@ -40,7 +38,7 @@ def name(self) -> str: @property def public_key(self) -> str: - """Get the SSH key public key value + """Get the SSH key public key value. :return: public SSH key :rtype: str @@ -49,19 +47,19 @@ def public_key(self) -> str: class SSHKeysService: - """A service for interacting with the SSH keys endpoint""" + """A service for interacting with the SSH keys endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[SSHKey]: - """Get all of the client's SSH keys + def get(self) -> list[SSHKey]: + """Get all of the client's SSH keys. :return: list of SSH keys objects - :rtype: List[SSHKey] + :rtype: list[SSHKey] """ keys = self._http_client.get(SSHKEYS_ENDPOINT).json() - keys_object_list = list(map(lambda key: SSHKey(key['id'], key['name'], key['key']), keys)) + keys_object_list = [SSHKey(key['id'], key['name'], key['key']) for key in keys] return keys_object_list @@ -77,18 +75,18 @@ def get_by_id(self, id: str) -> SSHKey: key_object = SSHKey(key_dict['id'], key_dict['name'], key_dict['key']) return key_object - def delete(self, id_list: List[str]) -> None: - """Delete multiple SSH keys by id + def delete(self, id_list: list[str]) -> None: + """Delete multiple SSH keys by id. :param id_list: list of SSH keys ids - :type id_list: List[str] + :type id_list: list[str] """ payload = {'keys': id_list} self._http_client.delete(SSHKEYS_ENDPOINT, json=payload) return def delete_by_id(self, id: str) -> None: - """Delete a single SSH key by id + """Delete a single SSH key by id. :param id: SSH key id :type id: str @@ -97,7 +95,7 @@ def delete_by_id(self, id: str) -> None: return def create(self, name: str, key: str) -> SSHKey: - """Create a new SSH key + """Create a new SSH key. :param name: SSH key name :type name: str diff --git a/datacrunch/startup_scripts/startup_scripts.py b/datacrunch/startup_scripts/startup_scripts.py index 7f9a30e..2d7ec53 100644 --- a/datacrunch/startup_scripts/startup_scripts.py +++ b/datacrunch/startup_scripts/startup_scripts.py @@ -1,13 +1,11 @@ -from typing import List - STARTUP_SCRIPTS_ENDPOINT = '/scripts' class StartupScript: - """A startup script model class""" + """A startup script model class.""" def __init__(self, id: str, name: str, script: str) -> None: - """Initialize a new startup script object + """Initialize a new startup script object. :param id: startup script id :type id: str @@ -22,7 +20,7 @@ def __init__(self, id: str, name: str, script: str) -> None: @property def id(self) -> str: - """Get the startup script id + """Get the startup script id. :return: startup script id :rtype: str @@ -31,7 +29,7 @@ def id(self) -> str: @property def name(self) -> str: - """Get the startup script name + """Get the startup script name. :return: startup script name :rtype: str @@ -40,7 +38,7 @@ def name(self) -> str: @property def script(self) -> str: - """Get the actual startup script code + """Get the actual startup script code. :return: startup script text :rtype: str @@ -49,24 +47,21 @@ def script(self) -> str: class StartupScriptsService: - """A service for interacting with the startup scripts endpoint""" + """A service for interacting with the startup scripts endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[StartupScript]: - """Get all of the client's startup scripts + def get(self) -> list[StartupScript]: + """Get all of the client's startup scripts. :return: list of startup script objects - :rtype: List[StartupScript] + :rtype: list[StartupScript] """ scripts = self._http_client.get(STARTUP_SCRIPTS_ENDPOINT).json() - scripts_objects = list( - map( - lambda script: StartupScript(script['id'], script['name'], script['script']), - scripts, - ) - ) + scripts_objects = [ + StartupScript(script['id'], script['name'], script['script']) for script in scripts + ] return scripts_objects def get_by_id(self, id) -> StartupScript: @@ -81,18 +76,18 @@ def get_by_id(self, id) -> StartupScript: return StartupScript(script['id'], script['name'], script['script']) - def delete(self, id_list: List[str]) -> None: - """Delete multiple startup scripts by id + def delete(self, id_list: list[str]) -> None: + """Delete multiple startup scripts by id. :param id_list: list of startup scripts ids - :type id_list: List[str] + :type id_list: list[str] """ payload = {'scripts': id_list} self._http_client.delete(STARTUP_SCRIPTS_ENDPOINT, json=payload) return def delete_by_id(self, id: str) -> None: - """Delete a single startup script by id + """Delete a single startup script by id. :param id: startup script id :type id: str @@ -101,7 +96,7 @@ def delete_by_id(self, id: str) -> None: return def create(self, name: str, script: str) -> StartupScript: - """Create a new startup script + """Create a new startup script. :param name: startup script name :type name: str diff --git a/datacrunch/volume_types/volume_types.py b/datacrunch/volume_types/volume_types.py index 4f33aac..8b0db98 100644 --- a/datacrunch/volume_types/volume_types.py +++ b/datacrunch/volume_types/volume_types.py @@ -1,11 +1,11 @@ -from typing import List - VOLUME_TYPES_ENDPOINT = '/volume-types' class VolumeType: + """Volume type.""" + def __init__(self, type: str, price_per_month_per_gb: float) -> None: - """Initialize a volume type object + """Initialize a volume type object. :param type: volume type name :type type: str @@ -17,7 +17,7 @@ def __init__(self, type: str, price_per_month_per_gb: float) -> None: @property def type(self) -> str: - """Get the volume type + """Get the volume type. :return: volume type :rtype: str @@ -26,7 +26,7 @@ def type(self) -> str: @property def price_per_month_per_gb(self) -> str: - """Get the volume price_per_month_per_gb + """Get the volume price_per_month_per_gb. :return: volume price_per_month_per_gb :rtype: str @@ -34,7 +34,7 @@ def price_per_month_per_gb(self) -> str: return self._price_per_month_per_gb def __str__(self) -> str: - """Prints the volume type + """Prints the volume type. :return: volume type string representation :rtype: str @@ -43,26 +43,24 @@ def __str__(self) -> str: class VolumeTypesService: - """A service for interacting with the volume-types endpoint""" + """A service for interacting with the volume-types endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self) -> List[VolumeType]: - """Get all volume types + def get(self) -> list[VolumeType]: + """Get all volume types. :return: list of volume type objects - :rtype: List[VolumesType] + :rtype: list[VolumesType] """ volume_types = self._http_client.get(VOLUME_TYPES_ENDPOINT).json() - volume_type_objects = list( - map( - lambda volume_type: VolumeType( - type=volume_type['type'], - price_per_month_per_gb=volume_type['price']['price_per_month_per_gb'], - ), - volume_types, + volume_type_objects = [ + VolumeType( + type=volume_type['type'], + price_per_month_per_gb=volume_type['price']['price_per_month_per_gb'], ) - ) + for volume_type in volume_types + ] return volume_type_objects diff --git a/datacrunch/volumes/volumes.py b/datacrunch/volumes/volumes.py index 69dd23b..554ba78 100644 --- a/datacrunch/volumes/volumes.py +++ b/datacrunch/volumes/volumes.py @@ -1,12 +1,11 @@ -from typing import List, Union, Optional -from datacrunch.constants import VolumeActions, Locations +from datacrunch.constants import Locations, VolumeActions from datacrunch.helpers import stringify_class_object_properties VOLUMES_ENDPOINT = '/volumes' class Volume: - """A volume model class""" + """A volume model class.""" def __init__( self, @@ -17,13 +16,13 @@ def __init__( type: str, is_os_volume: bool, created_at: str, - target: str = None, + target: str | None = None, location: str = Locations.FIN_03, - instance_id: str = None, - ssh_key_ids: List[str] = [], - deleted_at: str = None, + instance_id: str | None = None, + ssh_key_ids: list[str] = [], + deleted_at: str | None = None, ) -> None: - """Initialize the volume object + """Initialize the volume object. :param id: volume id :type id: str @@ -46,7 +45,7 @@ def __init__( :param instance_id: the instance id the volume is attached to, None if detached :type instance_id: str :param ssh_key_ids: list of ssh keys ids - :type ssh_key_ids: List[str] + :type ssh_key_ids: list[str] :param deleted_at: the time the volume was deleted (UTC), defaults to None :type deleted_at: str, optional """ @@ -65,7 +64,7 @@ def __init__( @property def id(self) -> str: - """Get the volume id + """Get the volume id. :return: volume id :rtype: str @@ -74,7 +73,7 @@ def id(self) -> str: @property def status(self) -> str: - """Get the volume status + """Get the volume status. :return: volume status :rtype: str @@ -83,7 +82,7 @@ def status(self) -> str: @property def name(self) -> str: - """Get the volume name + """Get the volume name. :return: volume name :rtype: str @@ -92,7 +91,7 @@ def name(self) -> str: @property def size(self) -> int: - """Get the volume size + """Get the volume size. :return: volume size :rtype: int @@ -101,7 +100,7 @@ def size(self) -> int: @property def type(self) -> int: - """Get the volume type + """Get the volume type. :return: volume type :rtype: string @@ -110,7 +109,7 @@ def type(self) -> int: @property def is_os_volume(self) -> bool: - """Return true iff the volume contains an operating system + """Return true iff the volume contains an operating system. :return: true iff the volume contains an OS :rtype: bool @@ -119,7 +118,7 @@ def is_os_volume(self) -> bool: @property def created_at(self) -> str: - """Get the time when the volume was created (UTC) + """Get the time when the volume was created (UTC). :return: time :rtype: str @@ -127,8 +126,8 @@ def created_at(self) -> str: return self._created_at @property - def target(self) -> Optional[str]: - """Get the target device + def target(self) -> str | None: + """Get the target device. :return: target device :rtype: str, optional @@ -137,7 +136,7 @@ def target(self) -> Optional[str]: @property def location(self) -> str: - """Get the volume datacenter location + """Get the volume datacenter location. :return: datacenter location :rtype: str @@ -145,8 +144,8 @@ def location(self) -> str: return self._location @property - def instance_id(self) -> Optional[str]: - """Get the instance id the volume is attached to, if attached. Otherwise None + def instance_id(self) -> str | None: + """Get the instance id the volume is attached to, if attached. Otherwise None. :return: instance id if attached, None otherwise :rtype: str, optional @@ -154,17 +153,17 @@ def instance_id(self) -> Optional[str]: return self._instance_id @property - def ssh_key_ids(self) -> List[str]: - """Get the SSH key IDs of the instance + def ssh_key_ids(self) -> list[str]: + """Get the SSH key IDs of the instance. :return: SSH key IDs - :rtype: List[str] + :rtype: list[str] """ return self._ssh_key_ids @property - def deleted_at(self) -> Optional[str]: - """Get the time when the volume was deleted (UTC) + def deleted_at(self) -> str | None: + """Get the time when the volume was deleted (UTC). :return: time :rtype: str @@ -173,14 +172,13 @@ def deleted_at(self) -> Optional[str]: @classmethod def create_from_dict(cls: 'Volume', volume_dict: dict) -> 'Volume': - """Create a Volume object from a dictionary + """Create a Volume object from a dictionary. :param volume_dict: dictionary representing the volume :type volume_dict: dict :return: Volume :rtype: Volume """ - return cls( id=volume_dict['id'], status=volume_dict['status'], @@ -197,7 +195,7 @@ def create_from_dict(cls: 'Volume', volume_dict: dict) -> 'Volume': ) def __str__(self) -> str: - """Returns a string of the json representation of the volume + """Returns a string of the json representation of the volume. :return: json representation of the volume :rtype: str @@ -206,24 +204,24 @@ def __str__(self) -> str: class VolumesService: - """A service for interacting with the volumes endpoint""" + """A service for interacting with the volumes endpoint.""" def __init__(self, http_client) -> None: self._http_client = http_client - def get(self, status: str = None) -> List[Volume]: + def get(self, status: str | None = None) -> list[Volume]: """Get all of the client's non-deleted volumes, or volumes with specific status. :param status: optional, status of the volumes, defaults to None :type status: str, optional :return: list of volume details objects - :rtype: List[Volume] + :rtype: list[Volume] """ volumes_dict = self._http_client.get(VOLUMES_ENDPOINT, params={'status': status}).json() return list(map(Volume.create_from_dict, volumes_dict)) def get_by_id(self, id: str) -> Volume: - """Get a specific volume by its + """Get a specific volume by its. :param id: volume id :type id: str @@ -234,11 +232,11 @@ def get_by_id(self, id: str) -> Volume: return Volume.create_from_dict(volume_dict) - def get_in_trash(self) -> List[Volume]: - """Get all volumes that are in trash + def get_in_trash(self) -> list[Volume]: + """Get all volumes that are in trash. :return: list of volume details objects - :rtype: List[Volume] + :rtype: list[Volume] """ volumes_dicts = self._http_client.get(VOLUMES_ENDPOINT + '/trash').json() @@ -249,10 +247,10 @@ def create( type: str, name: str, size: int, - instance_id: str = None, + instance_id: str | None = None, location: str = Locations.FIN_03, ) -> Volume: - """Create new volume + """Create new volume. :param type: volume type :type type: str @@ -278,12 +276,13 @@ def create( volume = self.get_by_id(id) return volume - def attach(self, id_list: Union[List[str], str], instance_id: str) -> None: - """Attach multiple volumes or single volume to an instance + def attach(self, id_list: list[str] | str, instance_id: str) -> None: + """Attach multiple volumes or single volume to an instance. + Note: the instance needs to be shut-down (offline) :param id_list: list of volume ids, or a volume id - :type id_list: Union[List[str], str] + :type id_list: Union[list[str], str] :param instance_id: instance id the volume(s) will be attached to :type instance_id: str """ @@ -296,12 +295,13 @@ def attach(self, id_list: Union[List[str], str], instance_id: str) -> None: self._http_client.put(VOLUMES_ENDPOINT, json=payload) return - def detach(self, id_list: Union[List[str], str]) -> None: - """Detach multiple volumes or single volume from an instance(s) + def detach(self, id_list: list[str] | str) -> None: + """Detach multiple volumes or single volume from an instance(s). + Note: the instances need to be shut-down (offline) :param id_list: list of volume ids, or a volume id - :type id_list: Union[List[str], str] + :type id_list: Union[list[str], str] """ payload = { 'id': id_list, @@ -311,17 +311,17 @@ def detach(self, id_list: Union[List[str], str]) -> None: self._http_client.put(VOLUMES_ENDPOINT, json=payload) return - def clone(self, id: str, name: str = None, type: str = None) -> Volume: - """Clone a volume or multiple volumes + def clone(self, id: str, name: str | None = None, type: str | None = None) -> Volume: + """Clone a volume or multiple volumes. :param id: volume id or list of volume ids - :type id: str or List[str] + :type id: str or list[str] :param name: new volume name :type name: str :param type: volume type :type type: str, optional :return: the new volume object, or a list of volume objects if cloned mutliple volumes - :rtype: Volume or List[Volume] + :rtype: Volume or list[Volume] """ payload = {'id': id, 'action': VolumeActions.CLONE, 'name': name, 'type': type} @@ -329,7 +329,7 @@ def clone(self, id: str, name: str = None, type: str = None) -> Volume: volume_ids_array = self._http_client.put(VOLUMES_ENDPOINT, json=payload).json() # map the IDs into Volume objects - volumes_array = list(map(lambda volume_id: self.get_by_id(volume_id), volume_ids_array)) + volumes_array = [self.get_by_id(volume_id) for volume_id in volume_ids_array] # if the array has only one element, return that element if len(volumes_array) == 1: @@ -338,11 +338,11 @@ def clone(self, id: str, name: str = None, type: str = None) -> Volume: # otherwise return the volumes array return volumes_array - def rename(self, id_list: Union[List[str], str], name: str) -> None: - """Rename multiple volumes or single volume + def rename(self, id_list: list[str] | str, name: str) -> None: + """Rename multiple volumes or single volume. :param id_list: list of volume ids, or a volume id - :type id_list: Union[List[str], str] + :type id_list: Union[list[str], str] :param name: new name :type name: str """ @@ -351,11 +351,11 @@ def rename(self, id_list: Union[List[str], str], name: str) -> None: self._http_client.put(VOLUMES_ENDPOINT, json=payload) return - def increase_size(self, id_list: Union[List[str], str], size: int) -> None: - """Increase size of multiple volumes or single volume + def increase_size(self, id_list: list[str] | str, size: int) -> None: + """Increase size of multiple volumes or single volume. :param id_list: list of volume ids, or a volume id - :type id_list: Union[List[str], str] + :type id_list: Union[list[str], str] :param size: new size in GB :type size: int """ @@ -368,12 +368,13 @@ def increase_size(self, id_list: Union[List[str], str], size: int) -> None: self._http_client.put(VOLUMES_ENDPOINT, json=payload) return - def delete(self, id_list: Union[List[str], str], is_permanent: bool = False) -> None: - """Delete multiple volumes or single volume + def delete(self, id_list: list[str] | str, is_permanent: bool = False) -> None: + """Delete multiple volumes or single volume. + Note: if attached to any instances, they need to be shut-down (offline) :param id_list: list of volume ids, or a volume id - :type id_list: Union[List[str], str] + :type id_list: Union[list[str], str] """ payload = { 'id': id_list, diff --git a/docs/source/conf.py b/docs/source/conf.py index 43ea39b..09297e1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -13,16 +12,18 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import datetime -from importlib.metadata import version as pkg_version, PackageNotFoundError -from recommonmark.parser import CommonMarkParser import os import sys +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as pkg_version + +from recommonmark.parser import CommonMarkParser # -- Project information ----------------------------------------------------- current_year = datetime.datetime.now().year project = 'DataCrunch Python SDK' -copyright = f'{current_year}, DataCrunch.io' +copyright = f'{current_year}, DataCrunch.io' # noqa: A001 author = 'DataCrunch.io' try: diff --git a/examples/advanced_create_instance.py b/examples/advanced_create_instance.py index 9cf6a0c..77f164a 100644 --- a/examples/advanced_create_instance.py +++ b/examples/advanced_create_instance.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient from datacrunch.exceptions import APIException @@ -36,7 +37,7 @@ # Get all SSH keys ssh_keys = datacrunch.ssh_keys.get() - ssh_keys_ids = list(map(lambda ssh_key: ssh_key.id, ssh_keys)) + ssh_keys_ids = [ssh_key.id for ssh_key in ssh_keys] # Get our current balance balance = datacrunch.balance.get() diff --git a/examples/containers/calling_the_endpoint_asynchronously.py b/examples/containers/calling_the_endpoint_asynchronously.py index fd98dbe..28a8bcb 100644 --- a/examples/containers/calling_the_endpoint_asynchronously.py +++ b/examples/containers/calling_the_endpoint_asynchronously.py @@ -1,5 +1,6 @@ import os from time import sleep + from datacrunch import DataCrunchClient from datacrunch.InferenceClient.inference_client import AsyncStatus diff --git a/examples/containers/calling_the_endpoint_synchronously.py b/examples/containers/calling_the_endpoint_synchronously.py index 407edd3..28443ca 100644 --- a/examples/containers/calling_the_endpoint_synchronously.py +++ b/examples/containers/calling_the_endpoint_synchronously.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Configuration - replace with your deployment name diff --git a/examples/containers/calling_the_endpoint_with_inference_key.py b/examples/containers/calling_the_endpoint_with_inference_key.py index 7d51032..e6bc52f 100644 --- a/examples/containers/calling_the_endpoint_with_inference_key.py +++ b/examples/containers/calling_the_endpoint_with_inference_key.py @@ -1,4 +1,5 @@ import os + from datacrunch.InferenceClient import InferenceClient # Get inference key and endpoint base url from environment variables diff --git a/examples/containers/calling_the_endpoint_with_inference_key_async.py b/examples/containers/calling_the_endpoint_with_inference_key_async.py index 85234b9..c23eca1 100644 --- a/examples/containers/calling_the_endpoint_with_inference_key_async.py +++ b/examples/containers/calling_the_endpoint_with_inference_key_async.py @@ -1,5 +1,6 @@ import os from time import sleep + from datacrunch.InferenceClient import InferenceClient from datacrunch.InferenceClient.inference_client import AsyncStatus diff --git a/examples/containers/compute_resources_example.py b/examples/containers/compute_resources_example.py index 825a01b..219e523 100644 --- a/examples/containers/compute_resources_example.py +++ b/examples/containers/compute_resources_example.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/examples/containers/container_deployments_example.py b/examples/containers/container_deployments_example.py index ad39f96..e5404b8 100644 --- a/examples/containers/container_deployments_example.py +++ b/examples/containers/container_deployments_example.py @@ -8,25 +8,25 @@ import time from datacrunch import DataCrunchClient -from datacrunch.exceptions import APIException from datacrunch.containers import ( - Container, ComputeResource, + Container, + ContainerDeploymentStatus, + ContainerRegistrySettings, + Deployment, EnvVar, EnvVarType, + GeneralStorageMount, + HealthcheckSettings, + QueueLoadScalingTrigger, ScalingOptions, ScalingPolicy, ScalingTriggers, - QueueLoadScalingTrigger, - UtilizationScalingTrigger, - HealthcheckSettings, - GeneralStorageMount, SecretMount, SharedFileSystemMount, - ContainerRegistrySettings, - Deployment, - ContainerDeploymentStatus, + UtilizationScalingTrigger, ) +from datacrunch.exceptions import APIException # Configuration constants DEPLOYMENT_NAME = 'my-deployment' @@ -57,7 +57,7 @@ def wait_for_deployment_health( Returns: bool: True if deployment is healthy, False otherwise """ - for attempt in range(max_attempts): + for _attempt in range(max_attempts): try: status = client.containers.get_deployment_status(deployment_name) print(f'Deployment status: {status}') diff --git a/examples/containers/delete_deployment_example.py b/examples/containers/delete_deployment_example.py index 274bfca..f135aed 100644 --- a/examples/containers/delete_deployment_example.py +++ b/examples/containers/delete_deployment_example.py @@ -1,6 +1,7 @@ """Example script demonstrating deleting a deployment using the DataCrunch API.""" import os + from datacrunch import DataCrunchClient DEPLOYMENT_NAME = 'sglang-deployment-example-20250411-160652' diff --git a/examples/containers/environment_variables_example.py b/examples/containers/environment_variables_example.py index 092ac58..69dd5ba 100644 --- a/examples/containers/environment_variables_example.py +++ b/examples/containers/environment_variables_example.py @@ -8,9 +8,9 @@ """ import os -from datacrunch.containers import EnvVar, EnvVarType + from datacrunch import DataCrunchClient -from typing import Dict, List +from datacrunch.containers import EnvVar, EnvVarType # Get client secret and id from environment variables DATACRUNCH_CLIENT_ID = os.environ.get('DATACRUNCH_CLIENT_ID') @@ -24,12 +24,12 @@ CONTAINER_NAME = 'main' -def print_env_vars(env_vars: Dict[str, List[EnvVar]]) -> None: +def print_env_vars(env_vars: dict[str, list[EnvVar]]) -> None: """Helper function to print environment variables""" print('\nCurrent environment variables:') - for container_name, vars in env_vars.items(): + for container_name, ev in env_vars.items(): print(f'\nContainer: {container_name}') - for var in vars: + for var in ev: print(f' {var.name}: {var.value_or_reference_to_secret} ({var.type})') diff --git a/examples/containers/fileset_secret_example.py b/examples/containers/fileset_secret_example.py index 65ca539..edb760f 100644 --- a/examples/containers/fileset_secret_example.py +++ b/examples/containers/fileset_secret_example.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Fileset secrets are a way to mount sensitive files like API keys, certs, and credentials securely inside a container, without hardcoding them in the image or env vars. diff --git a/examples/containers/registry_credentials_example.py b/examples/containers/registry_credentials_example.py index d017850..23179f3 100644 --- a/examples/containers/registry_credentials_example.py +++ b/examples/containers/registry_credentials_example.py @@ -1,11 +1,12 @@ import os + from datacrunch import DataCrunchClient from datacrunch.containers import ( - DockerHubCredentials, - GithubCredentials, - GCRCredentials, AWSECRCredentials, CustomRegistryCredentials, + DockerHubCredentials, + GCRCredentials, + GithubCredentials, ) # Get client secret and id from environment variables diff --git a/examples/containers/secrets_example.py b/examples/containers/secrets_example.py index d8ab2af..0418e7d 100644 --- a/examples/containers/secrets_example.py +++ b/examples/containers/secrets_example.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/examples/containers/sglang_deployment_example.py b/examples/containers/sglang_deployment_example.py index 5756d5e..9a12e77 100644 --- a/examples/containers/sglang_deployment_example.py +++ b/examples/containers/sglang_deployment_example.py @@ -4,29 +4,30 @@ including creation, monitoring, testing, and cleanup. """ +import json import os -import time import signal import sys -import json +import time from datetime import datetime + from datacrunch import DataCrunchClient -from datacrunch.exceptions import APIException from datacrunch.containers import ( - Container, ComputeResource, + Container, + ContainerDeploymentStatus, + Deployment, + EntrypointOverridesSettings, + EnvVar, + EnvVarType, + HealthcheckSettings, + QueueLoadScalingTrigger, ScalingOptions, ScalingPolicy, ScalingTriggers, - QueueLoadScalingTrigger, UtilizationScalingTrigger, - HealthcheckSettings, - EntrypointOverridesSettings, - EnvVar, - EnvVarType, - Deployment, - ContainerDeploymentStatus, ) +from datacrunch.exceptions import APIException CURRENT_TIMESTAMP = datetime.now().strftime('%Y%m%d-%H%M%S').lower() # e.g. 20250403-120000 @@ -88,7 +89,7 @@ def cleanup_resources(datacrunch_client: DataCrunchClient) -> None: print(f'Error during cleanup: {e}') -def graceful_shutdown(signum, frame) -> None: +def graceful_shutdown(signum, _frame) -> None: """Handle graceful shutdown on signals.""" print(f'\nSignal {signum} received, cleaning up resources...') try: diff --git a/examples/containers/update_deployment_scaling_example.py b/examples/containers/update_deployment_scaling_example.py index 74db5ae..f45b2e2 100644 --- a/examples/containers/update_deployment_scaling_example.py +++ b/examples/containers/update_deployment_scaling_example.py @@ -6,15 +6,14 @@ import os from datacrunch import DataCrunchClient -from datacrunch.exceptions import APIException from datacrunch.containers import ( + QueueLoadScalingTrigger, ScalingOptions, ScalingPolicy, ScalingTriggers, - QueueLoadScalingTrigger, UtilizationScalingTrigger, ) - +from datacrunch.exceptions import APIException # Get deployment name, client secret and id from environment variables DEPLOYMENT_NAME = os.environ.get('DATACRUNCH_DEPLOYMENT_NAME') diff --git a/examples/instance_actions.py b/examples/instance_actions.py index 09c9225..708a39f 100644 --- a/examples/instance_actions.py +++ b/examples/instance_actions.py @@ -1,9 +1,9 @@ import os import time + from datacrunch import DataCrunchClient from datacrunch.exceptions import APIException - # Get client secret and id from environment variables DATACRUNCH_CLIENT_ID = os.environ.get('DATACRUNCH_CLIENT_ID') DATACRUNCH_CLIENT_SECRET = os.environ.get('DATACRUNCH_CLIENT_SECRET') @@ -13,7 +13,7 @@ # Get all SSH keys ssh_keys = datacrunch.ssh_keys.get() -ssh_keys_ids = list(map(lambda ssh_key: ssh_key.id, ssh_keys)) +ssh_keys_ids = [ssh_key.id for ssh_key in ssh_keys] # Create a new 1V100.6V instance instance = datacrunch.instances.create( diff --git a/examples/instances_and_volumes.py b/examples/instances_and_volumes.py index 6d50887..8af410c 100644 --- a/examples/instances_and_volumes.py +++ b/examples/instances_and_volumes.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/examples/simple_create_instance.py b/examples/simple_create_instance.py index a55126e..576da30 100644 --- a/examples/simple_create_instance.py +++ b/examples/simple_create_instance.py @@ -1,7 +1,8 @@ -import time import os +import time + from datacrunch import DataCrunchClient -from datacrunch.constants import Locations, InstanceStatus +from datacrunch.constants import InstanceStatus, Locations # Get client secret and id from environment variables DATACRUNCH_CLIENT_ID = os.environ.get('DATACRUNCH_CLIENT_ID') @@ -12,7 +13,7 @@ # Get all SSH keys id's ssh_keys = datacrunch.ssh_keys.get() -ssh_keys_ids = list(map(lambda ssh_key: ssh_key.id, ssh_keys)) +ssh_keys_ids = [ssh_key.id for ssh_key in ssh_keys] # Create a new instance instance = datacrunch.instances.create( diff --git a/examples/ssh_keys.py b/examples/ssh_keys.py index fefaa60..5475ec6 100644 --- a/examples/ssh_keys.py +++ b/examples/ssh_keys.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/examples/startup_scripts.py b/examples/startup_scripts.py index 99f4352..e08c69f 100644 --- a/examples/startup_scripts.py +++ b/examples/startup_scripts.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/examples/storage_volumes.py b/examples/storage_volumes.py index 9efe142..5cec8df 100644 --- a/examples/storage_volumes.py +++ b/examples/storage_volumes.py @@ -1,4 +1,5 @@ import os + from datacrunch import DataCrunchClient # Get client secret and id from environment variables diff --git a/pyproject.toml b/pyproject.toml index e6ebbc4..5acec48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,10 +47,42 @@ module-name = "datacrunch" module-root = "" [tool.ruff] -# TODO(shamrin) fix these errors and stop ignoring them -lint.ignore = ["F401"] line-length = 100 +[tool.ruff.lint] +# https://docs.astral.sh/ruff/rules/ +select = [ + # default rules, via https://docs.astral.sh/ruff/settings/#lint_extend-select + "E4", "E7", "E9", "F", + # comprehensions + "C4", + # sort imports + "I", + # pytest + "PT", + # bugbear + "B", + # upgrade to new python syntax + "UP", + # pydocstyle + "D", + # builtins + "A", + # unused args + "ARG", + # Ruff + "RUF", +] +flake8-builtins.ignorelist = ["id", "type"] +pydocstyle.convention = "google" + +# TODO(shamrin) stop ignoring these errors +ignore = ["F401", "B006", "D100", "D105", "D107"] + +[tool.ruff.lint.per-file-ignores] +"{tests,examples}/*" = ["D"] +"__init__.py" = ["D104"] + [tool.ruff.format] quote-style = "single" docstring-code-format = true diff --git a/tests/integration_tests/conftest.py b/tests/integration_tests/conftest.py index 6a10724..91490fc 100644 --- a/tests/integration_tests/conftest.py +++ b/tests/integration_tests/conftest.py @@ -1,6 +1,8 @@ import os + import pytest from dotenv import load_dotenv + from datacrunch.datacrunch import DataCrunchClient """ diff --git a/tests/integration_tests/test_instances.py b/tests/integration_tests/test_instances.py index 1fe3d66..4540428 100644 --- a/tests/integration_tests/test_instances.py +++ b/tests/integration_tests/test_instances.py @@ -1,7 +1,9 @@ import os + import pytest -from datacrunch.datacrunch import DataCrunchClient + from datacrunch.constants import Locations +from datacrunch.datacrunch import DataCrunchClient IN_GITHUB_ACTIONS = os.getenv('GITHUB_ACTIONS') == 'true' diff --git a/tests/integration_tests/test_locations.py b/tests/integration_tests/test_locations.py index 8098a96..41484a0 100644 --- a/tests/integration_tests/test_locations.py +++ b/tests/integration_tests/test_locations.py @@ -1,7 +1,9 @@ import os + import pytest -from datacrunch.datacrunch import DataCrunchClient + from datacrunch.constants import Locations +from datacrunch.datacrunch import DataCrunchClient IN_GITHUB_ACTIONS = os.getenv('GITHUB_ACTIONS') == 'true' diff --git a/tests/integration_tests/test_volumes.py b/tests/integration_tests/test_volumes.py index b28ec28..10d4ff1 100644 --- a/tests/integration_tests/test_volumes.py +++ b/tests/integration_tests/test_volumes.py @@ -1,8 +1,10 @@ import os import time + import pytest + +from datacrunch.constants import Locations, VolumeStatus, VolumeTypes from datacrunch.datacrunch import DataCrunchClient -from datacrunch.constants import Locations, VolumeTypes, VolumeStatus IN_GITHUB_ACTIONS = os.getenv('GITHUB_ACTIONS') == 'true' diff --git a/tests/unit_tests/authentication/test_authentication.py b/tests/unit_tests/authentication/test_authentication.py index 523a017..589851e 100644 --- a/tests/unit_tests/authentication/test_authentication.py +++ b/tests/unit_tests/authentication/test_authentication.py @@ -1,10 +1,11 @@ +import time + import pytest import responses # https://github.com/getsentry/responses from responses import matchers -import time -from datacrunch.exceptions import APIException from datacrunch.authentication.authentication import AuthenticationService +from datacrunch.exceptions import APIException INVALID_REQUEST = 'invalid_request' INVALID_REQUEST_MESSAGE = 'Your existence is invalid' @@ -210,7 +211,7 @@ def test_refresh_failed(self, authentication_service, endpoint): == f'{{"grant_type": "refresh_token", "refresh_token": "{REFRESH_TOKEN}"}}'.encode() ) - def test_is_expired(self, authentication_service, endpoint): + def test_is_expired(self, authentication_service): # arrange current_time = time.time() future_time = current_time + 3600 diff --git a/tests/unit_tests/balance/test_balance.py b/tests/unit_tests/balance/test_balance.py index 93e508c..b1e15ce 100644 --- a/tests/unit_tests/balance/test_balance.py +++ b/tests/unit_tests/balance/test_balance.py @@ -1,6 +1,6 @@ import responses # https://github.com/getsentry/responses -from datacrunch.balance.balance import BalanceService, Balance +from datacrunch.balance.balance import Balance, BalanceService def test_balance(http_client): diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index aa7bf6b..1df8806 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -1,7 +1,8 @@ -import pytest from unittest.mock import Mock -from datacrunch.http_client.http_client import HTTPClient +import pytest + +from datacrunch.http_client.http_client import HTTPClient BASE_URL = 'https://api-testing.datacrunch.io/v1' ACCESS_TOKEN = 'test-token' diff --git a/tests/unit_tests/containers/test_containers.py b/tests/unit_tests/containers/test_containers.py index 3e5c3cb..965f4c9 100644 --- a/tests/unit_tests/containers/test_containers.py +++ b/tests/unit_tests/containers/test_containers.py @@ -7,31 +7,31 @@ CONTAINER_REGISTRY_CREDENTIALS_ENDPOINT, SECRETS_ENDPOINT, SERVERLESS_COMPUTE_RESOURCES_ENDPOINT, + AWSECRCredentials, + ComputeResource, Container, ContainerDeploymentStatus, ContainerRegistrySettings, ContainersService, + CustomRegistryCredentials, Deployment, + DockerHubCredentials, + EntrypointOverridesSettings, EnvVar, EnvVarType, - EntrypointOverridesSettings, + GCRCredentials, + GithubCredentials, HealthcheckSettings, + QueueLoadScalingTrigger, RegistryCredential, - Secret, - VolumeMount, - VolumeMountType, - ComputeResource, + ReplicaInfo, ScalingOptions, ScalingPolicy, ScalingTriggers, - QueueLoadScalingTrigger, + Secret, UtilizationScalingTrigger, - DockerHubCredentials, - GithubCredentials, - GCRCredentials, - AWSECRCredentials, - CustomRegistryCredentials, - ReplicaInfo, + VolumeMount, + VolumeMountType, ) from datacrunch.exceptions import APIException diff --git a/tests/unit_tests/http_client/test_http_client.py b/tests/unit_tests/http_client/test_http_client.py index 034efba..896dee1 100644 --- a/tests/unit_tests/http_client/test_http_client.py +++ b/tests/unit_tests/http_client/test_http_client.py @@ -1,6 +1,8 @@ +from unittest.mock import Mock + import pytest import responses # https://github.com/getsentry/responses -from unittest.mock import Mock + from datacrunch.exceptions import APIException INVALID_REQUEST = 'invalid_request' diff --git a/tests/unit_tests/images/test_images.py b/tests/unit_tests/images/test_images.py index 9dd455c..d458167 100644 --- a/tests/unit_tests/images/test_images.py +++ b/tests/unit_tests/images/test_images.py @@ -1,6 +1,6 @@ import responses # https://github.com/getsentry/responses -from datacrunch.images.images import ImagesService, Image +from datacrunch.images.images import Image, ImagesService def test_images(http_client): diff --git a/tests/unit_tests/instance_types/test_instance_types.py b/tests/unit_tests/instance_types/test_instance_types.py index 210414e..5426d0b 100644 --- a/tests/unit_tests/instance_types/test_instance_types.py +++ b/tests/unit_tests/instance_types/test_instance_types.py @@ -1,6 +1,6 @@ import responses # https://github.com/getsentry/responses -from datacrunch.instance_types.instance_types import InstanceTypesService, InstanceType +from datacrunch.instance_types.instance_types import InstanceType, InstanceTypesService TYPE_ID = '01cf5dc1-a5d2-4972-ae4e-d429115d055b' CPU_DESCRIPTION = '48 CPU 3.5GHz' diff --git a/tests/unit_tests/instances/test_instances.py b/tests/unit_tests/instances/test_instances.py index 2410250..0a7f856 100644 --- a/tests/unit_tests/instances/test_instances.py +++ b/tests/unit_tests/instances/test_instances.py @@ -1,9 +1,9 @@ import pytest import responses # https://github.com/getsentry/responses -from datacrunch.exceptions import APIException -from datacrunch.instances.instances import InstancesService, Instance from datacrunch.constants import Actions, ErrorCodes, Locations +from datacrunch.exceptions import APIException +from datacrunch.instances.instances import Instance, InstancesService INVALID_REQUEST = ErrorCodes.INVALID_REQUEST INVALID_REQUEST_MESSAGE = 'Your existence is invalid' diff --git a/tests/unit_tests/ssh_keys/test_ssh_keys.py b/tests/unit_tests/ssh_keys/test_ssh_keys.py index f6f5893..8621823 100644 --- a/tests/unit_tests/ssh_keys/test_ssh_keys.py +++ b/tests/unit_tests/ssh_keys/test_ssh_keys.py @@ -2,7 +2,7 @@ import responses # https://github.com/getsentry/responses from datacrunch.exceptions import APIException -from datacrunch.ssh_keys.ssh_keys import SSHKeysService, SSHKey +from datacrunch.ssh_keys.ssh_keys import SSHKey, SSHKeysService INVALID_REQUEST = 'invalid_request' INVALID_REQUEST_MESSAGE = 'Your existence is invalid' diff --git a/tests/unit_tests/startup_scripts/test_startup_scripts.py b/tests/unit_tests/startup_scripts/test_startup_scripts.py index 7b2b675..242f0a8 100644 --- a/tests/unit_tests/startup_scripts/test_startup_scripts.py +++ b/tests/unit_tests/startup_scripts/test_startup_scripts.py @@ -3,8 +3,8 @@ from datacrunch.exceptions import APIException from datacrunch.startup_scripts.startup_scripts import ( - StartupScriptsService, StartupScript, + StartupScriptsService, ) INVALID_REQUEST = 'invalid_request' diff --git a/tests/unit_tests/test_datacrunch.py b/tests/unit_tests/test_datacrunch.py index b2454fd..40a1f16 100644 --- a/tests/unit_tests/test_datacrunch.py +++ b/tests/unit_tests/test_datacrunch.py @@ -1,5 +1,6 @@ import pytest import responses # https://github.com/getsentry/responses + from datacrunch.datacrunch import DataCrunchClient from datacrunch.exceptions import APIException diff --git a/tests/unit_tests/test_exceptions.py b/tests/unit_tests/test_exceptions.py index 4cab370..940234a 100644 --- a/tests/unit_tests/test_exceptions.py +++ b/tests/unit_tests/test_exceptions.py @@ -1,4 +1,5 @@ import pytest + from datacrunch.exceptions import APIException ERROR_CODE = 'test_code' @@ -6,28 +7,22 @@ def test_api_exception_with_code(): - # arrange error_str = f'error code: {ERROR_CODE}\nmessage: {ERROR_MESSAGE}' - # act - with pytest.raises(APIException) as excinfo: + with pytest.raises(APIException) as exc_info: raise APIException(ERROR_CODE, ERROR_MESSAGE) - # assert - assert excinfo.value.code == ERROR_CODE - assert excinfo.value.message == ERROR_MESSAGE - assert excinfo.value.__str__() == error_str + assert exc_info.value.code == ERROR_CODE + assert exc_info.value.message == ERROR_MESSAGE + assert exc_info.value.__str__() == error_str def test_api_exception_without_code(): - # arrange error_str = f'message: {ERROR_MESSAGE}' - # act - with pytest.raises(APIException) as excinfo: + with pytest.raises(APIException) as exc_info: raise APIException(None, ERROR_MESSAGE) - # assert - assert excinfo.value.code is None - assert excinfo.value.message == ERROR_MESSAGE - assert excinfo.value.__str__() == error_str + assert exc_info.value.code is None + assert exc_info.value.message == ERROR_MESSAGE + assert exc_info.value.__str__() == error_str diff --git a/tests/unit_tests/volume_types/test_volume_types.py b/tests/unit_tests/volume_types/test_volume_types.py index 0682aa3..ddde59f 100644 --- a/tests/unit_tests/volume_types/test_volume_types.py +++ b/tests/unit_tests/volume_types/test_volume_types.py @@ -1,8 +1,7 @@ import responses # https://github.com/getsentry/responses -from datacrunch.volume_types.volume_types import VolumeTypesService, VolumeType from datacrunch.constants import VolumeTypes - +from datacrunch.volume_types.volume_types import VolumeType, VolumeTypesService USD = 'usd' NVMe_PRICE = 0.2 diff --git a/tests/unit_tests/volumes/test_volumes.py b/tests/unit_tests/volumes/test_volumes.py index 5577271..43c34a7 100644 --- a/tests/unit_tests/volumes/test_volumes.py +++ b/tests/unit_tests/volumes/test_volumes.py @@ -2,15 +2,15 @@ import responses # https://github.com/getsentry/responses from responses import matchers -from datacrunch.exceptions import APIException -from datacrunch.volumes.volumes import VolumesService, Volume from datacrunch.constants import ( - VolumeStatus, - VolumeTypes, - VolumeActions, ErrorCodes, Locations, + VolumeActions, + VolumeStatus, + VolumeTypes, ) +from datacrunch.exceptions import APIException +from datacrunch.volumes.volumes import Volume, VolumesService INVALID_REQUEST = ErrorCodes.INVALID_REQUEST INVALID_REQUEST_MESSAGE = 'Your existence is invalid'