This repository has been archived by the owner on Sep 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
509 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
|
||
from agbenchmark.app import get_artifact, get_skill_tree | ||
from fastapi import APIRouter | ||
from fastapi import ( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import abc | ||
import os | ||
import typing | ||
from pathlib import Path | ||
|
||
from google.cloud import storage | ||
|
||
|
||
class Workspace(abc.ABC): | ||
@abc.abstractclassmethod | ||
def __init__(self, base_path: str) -> None: | ||
self.base_path = base_path | ||
|
||
@abc.abstractclassmethod | ||
def read(self, path: str) -> bytes: | ||
pass | ||
|
||
@abc.abstractclassmethod | ||
def write(self, path: str, data: bytes) -> None: | ||
pass | ||
|
||
@abc.abstractclassmethod | ||
def delete( | ||
self, path: str, directory: bool = False, recursive: bool = False | ||
) -> None: | ||
pass | ||
|
||
@abc.abstractclassmethod | ||
def exists(self, path: str) -> bool: | ||
pass | ||
|
||
@abc.abstractclassmethod | ||
def list(self, path: str) -> typing.List[str]: | ||
pass | ||
|
||
|
||
class LocalWorkspace(Workspace): | ||
def __init__(self, base_path: str): | ||
self.base_path = Path(base_path).resolve() | ||
|
||
def _resolve_path(self, path: str) -> Path: | ||
abs_path = (self.base_path / path).resolve() | ||
if not str(abs_path).startswith(str(self.base_path)): | ||
raise ValueError("Directory traversal is not allowed!") | ||
return abs_path | ||
|
||
def read(self, path: str) -> bytes: | ||
path = self.base_path / path | ||
with open(self._resolve_path(path), "rb") as f: | ||
return f.read() | ||
|
||
def write(self, path: str, data: bytes) -> None: | ||
path = self.base_path / path | ||
with open(self._resolve_path(path), "wb") as f: | ||
f.write(data) | ||
|
||
def delete( | ||
self, path: str, directory: bool = False, recursive: bool = False | ||
) -> None: | ||
path = self.base_path / path | ||
resolved_path = self._resolve_path(path) | ||
if directory: | ||
if recursive: | ||
os.rmdir(resolved_path) | ||
else: | ||
os.removedirs(resolved_path) | ||
else: | ||
os.remove(resolved_path) | ||
|
||
def exists(self, path: str) -> bool: | ||
path = self.base_path / path | ||
return self._resolve_path(path).exists() | ||
|
||
def list(self, path: str) -> typing.List[str]: | ||
path = self.base_path / path | ||
base = self._resolve_path(path) | ||
return [str(p.relative_to(self.base_path)) for p in base.iterdir()] | ||
|
||
|
||
class GCSWorkspace(Workspace): | ||
def __init__(self, base_path: str, bucket_name: str): | ||
self.client = storage.Client() | ||
self.bucket_name = bucket_name | ||
self.base_path = base_path.strip("/") # Ensure no trailing or leading slash | ||
|
||
def _resolve_path(self, path: str) -> str: | ||
resolved = os.path.join(self.base_path, path).strip("/") | ||
if not resolved.startswith(self.base_path): | ||
raise ValueError("Directory traversal is not allowed!") | ||
return resolved | ||
|
||
def read(self, path: str) -> bytes: | ||
path = self.base_path / path | ||
bucket = self.client.get_bucket(self.bucket_name) | ||
blob = bucket.get_blob(self._resolve_path(path)) | ||
return blob.download_as_bytes() | ||
|
||
def write(self, path: str, data: bytes) -> None: | ||
path = self.base_path / path | ||
bucket = self.client.get_bucket(self.bucket_name) | ||
blob = bucket.blob(self._resolve_path(path)) | ||
blob.upload_from_string(data) | ||
|
||
def delete( | ||
self, path: str, directory: bool = False, recursive: bool = False | ||
) -> None: | ||
path = self.base_path / path | ||
bucket = self.client.get_bucket(self.bucket_name) | ||
if directory and recursive: | ||
# Note: GCS doesn't really have directories, so this will just delete all blobs with the given prefix | ||
blobs = bucket.list_blobs(prefix=self._resolve_path(path)) | ||
bucket.delete_blobs(blobs) | ||
else: | ||
blob = bucket.blob(self._resolve_path(path)) | ||
blob.delete() | ||
|
||
def exists(self, path: str) -> bool: | ||
path = self.base_path / path | ||
bucket = self.client.get_bucket(self.bucket_name) | ||
blob = bucket.blob(self._resolve_path(path)) | ||
return blob.exists() | ||
|
||
def list(self, path: str) -> typing.List[str]: | ||
path = self.base_path / path | ||
bucket = self.client.get_bucket(self.bucket_name) | ||
blobs = bucket.list_blobs(prefix=self._resolve_path(path)) | ||
return [blob.name for blob in blobs] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import os | ||
|
||
import pytest | ||
|
||
# Assuming the classes are defined in a file named workspace.py | ||
from .workspace import LocalWorkspace | ||
|
||
# Constants | ||
TEST_BASE_PATH = "/tmp/test_workspace" | ||
TEST_FILE_CONTENT = b"Hello World" | ||
|
||
|
||
# Setup and Teardown for LocalWorkspace | ||
|
||
|
||
@pytest.fixture | ||
def setup_local_workspace(): | ||
os.makedirs(TEST_BASE_PATH, exist_ok=True) | ||
yield | ||
os.system(f"rm -rf {TEST_BASE_PATH}") # Cleanup after tests | ||
|
||
|
||
def test_local_read_write_delete_exists(setup_local_workspace): | ||
workspace = LocalWorkspace(TEST_BASE_PATH) | ||
|
||
# Write | ||
workspace.write("test_file.txt", TEST_FILE_CONTENT) | ||
|
||
# Exists | ||
assert workspace.exists("test_file.txt") | ||
|
||
# Read | ||
assert workspace.read("test_file.txt") == TEST_FILE_CONTENT | ||
|
||
# Delete | ||
workspace.delete("test_file.txt") | ||
assert not workspace.exists("test_file.txt") | ||
|
||
|
||
def test_local_list(setup_local_workspace): | ||
workspace = LocalWorkspace(TEST_BASE_PATH) | ||
workspace.write("test1.txt", TEST_FILE_CONTENT) | ||
workspace.write("test2.txt", TEST_FILE_CONTENT) | ||
|
||
files = workspace.list(".") | ||
assert set(files) == set(["test1.txt", "test2.txt"]) |
Oops, something went wrong.