|
5 | 5 | in memory, which is useful for testing or temporary storage. |
6 | 6 | """ |
7 | 7 |
|
| 8 | +import json |
8 | 9 | import logging |
| 10 | +from typing import Union |
9 | 11 |
|
10 | | -from moatless.storage.base import BaseStorage |
| 12 | +from moatless.storage.base import BaseStorage, DateTimeEncoder |
11 | 13 |
|
12 | 14 | logger = logging.getLogger(__name__) |
13 | 15 |
|
14 | 16 |
|
15 | 17 | class MemoryStorage(BaseStorage): |
16 | | - """ |
17 | | - Storage implementation that uses in-memory dictionaries. |
| 18 | + """Simple in-memory implementation of :class:`BaseStorage`.""" |
18 | 19 |
|
19 | | - This class provides a storage implementation that keeps all data |
20 | | - in memory, which is useful for testing or temporary storage. |
21 | | - """ |
22 | | - |
23 | | - def __init__(self): |
| 20 | + def __init__(self) -> None: |
24 | 21 | """Initialize an empty in-memory storage.""" |
25 | | - self._data: dict[str, dict] = {} |
| 22 | + self._data: dict[str, object] = {} |
| 23 | + |
| 24 | + async def read_raw(self, path: str) -> str: |
| 25 | + """Return the raw value stored under *path*.""" |
| 26 | + normalized_key = self.normalize_path(path) |
| 27 | + if normalized_key not in self._data: |
| 28 | + raise KeyError(f"Key '{path}' does not exist") |
26 | 29 |
|
27 | | - async def read(self, path: str) -> dict: |
28 | | - """Read binary data from memory.""" |
| 30 | + value = self._data[normalized_key] |
| 31 | + if isinstance(value, list): |
| 32 | + # Represent lists as JSONL |
| 33 | + return "\n".join( |
| 34 | + json.dumps(v, cls=DateTimeEncoder) if isinstance(v, dict) else str(v) |
| 35 | + for v in value |
| 36 | + ) |
| 37 | + if isinstance(value, dict): |
| 38 | + return json.dumps(value, cls=DateTimeEncoder) |
| 39 | + return str(value) |
| 40 | + |
| 41 | + async def read_lines(self, path: str) -> list[dict]: |
| 42 | + """Return a list of objects stored under *path*.""" |
29 | 43 | normalized_key = self.normalize_path(path) |
30 | 44 | if normalized_key not in self._data: |
31 | 45 | raise KeyError(f"Key '{path}' does not exist") |
32 | | - return self._data[normalized_key] |
33 | 46 |
|
34 | | - async def write(self, key: str, data: dict) -> None: |
35 | | - """Write binary data to memory.""" |
36 | | - normalized_key = self.normalize_path(key) |
| 47 | + value = self._data[normalized_key] |
| 48 | + results: list[dict] = [] |
| 49 | + if isinstance(value, list): |
| 50 | + for item in value: |
| 51 | + if isinstance(item, str): |
| 52 | + item = item.strip() |
| 53 | + if item: |
| 54 | + results.append(json.loads(item)) |
| 55 | + else: |
| 56 | + results.append(item) |
| 57 | + elif isinstance(value, str): |
| 58 | + for line in value.splitlines(): |
| 59 | + line = line.strip() |
| 60 | + if line: |
| 61 | + results.append(json.loads(line)) |
| 62 | + elif isinstance(value, dict): |
| 63 | + results.append(value) |
| 64 | + return results |
| 65 | + |
| 66 | + async def write_raw(self, path: str, data: str) -> None: |
| 67 | + """Write raw string *data* to *path*.""" |
| 68 | + normalized_key = self.normalize_path(path) |
37 | 69 | self._data[normalized_key] = data |
38 | 70 |
|
39 | | - async def delete(self, key: str) -> None: |
40 | | - """Delete data from memory.""" |
41 | | - normalized_key = self.normalize_path(key) |
| 71 | + async def append(self, path: str, data: Union[dict, str]) -> None: |
| 72 | + """Append *data* to the entry at *path*.""" |
| 73 | + normalized_key = self.normalize_path(path) |
| 74 | + existing = self._data.get(normalized_key) |
| 75 | + |
| 76 | + if existing is None: |
| 77 | + self._data[normalized_key] = [] |
| 78 | + existing = self._data[normalized_key] |
| 79 | + |
| 80 | + if not isinstance(existing, list): |
| 81 | + if isinstance(existing, str): |
| 82 | + lines = existing.splitlines() |
| 83 | + self._data[normalized_key] = lines |
| 84 | + else: |
| 85 | + self._data[normalized_key] = [existing] |
| 86 | + existing = self._data[normalized_key] |
| 87 | + |
| 88 | + assert isinstance(existing, list) |
| 89 | + if isinstance(data, dict): |
| 90 | + existing.append(data) |
| 91 | + else: |
| 92 | + existing.append(data.rstrip("\n")) |
| 93 | + |
| 94 | + async def delete(self, path: str) -> None: |
| 95 | + """Delete the value at *path*.""" |
| 96 | + normalized_key = self.normalize_path(path) |
42 | 97 | if normalized_key not in self._data: |
43 | | - raise KeyError(f"Key '{key}' does not exist") |
| 98 | + raise KeyError(f"Key '{path}' does not exist") |
44 | 99 | del self._data[normalized_key] |
45 | 100 |
|
46 | | - async def exists(self, key: str) -> bool: |
47 | | - """Check if a key exists in memory.""" |
48 | | - normalized_key = self.normalize_path(key) |
| 101 | + async def exists(self, path: str) -> bool: |
| 102 | + """Return ``True`` if *path* exists.""" |
| 103 | + normalized_key = self.normalize_path(path) |
49 | 104 | return normalized_key in self._data |
50 | 105 |
|
51 | 106 | async def list_paths(self, prefix: str = "") -> list[str]: |
52 | | - """ |
53 | | - List all keys with the given prefix. |
54 | | -
|
55 | | - Args: |
56 | | - prefix: The key prefix to search for |
57 | | -
|
58 | | - Returns: |
59 | | - A list of keys that match the prefix |
60 | | - """ |
| 107 | + """List all keys starting with *prefix*.""" |
61 | 108 | normalized_prefix = self.normalize_path(prefix) |
62 | 109 |
|
63 | | - # When prefix is empty, return all keys |
64 | 110 | if not normalized_prefix: |
65 | 111 | return list(self._data.keys()) |
66 | 112 |
|
67 | | - # Filter keys that start with the prefix |
68 | | - return [key for key in self._data.keys() if key == normalized_prefix or key.startswith(normalized_prefix + "/")] |
| 113 | + return [ |
| 114 | + key |
| 115 | + for key in self._data.keys() |
| 116 | + if key == normalized_prefix or key.startswith(normalized_prefix + "/") |
| 117 | + ] |
0 commit comments