Skip to content

YEP-2927 Create YepCode Storage in run sdk (py) #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,36 @@ api = YepCodeApi(
processes = api.get_processes()
```

### 6. YepCode Storage

You can manage files in your YepCode workspace using the `YepCodeStorage` class. This allows you to upload, list, download, and delete files easily.

```python
from yepcode_run import YepCodeStorage, YepCodeApiConfig

storage = YepCodeStorage(
YepCodeApiConfig(api_token='your-api-token')
)

# Upload a file
with open('myfile.txt', 'rb') as f:
obj = storage.upload('myfile.txt', f)
print('Uploaded:', obj.name, obj.size, obj.link)

# List all storage objects
objects = storage.list()
for obj in objects:
print(obj.name, obj.size, obj.link)

# Download a file
content = storage.download('myfile.txt')
with open('downloaded.txt', 'wb') as f:
f.write(content)

# Delete a file
storage.delete('myfile.txt')
```

## SDK API Reference

### YepCodeRun
Expand Down Expand Up @@ -237,6 +267,59 @@ class Process:
created_at: str
```

### YepCodeStorage

The main class for managing files in YepCode's cloud storage.

#### Methods

##### `upload(name: str, file: bytes) -> StorageObject`
Uploads a file to YepCode storage.

**Parameters:**
- `name`: Name of the file in storage
- `file`: File content as bytes or a file-like object

**Returns:** StorageObject

##### `download(name: str) -> bytes`
Downloads a file from YepCode storage.

**Parameters:**
- `name`: Name of the file to download

**Returns:** File content as bytes

##### `delete(name: str) -> None`
Deletes a file from YepCode storage.

**Parameters:**
- `name`: Name of the file to delete

**Returns:** None

##### `list() -> List[StorageObject]`
Lists all files in YepCode storage.

**Returns:** List of StorageObject

#### Types

```python
class StorageObject:
name: str # File name
size: int # File size in bytes
md5_hash: str # MD5 hash of the file
content_type: str # MIME type
created_at: str # Creation timestamp (ISO8601)
updated_at: str # Last update timestamp (ISO8601)
link: str # Download link

class CreateStorageObjectInput:
name: str # File name
file: Any # File content (bytes or file-like)
```

## License

All rights reserved by YepCode. This package is part of the YepCode Platform and is subject to the [YepCode Terms of Service](https://yepcode.io/terms-of-use).
30 changes: 30 additions & 0 deletions yepcode_run/api/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,33 @@ class VersionedModuleAliasInput:
@dataclass
class VersionedModuleAliasesPaginatedResult(PaginatedResult):
data: Optional[List[VersionedModuleAlias]] = None


# Storage types
@dataclass
class StorageObject:
name: str
size: int
md5_hash: str
content_type: str
created_at: str
updated_at: str
link: str

@staticmethod
def from_dict(data: dict) -> "StorageObject":
return StorageObject(
name=data["name"],
size=data["size"],
md5_hash=data.get("md5Hash", data.get("md5_hash")),
content_type=data.get("contentType", data.get("content_type")),
created_at=data.get("createdAt", data.get("created_at")),
updated_at=data.get("updatedAt", data.get("updated_at")),
link=str(data.get("link")),
)


@dataclass
class CreateStorageObjectInput:
name: str
file: Any
66 changes: 66 additions & 0 deletions yepcode_run/api/yepcode_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime
import requests
from urllib.parse import urljoin
import mimetypes

from .types import (
YepCodeApiConfig,
Expand Down Expand Up @@ -38,6 +39,8 @@
VersionedProcessAliasInput,
VersionedModuleAliasInput,
ScheduledProcessInput,
CreateStorageObjectInput,
StorageObject,
)


Expand Down Expand Up @@ -443,3 +446,66 @@ def create_module_version_alias(
self, module_id: str, data: VersionedModuleAliasInput
) -> VersionedModuleAlias:
return self._request("POST", f"/modules/{module_id}/aliases", {"data": data})

def get_objects(self) -> List[StorageObject]:
response = self._request("GET", "/storage/objects")
return [StorageObject.from_dict(obj) for obj in response]

def get_object(self, name: str) -> requests.Response:
if not self.access_token:
self._get_access_token()
headers = {
"Authorization": f"Bearer {self.access_token}",
}
endpoint = f"/storage/objects/{name}"
url = urljoin(f"{self._get_base_url()}/", endpoint.lstrip("/"))
response = requests.get(url, headers=headers, stream=True, timeout=self.timeout / 1000)
response.raise_for_status()
return response

def create_object(self, data: CreateStorageObjectInput) -> StorageObject:
if not data.file:
raise ValueError("File or stream is required")
if not self.access_token:
self._get_access_token()
headers = {
"Authorization": f"Bearer {self.access_token}",
}
endpoint = f"/storage/objects?name={requests.utils.quote(data.name)}"
url = urljoin(f"{self._get_base_url()}/", endpoint.lstrip("/"))
# Detect content type
content_type, _ = mimetypes.guess_type(data.name)
files = {"file": (data.name, data.file, content_type or "application/octet-stream")}
response = requests.post(url, headers=headers, files=files, timeout=self.timeout / 1000)
if not response.ok:
try:
error_response = response.json()
message = error_response.get("message", response.reason)
except ValueError:
message = response.reason
raise YepCodeApiError(
f"HTTP error {response.status_code} in endpoint POST {endpoint}: {message}",
response.status_code,
)
return StorageObject.from_dict(response.json())

def delete_object(self, name: str) -> None:
if not self.access_token:
self._get_access_token()
headers = {
"Authorization": f"Bearer {self.access_token}",
}
endpoint = f"/storage/objects/{requests.utils.quote(name)}"
url = urljoin(f"{self._get_base_url()}/", endpoint.lstrip("/"))
response = requests.delete(url, headers=headers, timeout=self.timeout / 1000)
if not response.ok:
try:
error_response = response.json()
message = error_response.get("message", response.reason)
except ValueError:
message = response.reason
raise YepCodeApiError(
f"HTTP error {response.status_code} in endpoint DELETE {endpoint}: {message}",
response.status_code,
)
return None
29 changes: 29 additions & 0 deletions yepcode_run/storage/yepcode_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List

from ..api.api_manager import YepCodeApiManager
from ..api.types import CreateStorageObjectInput, StorageObject, YepCodeApiConfig


class YepCodeStorage:
def __init__(self, config: YepCodeApiConfig = None):
"""
Initialize YepCodeStorage with optional configuration.

Args:
config: YepCodeApiConfig instance for API configuration
"""
self._api = YepCodeApiManager.get_instance(config)

def download(self, name: str) -> bytes:
return self._api.get_object(name).content

def upload(self, name: str, file: bytes) -> StorageObject:
return self._api.create_object(
CreateStorageObjectInput(name=name, file=file)
)

def delete(self, name: str) -> None:
return self._api.delete_object(name)

def list(self) -> List[StorageObject]:
return self._api.get_objects()