blob: 02de173bb419a89d015aa79a4c3096d3b29b889f [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 kdanilov3d2bc4f2016-11-12 18:31:18 +02007from typing import Any, Iterable, TypeVar, Type, IO, Tuple, Union, Dict, 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
49 def __contains__(self, path: str) -> bool:
50 pass
51
52 @abc.abstractmethod
53 def list(self, path: str) -> Iterable[str]:
54 pass
55
56 @abc.abstractmethod
57 def get_stream(self, path: str) -> IO:
58 pass
59
60
61class ISerializer(metaclass=abc.ABCMeta):
62 """Interface for serialization class"""
63 @abc.abstractmethod
64 def pack(self, value: IStorable) -> bytes:
65 pass
66
67 @abc.abstractmethod
68 def unpack(self, data: bytes) -> IStorable:
69 pass
70
71
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020072class FSStorage(ISimpleStorage):
koder aka kdanilov22d134e2016-11-08 11:33:19 +020073 """Store all data in files on FS"""
74
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +020075 def __init__(self, root_path: str, existing: bool) -> None:
76 self.root_path = root_path
77 if existing:
78 if not os.path.isdir(self.root_path):
79 raise ValueError("No storage found at {!r}".format(root_path))
80
81 def ensure_dir(self, path):
82 os.makedirs(path, exist_ok=True)
83
koder aka kdanilov22d134e2016-11-08 11:33:19 +020084 @abc.abstractmethod
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)
87 self.ensure_dir(os.path.dirname(path))
88 with open(path, "wb") as fd:
89 fd.write(value)
90
91 @abc.abstractmethod
92 def __getitem__(self, path: str) -> bytes:
93 path = os.path.join(self.root_path, path)
94 with open(path, "rb") as fd:
95 return fd.read()
96
97 @abc.abstractmethod
98 def __contains__(self, path: str) -> bool:
99 path = os.path.join(self.root_path, path)
100 return os.path.exists(path)
101
102 @abc.abstractmethod
103 def list(self, path: str) -> Iterable[Tuple[bool, str]]:
104 path = os.path.join(self.root_path, path)
105 for entry in os.scandir(path):
106 if not entry.name in ('..', '.'):
107 yield entry.is_file(), entry.name
108
109 @abc.abstractmethod
110 def get_stream(self, path: str, mode: str = "rb") -> IO:
111 path = os.path.join(self.root_path, path)
112 return open(path, mode)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200113
114
115class YAMLSerializer(ISerializer):
116 """Serialize data to yaml"""
117 pass
118
119
koder aka kdanilov3d2bc4f2016-11-12 18:31:18 +0200120ISimpleStorable = Union[Dict, List, int, str, None, bool]
121
122
123class Storage:
124 """interface for storage"""
125 def __init__(self, storage: ISimpleStorage, serializer: ISerializer):
126 self.storage = storage
127 self.serializer = serializer
128
129 def __setitem__(self, path: str, value: IStorable) -> None:
130 self.storage[path] = self.serializer.pack(value)
131
132 @abc.abstractmethod
133 def __getitem__(self, path: str) -> ISimpleStorable:
134 return self.serializer.unpack(self.storage[path])
135
136 @abc.abstractmethod
137 def __contains__(self, path: str) -> bool:
138 return path in self.storage
139
140 @abc.abstractmethod
141 def list(self, path: str) -> Iterable[Tuple[bool, str]]:
142 return self.storage.list(path)
143
144 @abc.abstractmethod
145 def load(self, path: str, obj_class: Type[ObjClass]) -> ObjClass:
146 raw_val = self[path]
147 if obj_class in (int, str, dict, list, None):
148 if not isinstance(raw_val, obj_class):
149 raise ValueError("Can't load path {!r} into type {}. Real type is {}"
150 .format(path, obj_class, type(raw_val)))
151 return raw_val
152
153 if not isinstance(raw_val, dict):
154 raise ValueError("Can't load path {!r} into python type. Raw value not dict".format(path))
155
156 if not all(isinstance(str, key) for key in raw_val.keys):
157 raise ValueError("Can't load path {!r} into python type.".format(path) +
158 "Raw not all keys in raw value is strings")
159
160 obj = ObjClass.__new__(ObjClass)
161 obj.__dict__.update(raw_val)
162 return obj
163
164 @abc.abstractmethod
165 def get_stream(self, path: str) -> IO:
166 return self.storage.get_stream(path)
167
168
169def make_storage(url: str, existing: bool = False) -> Storage:
170 return Storage(FSStorage(url, existing), YAMLSerializer())
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200171