google_cloud.py 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import os
  2. from typing import List, Optional
  3. from google.cloud import storage
  4. from openhands.storage.files import FileStore
  5. class GoogleCloudFileStore(FileStore):
  6. def __init__(self, bucket_name: Optional[str] = None) -> None:
  7. """
  8. Create a new FileStore. If GOOGLE_APPLICATION_CREDENTIALS is defined in the
  9. environment it will be used for authentication. Otherwise access will be
  10. anonymous.
  11. """
  12. if bucket_name is None:
  13. bucket_name = os.environ['GOOGLE_CLOUD_BUCKET_NAME']
  14. self.storage_client = storage.Client()
  15. self.bucket = self.storage_client.bucket(bucket_name)
  16. def write(self, path: str, contents: str | bytes) -> None:
  17. blob = self.bucket.blob(path)
  18. with blob.open('w') as f:
  19. f.write(contents)
  20. def read(self, path: str) -> str:
  21. blob = self.bucket.blob(path)
  22. with blob.open('r') as f:
  23. return f.read()
  24. def list(self, path: str) -> List[str]:
  25. if not path or path == '/':
  26. path = ''
  27. elif not path.endswith('/'):
  28. path += '/'
  29. # The delimiter logic screens out directories, so we can't use it. :(
  30. # For example, given a structure:
  31. # foo/bar/zap.txt
  32. # foo/bar/bang.txt
  33. # ping.txt
  34. # prefix=None, delimiter="/" yields ["ping.txt"] # :(
  35. # prefix="foo", delimiter="/" yields [] # :(
  36. blobs = set()
  37. prefix_len = len(path)
  38. for blob in self.bucket.list_blobs(prefix=path):
  39. name = blob.name
  40. if name == path:
  41. continue
  42. try:
  43. index = name.index('/', prefix_len + 1)
  44. if index != prefix_len:
  45. blobs.add(name[: index + 1])
  46. except ValueError:
  47. blobs.add(name)
  48. return list(blobs)
  49. def delete(self, path: str) -> None:
  50. blob = self.bucket.blob(path)
  51. blob.delete()