blob: 05e4259ad4d79b2b9df9928359aa94821188142d [file] [log] [blame]
koder aka kdanilov22d134e2016-11-08 11:33:19 +02001"""
2This module contains interfaces for storage classes
3"""
4
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +02005import os
koder aka kdanilov22d134e2016-11-08 11:33:19 +02006import abc
koder aka kdanilov73084622016-11-16 21:51:08 +02007from typing import Any, Iterable, TypeVar, Type, IO, Tuple, cast, List
koder aka kdanilov22d134e2016-11-08 11:33:19 +02008
9
10class IStorable(metaclass=abc.ABCMeta):
11 """Interface for type, which can be stored"""
12 @abc.abstractmethod
13 def __getstate__(self) -> Any:
14 pass
15
16 @abc.abstractmethod
17 def __setstate__(self, Any):
18 pass
19
20
21# all builtin types can be stored
22IStorable.register(list) # type: ignore
23IStorable.register(dict) # type: ignore
24IStorable.register(tuple) # type: ignore
25IStorable.register(set) # type: ignore
26IStorable.register(None) # type: ignore
27IStorable.register(int) # type: ignore
28IStorable.register(str) # type: ignore
29IStorable.register(bytes) # type: ignore
30IStorable.register(bool) # type: ignore
31
32
33ObjClass = TypeVar('ObjClass')
34
35
koder aka kdanilov22d134e2016-11-08 11:33:19 +020036class ISimpleStorage(metaclass=abc.ABCMeta):
37 """interface for low-level storage, which doesn't support serialization
38 and can operate only on bytes"""
39
40 @abc.abstractmethod
koder aka kdanilov22d134e2016-11-08 11:33:19 +020041 def __setitem__(self, path: str, value: bytes) -> None:
42 pass
43
44 @abc.abstractmethod
45 def __getitem__(self, path: str) -> bytes:
46 pass
47
48 @abc.abstractmethod
koder aka kdanilov73084622016-11-16 21:51:08 +020049 def __delitem__(self, path: str) -> None:
50 pass
51
52 @abc.abstractmethod
koder aka kdanilov22d134e2016-11-08 11:33:19 +020053 def __contains__(self, path: str) -> bool:
54 pass
55
56 @abc.abstractmethod
koder aka kdanilov73084622016-11-16 21:51:08 +020057 def list(self, path: str) -> Iterable[Tuple[bool, str]]:
koder aka kdanilov22d134e2016-11-08 11:33:19 +020058 pass
59
60 @abc.abstractmethod
61 def get_stream(self, path: str) -> IO:
62 pass
63
64
65class ISerializer(metaclass=abc.ABCMeta):
66 """Interface for serialization class"""
67 @abc.abstractmethod
68 def pack(self, value: IStorable) -> bytes:
69 pass
70
71 @abc.abstractmethod
72 def unpack(self, data: bytes) -> IStorable:
73 pass
74
75
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020076class FSStorage(ISimpleStorage):
koder aka kdanilov22d134e2016-11-08 11:33:19 +020077 """Store all data in files on FS"""
78
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020079 def __init__(self, root_path: str, existing: bool) -> None:
80 self.root_path = root_path
81 if existing:
82 if not os.path.isdir(self.root_path):
83 raise ValueError("No storage found at {!r}".format(root_path))
84
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020085 def __setitem__(self, path: str, value: bytes) -> None:
86 path = os.path.join(self.root_path, path)
koder aka kdanilov73084622016-11-16 21:51:08 +020087 os.makedirs(os.path.dirname(path), exist_ok=True)
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020088 with open(path, "wb") as fd:
89 fd.write(value)
90
koder aka kdanilov73084622016-11-16 21:51:08 +020091 def __delitem__(self, path: str) -> None:
92 try:
93 os.unlink(path)
94 except FileNotFoundError:
95 pass
96
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020097 def __getitem__(self, path: str) -> bytes:
98 path = os.path.join(self.root_path, path)
99 with open(path, "rb") as fd:
100 return fd.read()
101
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200102 def __contains__(self, path: str) -> bool:
103 path = os.path.join(self.root_path, path)
104 return os.path.exists(path)
105
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200106 def list(self, path: str) -> Iterable[Tuple[bool, str]]:
107 path = os.path.join(self.root_path, path)
108 for entry in os.scandir(path):
109 if not entry.name in ('..', '.'):
110 yield entry.is_file(), entry.name
111
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200112 def get_stream(self, path: str, mode: str = "rb") -> IO:
113 path = os.path.join(self.root_path, path)
114 return open(path, mode)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200115
116
117class YAMLSerializer(ISerializer):
118 """Serialize data to yaml"""
koder aka kdanilov73084622016-11-16 21:51:08 +0200119 def pack(self, value: IStorable) -> bytes:
120 raise NotImplementedError()
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200121
koder aka kdanilov73084622016-11-16 21:51:08 +0200122 def unpack(self, data: bytes) -> IStorable:
123 raise NotImplementedError()
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200124
125
126class Storage:
127 """interface for storage"""
koder aka kdanilov73084622016-11-16 21:51:08 +0200128 def __init__(self, storage: ISimpleStorage, serializer: ISerializer) -> None:
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200129 self.storage = storage
130 self.serializer = serializer
131
132 def __setitem__(self, path: str, value: IStorable) -> None:
133 self.storage[path] = self.serializer.pack(value)
134
koder aka kdanilov73084622016-11-16 21:51:08 +0200135 def __getitem__(self, path: str) -> IStorable:
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200136 return self.serializer.unpack(self.storage[path])
137
koder aka kdanilov73084622016-11-16 21:51:08 +0200138 def __delitem__(self, path: str) -> None:
139 del self.storage[path]
140
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200141 def __contains__(self, path: str) -> bool:
142 return path in self.storage
143
koder aka kdanilov70227062016-11-26 23:23:21 +0200144 def list(self, *path: str) -> Iterable[Tuple[bool, str]]:
145 return self.storage.list("/".join(path))
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200146
koder aka kdanilov73084622016-11-16 21:51:08 +0200147 def construct(self, path: str, raw_val: IStorable, obj_class: Type[ObjClass]) -> ObjClass:
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200148 if obj_class in (int, str, dict, list, None):
149 if not isinstance(raw_val, obj_class):
150 raise ValueError("Can't load path {!r} into type {}. Real type is {}"
151 .format(path, obj_class, type(raw_val)))
koder aka kdanilov73084622016-11-16 21:51:08 +0200152 return cast(ObjClass, raw_val)
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200153
154 if not isinstance(raw_val, dict):
155 raise ValueError("Can't load path {!r} into python type. Raw value not dict".format(path))
156
157 if not all(isinstance(str, key) for key in raw_val.keys):
158 raise ValueError("Can't load path {!r} into python type.".format(path) +
159 "Raw not all keys in raw value is strings")
160
koder aka kdanilov73084622016-11-16 21:51:08 +0200161 obj = obj_class.__new__(obj_class) # type: ObjClass
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200162 obj.__dict__.update(raw_val)
163 return obj
164
koder aka kdanilov70227062016-11-26 23:23:21 +0200165 def load_list(self, obj_class: Type[ObjClass], *path: str) -> List[ObjClass]:
166 path_s = "/".join(path)
167 raw_val = self[path_s]
koder aka kdanilov73084622016-11-16 21:51:08 +0200168 assert isinstance(raw_val, list)
koder aka kdanilov70227062016-11-26 23:23:21 +0200169 return [self.construct(path_s, val, obj_class) for val in cast(list, raw_val)]
koder aka kdanilov73084622016-11-16 21:51:08 +0200170
koder aka kdanilov70227062016-11-26 23:23:21 +0200171 def load(self, obj_class: Type[ObjClass], *path: str) -> ObjClass:
172 path_s = "/".join(path)
173 return self.construct(path_s, self[path_s], obj_class)
koder aka kdanilov73084622016-11-16 21:51:08 +0200174
koder aka kdanilov70227062016-11-26 23:23:21 +0200175 def get_stream(self, *path: str) -> IO:
176 return self.storage.get_stream("/".join(path))
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200177
koder aka kdanilov73084622016-11-16 21:51:08 +0200178 def get(self, path: str, default: Any = None) -> Any:
179 try:
180 return self[path]
181 except KeyError:
182 return default
183
koder aka kdanilov70227062016-11-26 23:23:21 +0200184 def append(self, path: str, data: List):
185 raise NotImplemented()
186
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200187
188def make_storage(url: str, existing: bool = False) -> Storage:
189 return Storage(FSStorage(url, existing), YAMLSerializer())
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200190