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.
- borgstore-0.0.1/CHANGES.rst +7 -0
- borgstore-0.0.1/LICENSE.rst +28 -0
- borgstore-0.0.1/PKG-INFO +184 -0
- borgstore-0.0.1/README.rst +158 -0
- borgstore-0.0.1/pyproject.toml +62 -0
- borgstore-0.0.1/setup.cfg +4 -0
- borgstore-0.0.1/src/borgstore/__init__.py +5 -0
- borgstore-0.0.1/src/borgstore/__main__.py +71 -0
- borgstore-0.0.1/src/borgstore/_version.py +1 -0
- borgstore-0.0.1/src/borgstore/backends/__init__.py +3 -0
- borgstore-0.0.1/src/borgstore/backends/_base.py +104 -0
- borgstore-0.0.1/src/borgstore/backends/errors.py +27 -0
- borgstore-0.0.1/src/borgstore/backends/posixfs.py +169 -0
- borgstore-0.0.1/src/borgstore/backends/sftp.py +212 -0
- borgstore-0.0.1/src/borgstore/constants.py +11 -0
- borgstore-0.0.1/src/borgstore/mstore.py +204 -0
- borgstore-0.0.1/src/borgstore/store.py +172 -0
- borgstore-0.0.1/src/borgstore/utils/__init__.py +0 -0
- borgstore-0.0.1/src/borgstore/utils/nesting.py +65 -0
- borgstore-0.0.1/src/borgstore.egg-info/PKG-INFO +184 -0
- borgstore-0.0.1/src/borgstore.egg-info/SOURCES.txt +22 -0
- borgstore-0.0.1/src/borgstore.egg-info/dependency_links.txt +1 -0
- borgstore-0.0.1/src/borgstore.egg-info/requires.txt +2 -0
- borgstore-0.0.1/src/borgstore.egg-info/top_level.txt +1 -0
|
@@ -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.
|
borgstore-0.0.1/PKG-INFO
ADDED
|
@@ -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,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,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."""
|