borgstore 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ChangeLog
2
+ =========
3
+
4
+ Version 0.0.1 2024-08-23
5
+ ------------------------
6
+
7
+ First PyPi release.
@@ -0,0 +1,28 @@
1
+ Copyright (C) 2024 Thomas Waldmann <tw@waldmann-edv.de>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions
6
+ are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in
12
+ the documentation and/or other materials provided with the
13
+ distribution.
14
+ 3. The name of the author may not be used to endorse or promote
15
+ products derived from this software without specific prior
16
+ written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.1
2
+ Name: borgstore
3
+ Version: 0.0.1
4
+ Summary: key/value store
5
+ Author-email: Thomas Waldmann <tw@waldmann-edv.de>
6
+ License: BSD
7
+ Project-URL: Homepage, https://github.com/borgbackup/borgstore
8
+ Keywords: kv,key/value,store
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/x-rst
23
+ License-File: LICENSE.rst
24
+ Requires-Dist: cryptography<43.0.0
25
+ Requires-Dist: paramiko
26
+
27
+ BorgStore
28
+ =========
29
+
30
+ A key/value store implementation in Python, supporting multiple backends,
31
+ data redundancy and distribution.
32
+
33
+ Keys
34
+ ----
35
+
36
+ A key (str) can look like:
37
+
38
+ - 0123456789abcdef... (usually a long, hex-encoded hash value)
39
+ - Any other pure ASCII string without "/" or ".." or " ".
40
+
41
+
42
+ Namespaces
43
+ ----------
44
+
45
+ To keep stuff apart, keys should get prefixed with a namespace, like:
46
+
47
+ - config/settings
48
+ - meta/0123456789abcdef...
49
+ - data/0123456789abcdef...
50
+
51
+ Please note:
52
+
53
+ 1. you should always use namespaces.
54
+ 2. nested namespaces like namespace1/namespace2/key are not supported.
55
+ 3. the code could work without a namespace (namespace ""), but then you
56
+ can't add another namespace later, because then you would have created
57
+ nested namespaces.
58
+
59
+ Values
60
+ ------
61
+
62
+ Values can be any arbitrary binary data (bytes).
63
+
64
+ Store Operations
65
+ ----------------
66
+
67
+ The high-level Store API implementation transparently deals with nesting and
68
+ soft deletion, so the caller doesn't have to care much for that and the Backend
69
+ API can be much simpler:
70
+
71
+ - create/destroy: initialize or remove the whole store.
72
+ - list: flat list of the items in the given namespace, with or without soft
73
+ deleted items.
74
+ - store: write a new item into the store (giving its key/value pair)
75
+ - load: read a value from the store (giving its key), partial loads giving
76
+ offset and/or size are supported.
77
+ - info: get information about an item via its key (exists? size? ...)
78
+ - delete: immediately remove an item from the store (giving its key)
79
+ - move: implements rename, soft delete / undelete, move to current
80
+ nesting level
81
+
82
+ Automatic Nesting
83
+ -----------------
84
+
85
+ For the Store user, items have names like e.g.:
86
+
87
+ namespace/0123456789abcdef...
88
+ namespace/abcdef0123456789...
89
+
90
+ If there are very many items in the namespace, this could lead to scalability
91
+ issues in the backend, thus the Store implementation offers transparent
92
+ nesting, so that internally the Backend API will be called with
93
+ names like e.g.:
94
+
95
+ namespace/01/23/56/0123456789abcdef...
96
+ namespace/ab/cd/ef/abcdef0123456789...
97
+
98
+ The nesting depth can be configured from 0 (= no nesting) to N levels and
99
+ there can be different nesting configurations depending on the namespace.
100
+
101
+ The Store supports operating at different nesting levels in the same
102
+ namespace at the same time.
103
+
104
+ Soft deletion
105
+ -------------
106
+
107
+ To soft delete an item (so its value could be still read or it could be
108
+ undeleted), the store just renames the item, appending ".del" to its name.
109
+
110
+ Undelete reverses this by removing the ".del" suffix from the name.
111
+
112
+ Some store operations have a boolean flag "deleted" to choose whether they
113
+ shall consider soft deleted items.
114
+
115
+ Backends
116
+ --------
117
+
118
+ The backend API is rather simple, one only needs to provide some very
119
+ basic operations.
120
+
121
+ Currently, these storage backends are implemented:
122
+
123
+ - POSIX filesystems (namespaces: directories, values: in key-named files)
124
+ - SFTP (access a server via sftp, namespaces: directories, values: in key-named files)
125
+ - (more might come in future)
126
+
127
+ MStore
128
+ ------
129
+
130
+ API of MStore is very similar to Store, but instead of directly using one backend
131
+ only (like Store does), it uses multiple Stores internally to implement:
132
+
133
+ - redundancy (keep same data at multiple places)
134
+ - distribution (keep different data at multiple places)
135
+
136
+ Scalability
137
+ -----------
138
+
139
+ - Count of key/value pairs stored in a namespace: automatic nesting is
140
+ provided for keys to address common scalability issues.
141
+ - Key size: there are no special provisions for extremely long keys (like:
142
+ more than backend limitations). Usually this is not a problem though.
143
+ - Value size: there are no special provisions for dealing with large value
144
+ sizes (like: more than free memory, more than backend storage limitations,
145
+ etc.). If one deals with very large values, one usually cuts them into
146
+ chunks before storing them into the store.
147
+ - Partial loads improve performance by avoiding a full load if only a part
148
+ of the value is needed (e.g. a header with metadata).
149
+
150
+ Want a demo?
151
+ ------------
152
+
153
+ Run this to get instructions how to run the demo:
154
+
155
+ python3 -m borgstore
156
+
157
+ State of this project
158
+ ---------------------
159
+
160
+ **API is still unstable and expected to change as development goes on.**
161
+
162
+ **There will be no data migration tools involving development/testing releases,
163
+ like e.g. upgrading a store from alpha1 to alpha2 or beta13 to release.**
164
+
165
+ There are tests and they succeed for the basic functionality, so some of the
166
+ stuff is already working well.
167
+
168
+ There might be missing features or optimization potential, feedback welcome!
169
+
170
+ There are a lot of possible, but still missing backends (like e.g. for cloud
171
+ storage). If you want to create and support one: pull requests are welcome.
172
+
173
+ Borg?
174
+ -----
175
+
176
+ Please note that this code is developed by the Borg Collective and hosted
177
+ by the BorgBackup github organisation, but is currently **not** used by
178
+ the BorgBackup (aka "borg") backup tool stable release.
179
+
180
+ License
181
+ -------
182
+
183
+ BSD license.
184
+
@@ -0,0 +1,158 @@
1
+ BorgStore
2
+ =========
3
+
4
+ A key/value store implementation in Python, supporting multiple backends,
5
+ data redundancy and distribution.
6
+
7
+ Keys
8
+ ----
9
+
10
+ A key (str) can look like:
11
+
12
+ - 0123456789abcdef... (usually a long, hex-encoded hash value)
13
+ - Any other pure ASCII string without "/" or ".." or " ".
14
+
15
+
16
+ Namespaces
17
+ ----------
18
+
19
+ To keep stuff apart, keys should get prefixed with a namespace, like:
20
+
21
+ - config/settings
22
+ - meta/0123456789abcdef...
23
+ - data/0123456789abcdef...
24
+
25
+ Please note:
26
+
27
+ 1. you should always use namespaces.
28
+ 2. nested namespaces like namespace1/namespace2/key are not supported.
29
+ 3. the code could work without a namespace (namespace ""), but then you
30
+ can't add another namespace later, because then you would have created
31
+ nested namespaces.
32
+
33
+ Values
34
+ ------
35
+
36
+ Values can be any arbitrary binary data (bytes).
37
+
38
+ Store Operations
39
+ ----------------
40
+
41
+ The high-level Store API implementation transparently deals with nesting and
42
+ soft deletion, so the caller doesn't have to care much for that and the Backend
43
+ API can be much simpler:
44
+
45
+ - create/destroy: initialize or remove the whole store.
46
+ - list: flat list of the items in the given namespace, with or without soft
47
+ deleted items.
48
+ - store: write a new item into the store (giving its key/value pair)
49
+ - load: read a value from the store (giving its key), partial loads giving
50
+ offset and/or size are supported.
51
+ - info: get information about an item via its key (exists? size? ...)
52
+ - delete: immediately remove an item from the store (giving its key)
53
+ - move: implements rename, soft delete / undelete, move to current
54
+ nesting level
55
+
56
+ Automatic Nesting
57
+ -----------------
58
+
59
+ For the Store user, items have names like e.g.:
60
+
61
+ namespace/0123456789abcdef...
62
+ namespace/abcdef0123456789...
63
+
64
+ If there are very many items in the namespace, this could lead to scalability
65
+ issues in the backend, thus the Store implementation offers transparent
66
+ nesting, so that internally the Backend API will be called with
67
+ names like e.g.:
68
+
69
+ namespace/01/23/56/0123456789abcdef...
70
+ namespace/ab/cd/ef/abcdef0123456789...
71
+
72
+ The nesting depth can be configured from 0 (= no nesting) to N levels and
73
+ there can be different nesting configurations depending on the namespace.
74
+
75
+ The Store supports operating at different nesting levels in the same
76
+ namespace at the same time.
77
+
78
+ Soft deletion
79
+ -------------
80
+
81
+ To soft delete an item (so its value could be still read or it could be
82
+ undeleted), the store just renames the item, appending ".del" to its name.
83
+
84
+ Undelete reverses this by removing the ".del" suffix from the name.
85
+
86
+ Some store operations have a boolean flag "deleted" to choose whether they
87
+ shall consider soft deleted items.
88
+
89
+ Backends
90
+ --------
91
+
92
+ The backend API is rather simple, one only needs to provide some very
93
+ basic operations.
94
+
95
+ Currently, these storage backends are implemented:
96
+
97
+ - POSIX filesystems (namespaces: directories, values: in key-named files)
98
+ - SFTP (access a server via sftp, namespaces: directories, values: in key-named files)
99
+ - (more might come in future)
100
+
101
+ MStore
102
+ ------
103
+
104
+ API of MStore is very similar to Store, but instead of directly using one backend
105
+ only (like Store does), it uses multiple Stores internally to implement:
106
+
107
+ - redundancy (keep same data at multiple places)
108
+ - distribution (keep different data at multiple places)
109
+
110
+ Scalability
111
+ -----------
112
+
113
+ - Count of key/value pairs stored in a namespace: automatic nesting is
114
+ provided for keys to address common scalability issues.
115
+ - Key size: there are no special provisions for extremely long keys (like:
116
+ more than backend limitations). Usually this is not a problem though.
117
+ - Value size: there are no special provisions for dealing with large value
118
+ sizes (like: more than free memory, more than backend storage limitations,
119
+ etc.). If one deals with very large values, one usually cuts them into
120
+ chunks before storing them into the store.
121
+ - Partial loads improve performance by avoiding a full load if only a part
122
+ of the value is needed (e.g. a header with metadata).
123
+
124
+ Want a demo?
125
+ ------------
126
+
127
+ Run this to get instructions how to run the demo:
128
+
129
+ python3 -m borgstore
130
+
131
+ State of this project
132
+ ---------------------
133
+
134
+ **API is still unstable and expected to change as development goes on.**
135
+
136
+ **There will be no data migration tools involving development/testing releases,
137
+ like e.g. upgrading a store from alpha1 to alpha2 or beta13 to release.**
138
+
139
+ There are tests and they succeed for the basic functionality, so some of the
140
+ stuff is already working well.
141
+
142
+ There might be missing features or optimization potential, feedback welcome!
143
+
144
+ There are a lot of possible, but still missing backends (like e.g. for cloud
145
+ storage). If you want to create and support one: pull requests are welcome.
146
+
147
+ Borg?
148
+ -----
149
+
150
+ Please note that this code is developed by the Borg Collective and hosted
151
+ by the BorgBackup github organisation, but is currently **not** used by
152
+ the BorgBackup (aka "borg") backup tool stable release.
153
+
154
+ License
155
+ -------
156
+
157
+ BSD license.
158
+
@@ -0,0 +1,62 @@
1
+ [project]
2
+ name = "borgstore"
3
+ dynamic = ["version"]
4
+ authors = [{name="Thomas Waldmann", email="tw@waldmann-edv.de"}, ]
5
+ description = "key/value store"
6
+ readme = "README.rst"
7
+ keywords = ["kv", "key/value", "store"]
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Intended Audience :: Developers",
11
+ "License :: OSI Approved :: BSD License",
12
+ "Operating System :: POSIX",
13
+ "Programming Language :: Python",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.9",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development :: Libraries",
20
+ "Topic :: Software Development :: Libraries :: Python Modules",
21
+ ]
22
+ license = {text="BSD"}
23
+ requires-python = ">=3.9"
24
+ dependencies = [
25
+ "cryptography < 43.0.0", # using a more recent version triggers annoying warnings with paramiko
26
+ "paramiko",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/borgbackup/borgstore"
31
+
32
+ [build-system]
33
+ requires = ["setuptools", "setuptools_scm[toml]>=6.2"]
34
+ build-backend = "setuptools.build_meta"
35
+
36
+ [tool.setuptools_scm]
37
+ # make sure we have the same versioning scheme with all setuptools_scm versions, to avoid different autogenerated files
38
+ # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1015052
39
+ # https://github.com/borgbackup/borg/issues/6875
40
+ write_to = "src/borgstore/_version.py"
41
+ write_to_template = "__version__ = version = {version!r}\n"
42
+
43
+ [tool.black]
44
+ line-length = 120
45
+ skip-magic-trailing-comma = true
46
+
47
+ [tool.pytest.ini_options]
48
+ minversion = "6.0"
49
+ testpaths = ["tests"]
50
+
51
+ [tool.flake8]
52
+ # Ignoring E203 due to https://github.com/PyCQA/pycodestyle/issues/373
53
+ ignore = ['E226', 'W503', 'E203']
54
+ max_line_length = 120
55
+ exclude = ['build', 'dist', '.git', '.idea', '.mypy_cache', '.tox']
56
+
57
+ [tool.mypy]
58
+ python_version = '3.10'
59
+ strict_optional = false
60
+ local_partial_types = true
61
+ show_error_codes = true
62
+ files = 'src/borgstore/**/*.py'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """
2
+ BorgStore - a key/value store.
3
+ """
4
+
5
+ from ._version import __version__, version # noqa
@@ -0,0 +1,71 @@
1
+ """
2
+ Demo for BorgStore
3
+ ==================
4
+
5
+ Usage: python -m borgstore <borgstore_storage_url>
6
+
7
+ E.g.: python -m borgstore file:///tmp/borgstore_storage
8
+
9
+ Please be careful: the given storage will be created, used and **completely deleted**!
10
+ """
11
+
12
+
13
+ def run_demo(storage_url):
14
+ from .store import Store
15
+
16
+ def id_key(data: bytes):
17
+ from hashlib import new
18
+
19
+ h = new("sha256", data)
20
+ return f"data/{h.hexdigest()}"
21
+
22
+ levels_config = {
23
+ "config/": [0], # no nesting needed/wanted for the configs
24
+ "data/": [2], # 2 nesting levels wanted for the data
25
+ }
26
+ store = Store(url=storage_url, levels=levels_config)
27
+ try:
28
+ store.create()
29
+ except FileExistsError:
30
+ # currently, we only have file:// storages, so this should be fine.
31
+ print("Error: you must not give an existing directory.")
32
+ return
33
+
34
+ print("Writing 2 items to config namespace...")
35
+ settings1_key = "config/settings1"
36
+ store.store(settings1_key, b"value1 = 42")
37
+ settings2_key = "config/settings2"
38
+ store.store(settings2_key, b"value2 = 23")
39
+
40
+ print(f"Listing config namespace contents: {list(store.list('config'))}")
41
+
42
+ settings1_value = store.load(settings1_key)
43
+ print(f"Loaded from store: {settings1_key}: {settings1_value.decode()}")
44
+ settings2_value = store.load(settings2_key)
45
+ print(f"Loaded from store: {settings2_key}: {settings2_value.decode()}")
46
+
47
+ print("Writing 2 items to data namespace...")
48
+ data1 = b"some arbitrary binary data."
49
+ key1 = id_key(data1)
50
+ store.store(key1, data1)
51
+ data2 = b"more arbitrary binary data. " * 2
52
+ key2 = id_key(data2)
53
+ store.store(key2, data2)
54
+ print(f"Soft deleting item {key2} ...")
55
+ store.move(key2, delete=True)
56
+
57
+ print(f"Listing data namespace contents: {list(store.list('data', deleted=False))}")
58
+ print(f"Listing data namespace contents, incl. deleted: {list(store.list('data', deleted=True))}")
59
+
60
+ answer = input("After you've inspected the storage, enter DESTROY to destroy the storage, anything else to abort: ")
61
+ if answer == "DESTROY":
62
+ store.destroy()
63
+
64
+
65
+ if __name__ == "__main__":
66
+ import sys
67
+
68
+ if len(sys.argv) == 2:
69
+ run_demo(sys.argv[1])
70
+ else:
71
+ print(__doc__)
@@ -0,0 +1 @@
1
+ __version__ = version = '0.0.1'
@@ -0,0 +1,3 @@
1
+ """
2
+ Package with misc. backend implementations. See backends._base for details.
3
+ """
@@ -0,0 +1,104 @@
1
+ """
2
+ Base class and type definitions for all backend implementations in this package.
3
+
4
+ Docs that are not backend-specific are also found here.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from collections import namedtuple
9
+ from typing import Iterator
10
+
11
+ from ..constants import MAX_NAME_LENGTH
12
+
13
+ ItemInfo = namedtuple("ItemInfo", "name exists size directory")
14
+
15
+
16
+ def validate_name(name):
17
+ """validate a backend key / name"""
18
+ if not isinstance(name, str):
19
+ raise TypeError(f"name must be str, but got: {type(name)}")
20
+ # name must not be too long
21
+ if len(name) > MAX_NAME_LENGTH:
22
+ raise ValueError(f"name is too long (max: {MAX_NAME_LENGTH}): {name}")
23
+ # avoid encoding issues
24
+ try:
25
+ name.encode("ascii")
26
+ except UnicodeEncodeError:
27
+ raise ValueError(f"name must encode to plain ascii, but failed with: {name}")
28
+ # security: name must be relative - can be foo or foo/bar/baz, but must never be /foo or ../foo
29
+ if name.startswith("/") or name.endswith("/") or ".." in name:
30
+ raise ValueError(f"name must be relative and not contain '..': {name}")
31
+ # names used here always have '/' as separator, never '\' -
32
+ # this is to avoid confusion in case this is ported to e.g. Windows.
33
+ # also: no blanks - simplifies usage via CLI / shell.
34
+ if "\\" in name or " " in name:
35
+ raise ValueError(f"name must not contain backslashes or blanks: {name}")
36
+ # name must be lowercase - this is to avoid troubles in case this is ported to a non-case-sensitive backend.
37
+ # also, guess we want to avoid that a key "config" would address a different item than a key "CONFIG" or
38
+ # a key "1234CAFE5678BABE" would address a different item than a key "1234cafe5678babe".
39
+ if name != name.lower():
40
+ raise ValueError(f"name must be lowercase, but got: {name}")
41
+
42
+
43
+ class BackendBase(ABC):
44
+ @abstractmethod
45
+ def create(self):
46
+ """create (initialize) a backend storage"""
47
+
48
+ @abstractmethod
49
+ def destroy(self):
50
+ """completely remove the backend storage (and its contents)"""
51
+
52
+ def __enter__(self):
53
+ self.open()
54
+ return self
55
+
56
+ def __exit__(self, exc_type, exc_val, exc_tb):
57
+ self.close()
58
+ return False
59
+
60
+ @abstractmethod
61
+ def open(self):
62
+ """open (start using) a backend storage"""
63
+
64
+ @abstractmethod
65
+ def close(self):
66
+ """close (stop using) a backend storage"""
67
+
68
+ @abstractmethod
69
+ def mkdir(self, name: str) -> None:
70
+ """create directory/namespace <name>"""
71
+
72
+ @abstractmethod
73
+ def rmdir(self, name: str) -> None:
74
+ """remove directory/namespace <name>"""
75
+
76
+ @abstractmethod
77
+ def info(self, name) -> ItemInfo:
78
+ """return information about <name>"""
79
+
80
+ @abstractmethod
81
+ def load(self, name: str, *, size=None, offset=0) -> bytes:
82
+ """load value from <name>"""
83
+
84
+ @abstractmethod
85
+ def store(self, name: str, value: bytes) -> None:
86
+ """store <value> into <name>"""
87
+
88
+ @abstractmethod
89
+ def delete(self, name: str) -> None:
90
+ """delete <name>"""
91
+
92
+ @abstractmethod
93
+ def move(self, curr_name: str, new_name: str) -> None:
94
+ """rename curr_name to new_name (overwrite target)"""
95
+
96
+ @abstractmethod
97
+ def list(self, name: str) -> Iterator[ItemInfo]:
98
+ """list the contents of <name>, non-recursively.
99
+
100
+ Does not yield TMP_SUFFIX items - usually they are either not finished
101
+ uploading or they are leftover crap from aborted uploads.
102
+
103
+ The yielded ItemInfos are sorted alphabetically by name.
104
+ """
@@ -0,0 +1,27 @@
1
+ """
2
+ Generic exception classes used by all backends.
3
+ """
4
+
5
+
6
+ class BackendError(Exception):
7
+ """Base class for exceptions in this module."""
8
+
9
+
10
+ class BackendAlreadyExists(BackendError):
11
+ """Raised when a backend already exists."""
12
+
13
+
14
+ class BackendDoesNotExist(BackendError):
15
+ """Raised when a backend does not exist."""
16
+
17
+
18
+ class BackendMustNotBeOpen(BackendError):
19
+ """Backend must not be open."""
20
+
21
+
22
+ class BackendMustBeOpen(BackendError):
23
+ """Backend must be open."""
24
+
25
+
26
+ class ObjectNotFound(BackendError):
27
+ """Object not found."""