truhe 1.0.0__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.
truhe-1.0.0/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.egg-info/
2
+ *.pyc
3
+ /.coverage
4
+ /.mxmake
5
+ /.ruff_cache
6
+ /dist/
7
+ /htmlcov/
8
+ /requirements-mxdev.txt
9
+ /venv
10
+ /build
@@ -0,0 +1,10 @@
1
+ Changes
2
+ =======
3
+
4
+ 1.0.0 (2026-11-04)
5
+ ------------------
6
+
7
+ - Initial release. Encrypted blob store with stores for memory, the
8
+ filesystem and S3, synchronous over ``urllib3``, asynchronous over
9
+ ``httpx``. AES-256-GCM in 64 KiB blocks, streaming in both directions.
10
+ [rnix]
@@ -0,0 +1,24 @@
1
+ License
2
+ =======
3
+
4
+ MIT License
5
+
6
+ Copyright (c) 2026 Conestack
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
truhe-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.5
2
+ Name: truhe
3
+ Version: 1.0.0
4
+ Summary: Encrypted blob stores for S3, the filesystem and memory, synchronous and asynchronous.
5
+ Project-URL: Homepage, http://github.com/conestack/truhe
6
+ Author-email: Conestack Contributors <dev@conestack.org>
7
+ License: MIT
8
+ License-File: LICENSE.rst
9
+ Keywords: aes-gcm,blob,encryption,s3,sigv4,storage
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Topic :: Security :: Cryptography
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: cryptography
25
+ Provides-Extra: httpx
26
+ Requires-Dist: httpx<1; extra == 'httpx'
27
+ Provides-Extra: test
28
+ Requires-Dist: httpx<1; extra == 'test'
29
+ Requires-Dist: pytest; extra == 'test'
30
+ Requires-Dist: urllib3>=2; extra == 'test'
31
+ Provides-Extra: urllib3
32
+ Requires-Dist: urllib3>=2; extra == 'urllib3'
33
+ Description-Content-Type: text/x-rst
34
+
35
+ .. image:: https://img.shields.io/pypi/v/truhe.svg
36
+ :target: https://pypi.python.org/pypi/truhe
37
+ :alt: Latest PyPI version
38
+
39
+ .. image:: https://img.shields.io/pypi/dm/truhe.svg
40
+ :target: https://pypi.python.org/pypi/truhe
41
+ :alt: Number of PyPI downloads
42
+
43
+ .. image:: https://github.com/conestack/truhe/actions/workflows/test.yml/badge.svg
44
+ :target: https://github.com/conestack/truhe/actions/workflows/test.yml
45
+ :alt: Test truhe
46
+
47
+
48
+ truhe
49
+ =====
50
+
51
+ Encrypted blob stores. An application puts its bytes into a store and gets
52
+ them back; the store, S3, a directory or memory, only ever sees ciphertext.
53
+
54
+ - AES-256-GCM in 64 KiB blocks, so objects of any size stream in and out
55
+ without being held in memory.
56
+ - Stores for S3 compatible object storage, the filesystem and memory.
57
+ - Synchronous over ``urllib3``, asynchronous over ``httpx``.
58
+ - AWS Signature Version 4 of its own, no SDK.
59
+ - Keys rotate without rewriting the store.
60
+
61
+ .. contents::
62
+ :depth: 2
63
+
64
+
65
+ Installation
66
+ ------------
67
+
68
+ The S3 store needs an HTTP client, chosen with an extra:
69
+
70
+ .. code-block:: shell
71
+
72
+ pip install truhe[urllib3] # synchronous
73
+ pip install truhe[httpx] # asynchronous
74
+
75
+ Without extra, the filesystem and the memory store are available.
76
+
77
+
78
+ Usage
79
+ -----
80
+
81
+ A store wrapped in ``EncryptedBlobStore`` encrypts on the way in and decrypts
82
+ on the way out:
83
+
84
+ .. code-block:: python
85
+
86
+ import os
87
+
88
+ from truhe import EncryptedBlobStore
89
+ from truhe import Keyring
90
+ from truhe.s3 import S3BlobStore
91
+ from truhe.sigv4 import Credentials
92
+
93
+ credentials = Credentials(
94
+ access_key='...',
95
+ secret_key='...',
96
+ region='fsn1',
97
+ )
98
+ store = EncryptedBlobStore(
99
+ S3BlobStore(credentials, 'https://fsn1.your-objectstorage.com', 'bucket'),
100
+ Keyring.from_setting(keys),
101
+ magic=b'MYAPP',
102
+ )
103
+
104
+ # bytes
105
+ store.put('reports/2026/q3.pdf', data, 'application/pdf')
106
+
107
+ # a stream, with its length up front
108
+ with open('intro.mp4', 'rb') as f:
109
+ store.put(
110
+ 'videos/intro.mp4',
111
+ iter(lambda: f.read(65536), b''),
112
+ 'video/mp4',
113
+ content_length=os.fstat(f.fileno()).st_size,
114
+ )
115
+
116
+ with store.open('videos/intro.mp4') as stream:
117
+ for chunk in stream.chunks:
118
+ ...
119
+
120
+ store.head('videos/intro.mp4') # StoredBlob(size=..., content_type=...)
121
+ list(store.list('videos/')) # ListedBlob(key, size, modified)
122
+ store.delete('videos/intro.mp4')
123
+
124
+ The asynchronous stores have the same operations as coroutines, ``open`` is an
125
+ async context manager, ``list`` an async iterator, a stream to ``put`` an
126
+ async iterable:
127
+
128
+ .. code-block:: python
129
+
130
+ import httpx
131
+
132
+ from truhe.aio import EncryptedBlobStore
133
+ from truhe.aio.s3 import S3BlobStore
134
+
135
+ async with httpx.AsyncClient() as client:
136
+ store = EncryptedBlobStore(
137
+ S3BlobStore(credentials, endpoint, 'bucket', client),
138
+ keyring,
139
+ )
140
+ await store.put('k', data, 'application/octet-stream')
141
+ async with store.open('k') as stream:
142
+ async for chunk in stream.chunks:
143
+ ...
144
+
145
+ Sizes are always plaintext sizes, the framing stays inside
146
+ ``EncryptedBlobStore``.
147
+
148
+
149
+ Streams
150
+ -------
151
+
152
+ ``put`` takes bytes, or an iterable of bytes together with ``content_length``.
153
+ A stream which delivers more or fewer bytes than announced fails, and nothing
154
+ is stored: the piece which would complete the object is only passed on once
155
+ the stream is known to end there.
156
+
157
+ Bytes go to S3 with their SHA-256 in the signature and ``Content-MD5``, so the
158
+ store verifies what arrived. A stream goes out with ``UNSIGNED-PAYLOAD`` and
159
+ its ``Content-Length``; hashing it first would mean holding it. One request
160
+ per object, so an object is at most 5 GiB, the limit of a single ``PUT``.
161
+
162
+
163
+ Keys
164
+ ----
165
+
166
+ A ``Keyring`` holds 32 byte keys, newest first. New objects are sealed with the
167
+ first, every object names its key in its header. To rotate, put a new key in
168
+ front, old objects stay readable. Dropping a key makes its objects
169
+ unreadable.
170
+
171
+ .. code-block:: shell
172
+
173
+ openssl rand -hex 32
174
+
175
+ ``Keyring.from_setting`` reads a comma separated list of hex keys, the form of
176
+ an environment variable. ``Keyring.primary_id`` is the id of the current key,
177
+ worth recording next to every object, so the progress of a rotation is a
178
+ query.
179
+
180
+ **The key is the data.** Lose it, and every object is gone while the store is
181
+ perfectly healthy.
182
+
183
+
184
+ S3 compatible stores
185
+ --------------------
186
+
187
+ ``S3BlobStore`` addresses the bucket virtual hosted,
188
+ ``{bucket}.{endpoint host}``, the form most providers document. Pass
189
+ ``path_style=True`` for ``{endpoint}/{bucket}``, e.g. MinIO or Garage without
190
+ a domain. The region is the location code of the provider.
191
+
192
+ The stores do not retry. A retried upload after a lost answer is the
193
+ application's decision.
194
+
195
+
196
+ Format
197
+ ------
198
+
199
+ An object is a header and its sealed blocks:
200
+
201
+ =============== ======= =====================================================
202
+ Field Bytes Content
203
+ =============== ======= =====================================================
204
+ magic 5 names the application, ``TRUHE`` by default
205
+ version 1 ``1``
206
+ key id 8 truncated SHA-256 of the key
207
+ nonce prefix 8 random per object
208
+ blocks n 64 KiB plaintext each, the last one shorter, sealed
209
+ with AES-256-GCM, 16 byte tag
210
+ =============== ======= =====================================================
211
+
212
+ The nonce of a block is the prefix and the block counter, the associated data
213
+ is the header and a flag for the last block. So a block cannot be reordered
214
+ or moved to another object, a truncated object does not verify, and a header
215
+ cannot be swapped. An empty object has one sealed empty block.
216
+
217
+ ``truhe.format`` has the format without I/O: ``Sealer`` and ``Opener`` take
218
+ bytes as they arrive and return complete blocks, ``ciphertext_length`` gives
219
+ the stored size before the first byte.
220
+
221
+ Not supported: HTTP ``Range``, multipart uploads, presigned URLs.
222
+
223
+
224
+ Tests
225
+ -----
226
+
227
+ .. code-block:: shell
228
+
229
+ make install
230
+ make test
231
+
232
+ The S3 stores are tested offline. The tests against a store are opt-in, ``make
233
+ test`` skips them. To run them against a throwaway MinIO:
234
+
235
+ .. code-block:: shell
236
+
237
+ make test-s3
238
+
239
+ This starts the container from ``testing/compose.minio.yml``, runs the S3 tests
240
+ and discards the container, also when a test fails. To keep it running while
241
+ working on the tests:
242
+
243
+ .. code-block:: shell
244
+
245
+ ./scripts/minio.sh up
246
+ eval "$(./scripts/minio.sh env)"
247
+ make test
248
+ ./scripts/minio.sh down
249
+
250
+ The store is selected by ``TRUHE_TEST_S3_ENDPOINT``,
251
+ ``TRUHE_TEST_S3_ACCESS_KEY``, ``TRUHE_TEST_S3_SECRET_KEY``,
252
+ ``TRUHE_TEST_S3_REGION`` and ``TRUHE_TEST_S3_BUCKET``, plus
253
+ ``TRUHE_TEST_S3_PATH_STYLE=1`` if needed. **The bucket is emptied before each
254
+ test**, so it must be one which holds nothing else.
255
+
256
+
257
+ Contributors
258
+ ------------
259
+
260
+ - Robert Niederreiter
261
+
262
+
263
+ Changes
264
+ =======
265
+
266
+ 1.0.0 (2026-11-04)
267
+ ------------------
268
+
269
+ - Initial release. Encrypted blob store with stores for memory, the
270
+ filesystem and S3, synchronous over ``urllib3``, asynchronous over
271
+ ``httpx``. AES-256-GCM in 64 KiB blocks, streaming in both directions.
272
+ [rnix]
273
+
274
+
275
+ License
276
+ =======
277
+
278
+ MIT License
279
+
280
+ Copyright (c) 2026 Conestack
281
+
282
+ Permission is hereby granted, free of charge, to any person obtaining a copy
283
+ of this software and associated documentation files (the "Software"), to deal
284
+ in the Software without restriction, including without limitation the rights
285
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
286
+ copies of the Software, and to permit persons to whom the Software is
287
+ furnished to do so, subject to the following conditions:
288
+
289
+ The above copyright notice and this permission notice shall be included in all
290
+ copies or substantial portions of the Software.
291
+
292
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
293
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
294
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
295
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
296
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
297
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
298
+ SOFTWARE.
truhe-1.0.0/README.rst ADDED
@@ -0,0 +1,226 @@
1
+ .. image:: https://img.shields.io/pypi/v/truhe.svg
2
+ :target: https://pypi.python.org/pypi/truhe
3
+ :alt: Latest PyPI version
4
+
5
+ .. image:: https://img.shields.io/pypi/dm/truhe.svg
6
+ :target: https://pypi.python.org/pypi/truhe
7
+ :alt: Number of PyPI downloads
8
+
9
+ .. image:: https://github.com/conestack/truhe/actions/workflows/test.yml/badge.svg
10
+ :target: https://github.com/conestack/truhe/actions/workflows/test.yml
11
+ :alt: Test truhe
12
+
13
+
14
+ truhe
15
+ =====
16
+
17
+ Encrypted blob stores. An application puts its bytes into a store and gets
18
+ them back; the store, S3, a directory or memory, only ever sees ciphertext.
19
+
20
+ - AES-256-GCM in 64 KiB blocks, so objects of any size stream in and out
21
+ without being held in memory.
22
+ - Stores for S3 compatible object storage, the filesystem and memory.
23
+ - Synchronous over ``urllib3``, asynchronous over ``httpx``.
24
+ - AWS Signature Version 4 of its own, no SDK.
25
+ - Keys rotate without rewriting the store.
26
+
27
+ .. contents::
28
+ :depth: 2
29
+
30
+
31
+ Installation
32
+ ------------
33
+
34
+ The S3 store needs an HTTP client, chosen with an extra:
35
+
36
+ .. code-block:: shell
37
+
38
+ pip install truhe[urllib3] # synchronous
39
+ pip install truhe[httpx] # asynchronous
40
+
41
+ Without extra, the filesystem and the memory store are available.
42
+
43
+
44
+ Usage
45
+ -----
46
+
47
+ A store wrapped in ``EncryptedBlobStore`` encrypts on the way in and decrypts
48
+ on the way out:
49
+
50
+ .. code-block:: python
51
+
52
+ import os
53
+
54
+ from truhe import EncryptedBlobStore
55
+ from truhe import Keyring
56
+ from truhe.s3 import S3BlobStore
57
+ from truhe.sigv4 import Credentials
58
+
59
+ credentials = Credentials(
60
+ access_key='...',
61
+ secret_key='...',
62
+ region='fsn1',
63
+ )
64
+ store = EncryptedBlobStore(
65
+ S3BlobStore(credentials, 'https://fsn1.your-objectstorage.com', 'bucket'),
66
+ Keyring.from_setting(keys),
67
+ magic=b'MYAPP',
68
+ )
69
+
70
+ # bytes
71
+ store.put('reports/2026/q3.pdf', data, 'application/pdf')
72
+
73
+ # a stream, with its length up front
74
+ with open('intro.mp4', 'rb') as f:
75
+ store.put(
76
+ 'videos/intro.mp4',
77
+ iter(lambda: f.read(65536), b''),
78
+ 'video/mp4',
79
+ content_length=os.fstat(f.fileno()).st_size,
80
+ )
81
+
82
+ with store.open('videos/intro.mp4') as stream:
83
+ for chunk in stream.chunks:
84
+ ...
85
+
86
+ store.head('videos/intro.mp4') # StoredBlob(size=..., content_type=...)
87
+ list(store.list('videos/')) # ListedBlob(key, size, modified)
88
+ store.delete('videos/intro.mp4')
89
+
90
+ The asynchronous stores have the same operations as coroutines, ``open`` is an
91
+ async context manager, ``list`` an async iterator, a stream to ``put`` an
92
+ async iterable:
93
+
94
+ .. code-block:: python
95
+
96
+ import httpx
97
+
98
+ from truhe.aio import EncryptedBlobStore
99
+ from truhe.aio.s3 import S3BlobStore
100
+
101
+ async with httpx.AsyncClient() as client:
102
+ store = EncryptedBlobStore(
103
+ S3BlobStore(credentials, endpoint, 'bucket', client),
104
+ keyring,
105
+ )
106
+ await store.put('k', data, 'application/octet-stream')
107
+ async with store.open('k') as stream:
108
+ async for chunk in stream.chunks:
109
+ ...
110
+
111
+ Sizes are always plaintext sizes, the framing stays inside
112
+ ``EncryptedBlobStore``.
113
+
114
+
115
+ Streams
116
+ -------
117
+
118
+ ``put`` takes bytes, or an iterable of bytes together with ``content_length``.
119
+ A stream which delivers more or fewer bytes than announced fails, and nothing
120
+ is stored: the piece which would complete the object is only passed on once
121
+ the stream is known to end there.
122
+
123
+ Bytes go to S3 with their SHA-256 in the signature and ``Content-MD5``, so the
124
+ store verifies what arrived. A stream goes out with ``UNSIGNED-PAYLOAD`` and
125
+ its ``Content-Length``; hashing it first would mean holding it. One request
126
+ per object, so an object is at most 5 GiB, the limit of a single ``PUT``.
127
+
128
+
129
+ Keys
130
+ ----
131
+
132
+ A ``Keyring`` holds 32 byte keys, newest first. New objects are sealed with the
133
+ first, every object names its key in its header. To rotate, put a new key in
134
+ front, old objects stay readable. Dropping a key makes its objects
135
+ unreadable.
136
+
137
+ .. code-block:: shell
138
+
139
+ openssl rand -hex 32
140
+
141
+ ``Keyring.from_setting`` reads a comma separated list of hex keys, the form of
142
+ an environment variable. ``Keyring.primary_id`` is the id of the current key,
143
+ worth recording next to every object, so the progress of a rotation is a
144
+ query.
145
+
146
+ **The key is the data.** Lose it, and every object is gone while the store is
147
+ perfectly healthy.
148
+
149
+
150
+ S3 compatible stores
151
+ --------------------
152
+
153
+ ``S3BlobStore`` addresses the bucket virtual hosted,
154
+ ``{bucket}.{endpoint host}``, the form most providers document. Pass
155
+ ``path_style=True`` for ``{endpoint}/{bucket}``, e.g. MinIO or Garage without
156
+ a domain. The region is the location code of the provider.
157
+
158
+ The stores do not retry. A retried upload after a lost answer is the
159
+ application's decision.
160
+
161
+
162
+ Format
163
+ ------
164
+
165
+ An object is a header and its sealed blocks:
166
+
167
+ =============== ======= =====================================================
168
+ Field Bytes Content
169
+ =============== ======= =====================================================
170
+ magic 5 names the application, ``TRUHE`` by default
171
+ version 1 ``1``
172
+ key id 8 truncated SHA-256 of the key
173
+ nonce prefix 8 random per object
174
+ blocks n 64 KiB plaintext each, the last one shorter, sealed
175
+ with AES-256-GCM, 16 byte tag
176
+ =============== ======= =====================================================
177
+
178
+ The nonce of a block is the prefix and the block counter, the associated data
179
+ is the header and a flag for the last block. So a block cannot be reordered
180
+ or moved to another object, a truncated object does not verify, and a header
181
+ cannot be swapped. An empty object has one sealed empty block.
182
+
183
+ ``truhe.format`` has the format without I/O: ``Sealer`` and ``Opener`` take
184
+ bytes as they arrive and return complete blocks, ``ciphertext_length`` gives
185
+ the stored size before the first byte.
186
+
187
+ Not supported: HTTP ``Range``, multipart uploads, presigned URLs.
188
+
189
+
190
+ Tests
191
+ -----
192
+
193
+ .. code-block:: shell
194
+
195
+ make install
196
+ make test
197
+
198
+ The S3 stores are tested offline. The tests against a store are opt-in, ``make
199
+ test`` skips them. To run them against a throwaway MinIO:
200
+
201
+ .. code-block:: shell
202
+
203
+ make test-s3
204
+
205
+ This starts the container from ``testing/compose.minio.yml``, runs the S3 tests
206
+ and discards the container, also when a test fails. To keep it running while
207
+ working on the tests:
208
+
209
+ .. code-block:: shell
210
+
211
+ ./scripts/minio.sh up
212
+ eval "$(./scripts/minio.sh env)"
213
+ make test
214
+ ./scripts/minio.sh down
215
+
216
+ The store is selected by ``TRUHE_TEST_S3_ENDPOINT``,
217
+ ``TRUHE_TEST_S3_ACCESS_KEY``, ``TRUHE_TEST_S3_SECRET_KEY``,
218
+ ``TRUHE_TEST_S3_REGION`` and ``TRUHE_TEST_S3_BUCKET``, plus
219
+ ``TRUHE_TEST_S3_PATH_STYLE=1`` if needed. **The bucket is emptied before each
220
+ test**, so it must be one which holds nothing else.
221
+
222
+
223
+ Contributors
224
+ ------------
225
+
226
+ - Robert Niederreiter
@@ -0,0 +1,98 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-fancy-pypi-readme"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "truhe"
7
+ version = "1.0.0"
8
+ description = "Encrypted blob stores for S3, the filesystem and memory, synchronous and asynchronous."
9
+ dynamic = ["readme"]
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "Conestack Contributors", email = "dev@conestack.org"}]
13
+ keywords = ["s3", "blob", "storage", "encryption", "aes-gcm", "sigv4"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Framework :: AsyncIO",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Topic :: Security :: Cryptography",
20
+ "Topic :: Software Development :: Libraries",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Programming Language :: Python :: 3.15",
28
+ ]
29
+ dependencies = [
30
+ "cryptography",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ urllib3 = [
35
+ "urllib3>=2",
36
+ ]
37
+ httpx = [
38
+ "httpx<1",
39
+ ]
40
+ test = [
41
+ "httpx<1",
42
+ "pytest",
43
+ "urllib3>=2",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "http://github.com/conestack/truhe"
48
+
49
+ [tool.hatch.metadata.hooks.fancy-pypi-readme]
50
+ content-type = "text/x-rst"
51
+ fragments = [
52
+ {path = "README.rst"},
53
+ {text = "\n\n"},
54
+ {path = "CHANGES.rst"},
55
+ {text = "\n\n"},
56
+ {path = "LICENSE.rst"},
57
+ ]
58
+
59
+ [tool.hatch.build.targets.sdist]
60
+ exclude = [
61
+ "/.github/",
62
+ "/include.mk",
63
+ "/Makefile",
64
+ "/mx.ini",
65
+ "/scripts/",
66
+ "/testing/",
67
+ ]
68
+
69
+ [tool.hatch.build.targets.wheel]
70
+ packages = ["src/truhe"]
71
+
72
+ [tool.pytest.ini_options]
73
+ consider_namespace_packages = true
74
+ addopts = ["--import-mode=importlib"]
75
+ pythonpath = "src"
76
+
77
+ # The S3 tests are opt-in, without a bucket their bodies never run.
78
+ [tool.coverage.run]
79
+ omit = ["*/truhe/tests/*"]
80
+
81
+ [tool.ruff]
82
+ line-length = 90
83
+
84
+ [tool.ruff.lint]
85
+ select = ["E4", "E7", "E9", "F", "I", "UP"]
86
+
87
+ [tool.ruff.lint.isort]
88
+ force-single-line = true
89
+ from-first = true
90
+ no-sections = true
91
+ lines-after-imports = 2
92
+ order-by-type = false
93
+
94
+ [tool.ruff.format]
95
+ quote-style = "single"
96
+
97
+ [tool.zest-releaser]
98
+ create-wheel = true
@@ -0,0 +1,9 @@
1
+ from truhe.filesystem import FileSystemBlobStore # noqa
2
+ from truhe.format import EncryptionError # noqa
3
+ from truhe.format import Keyring # noqa
4
+ from truhe.sealed import EncryptedBlobStore # noqa
5
+ from truhe.store import BlobStoreError # noqa
6
+ from truhe.store import BlobStream # noqa
7
+ from truhe.store import ListedBlob # noqa
8
+ from truhe.store import MemoryBlobStore # noqa
9
+ from truhe.store import StoredBlob # noqa