ansys-bdm-api 0.5.dev0__py3-none-any.whl
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.
- ansys/bdm/api/__init__.py +62 -0
- ansys/bdm/api/entity_handle.py +98 -0
- ansys/bdm/api/iasync_entity_writer.py +71 -0
- ansys/bdm/api/iasync_storage_scope.py +605 -0
- ansys/bdm/api/ientity_writer.py +70 -0
- ansys/bdm/api/istorage_scope.py +602 -0
- ansys/bdm/api/istorage_scope_factory.py +135 -0
- ansys/bdm/api/py.typed +0 -0
- ansys/bdm/api/recursive_dictionary.py +132 -0
- ansys/bdm/api/storage_exceptions.py +45 -0
- ansys/bdm/base/__init__.py +15 -0
- ansys/bdm/base/base_async_storage_scope.py +92 -0
- ansys/bdm/base/base_storage_scope.py +80 -0
- ansys/bdm/base/encoder.py +89 -0
- ansys/bdm/base/py.typed +0 -0
- ansys_bdm_api-0.5.dev0.dist-info/METADATA +171 -0
- ansys_bdm_api-0.5.dev0.dist-info/RECORD +20 -0
- ansys_bdm_api-0.5.dev0.dist-info/WHEEL +4 -0
- ansys_bdm_api-0.5.dev0.dist-info/licenses/AUTHORS +12 -0
- ansys_bdm_api-0.5.dev0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
# Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
from collections.abc import AsyncIterator
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from types import NotImplementedType, TracebackType
|
|
20
|
+
from typing import TYPE_CHECKING, Protocol
|
|
21
|
+
|
|
22
|
+
import aioshutil
|
|
23
|
+
import anyio
|
|
24
|
+
from anyio.abc import ByteReceiveStream
|
|
25
|
+
|
|
26
|
+
from ansys.bdm.api.entity_handle import EntityHandle
|
|
27
|
+
from ansys.bdm.api.iasync_entity_writer import IAsyncEntityWriter
|
|
28
|
+
from ansys.bdm.api.recursive_dictionary import (
|
|
29
|
+
RecursiveDictionaryOfEntityHandles,
|
|
30
|
+
get_and_create_nested_dict,
|
|
31
|
+
validate_path_component,
|
|
32
|
+
)
|
|
33
|
+
from ansys.bdm.api.storage_exceptions import NotFoundInLocalStorageRootError
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from os import PathLike
|
|
37
|
+
|
|
38
|
+
import ansys.bdm.api.istorage_scope
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class IAsyncReadStorageScope(Protocol):
|
|
42
|
+
"""
|
|
43
|
+
Represents access to a read only Blob Data Management system.
|
|
44
|
+
Allows consumers to produce and consume :class:`EntityHandle` instances.
|
|
45
|
+
|
|
46
|
+
This is the asynchronous version of the :class:`IReadStorageScope` interface.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
async def __aenter__(self) -> "IAsyncReadStorageScope":
|
|
50
|
+
"""
|
|
51
|
+
Track the set of files that are stored centrally.
|
|
52
|
+
"""
|
|
53
|
+
# deliberately not implemented
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
async def __aexit__(
|
|
57
|
+
self,
|
|
58
|
+
__exc_type: type[BaseException] | None, # noqa: PYI063
|
|
59
|
+
__exc_value: BaseException | None,
|
|
60
|
+
__traceback: TracebackType | None,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Remove all files from local storage that are not stored centrally.
|
|
64
|
+
"""
|
|
65
|
+
# deliberately not implemented
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
async def get_cached(self, entity: EntityHandle) -> Path:
|
|
69
|
+
"""
|
|
70
|
+
Realize the data to a local filesystem if needed and returns a path to the cached or original file.
|
|
71
|
+
|
|
72
|
+
The :class:`EntityHandle` is intended to represent an immutable value. The file
|
|
73
|
+
returned by this call may point to a cached or even the original file. Callers
|
|
74
|
+
must not modify the file on disk or undefined behavior, including class 3 errors,
|
|
75
|
+
may occur. If the caller needs to modify the file, consider using
|
|
76
|
+
:func:`get_copy()`, or copying the file before modifying it.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
entity: EntityHandle
|
|
81
|
+
The handle to the data to realize
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
Path
|
|
86
|
+
The path to the contents of the EntityHandle as realized locally. The caller
|
|
87
|
+
MUST NOT modify the returned path.
|
|
88
|
+
"""
|
|
89
|
+
# deliberately not implemented
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
async def get_copy(self, entity: EntityHandle, destination: Path) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Realize the data to a local filesystem by writing to a specified file.
|
|
95
|
+
|
|
96
|
+
The caller is free to modify the written file. The caller is responsible
|
|
97
|
+
for deleting the generated file. If the destination path is within the storage root
|
|
98
|
+
then the copy at that location will be deleted when the storage scope context is closed.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
entity: EntityHandle
|
|
103
|
+
The handle to the data to realize
|
|
104
|
+
destination: Path
|
|
105
|
+
The path to the file to write
|
|
106
|
+
"""
|
|
107
|
+
# deliberately not implemented
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
async def get_stream(self, entity: EntityHandle) -> ByteReceiveStream:
|
|
111
|
+
"""
|
|
112
|
+
Open the EntityHandle contents for reading as a stream.
|
|
113
|
+
|
|
114
|
+
The returned stream MUST NOT be writable. The returned stream MAY be seekable.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
entity: EntityHandle
|
|
119
|
+
The handle to the data to realize
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
ByteReceiveStream
|
|
124
|
+
The stream which, when read, will return the contents of the EntityHandle.
|
|
125
|
+
|
|
126
|
+
Raises
|
|
127
|
+
------
|
|
128
|
+
|
|
129
|
+
CannotGenerateStreamForDirectoryError
|
|
130
|
+
If the entity requested is a collection
|
|
131
|
+
"""
|
|
132
|
+
# deliberately not implemented
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
async def get_bytes(self, entity: EntityHandle) -> bytes:
|
|
136
|
+
"""
|
|
137
|
+
Return the content of the referenced blob.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
entity: EntityHandle
|
|
142
|
+
The handle to the data to realize
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
bytes
|
|
147
|
+
the contents of the EntityHandle.
|
|
148
|
+
|
|
149
|
+
Raises
|
|
150
|
+
------
|
|
151
|
+
|
|
152
|
+
CannotGenerateStreamForDirectoryError
|
|
153
|
+
If the entity requested is a collection
|
|
154
|
+
"""
|
|
155
|
+
# deliberately not implemented
|
|
156
|
+
...
|
|
157
|
+
|
|
158
|
+
async def get_text(self, entity: EntityHandle, encoding: str | None = None) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Return the content of the referenced blob.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
entity: EntityHandle
|
|
165
|
+
The handle to the data to realize
|
|
166
|
+
encoding: Optional[str]
|
|
167
|
+
The name of an encoding. When this argument is not None the bytes
|
|
168
|
+
of ``entity`` will be read using that encoding.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
str
|
|
173
|
+
the contents of the blob referenced by ``entity`` in text form with the encoding determined
|
|
174
|
+
in order of preference by:
|
|
175
|
+
- the encoding argument if not None
|
|
176
|
+
- the encoding field of the entity argument if set
|
|
177
|
+
- the BOM of the referenced entity if it contains one; or
|
|
178
|
+
- UTF-8
|
|
179
|
+
|
|
180
|
+
Raises
|
|
181
|
+
------
|
|
182
|
+
|
|
183
|
+
CannotGenerateStreamForDirectoryError
|
|
184
|
+
If the entity requested is a collection
|
|
185
|
+
"""
|
|
186
|
+
# deliberately not implemented
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
async def get_children(self, entity: EntityHandle) -> AsyncIterator[EntityHandle]:
|
|
190
|
+
"""
|
|
191
|
+
Return the entities contained in this entity.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
|
|
196
|
+
entity: EntityHandle
|
|
197
|
+
The entity to query
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
|
|
203
|
+
List[EntityHandle]
|
|
204
|
+
The list of entities that are children of the provided entity
|
|
205
|
+
|
|
206
|
+
Raises
|
|
207
|
+
------
|
|
208
|
+
|
|
209
|
+
NotADirectoryError
|
|
210
|
+
If the requested entity is not a directory
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
# Note: python requires a yield keyword in a function to actually transform the
|
|
214
|
+
# function's signature to a generator or iterator.
|
|
215
|
+
# So this workaround adds a dummy yield to force the Python interpreter to
|
|
216
|
+
# transform the function's signature to a generator and to please static linters.
|
|
217
|
+
yield NotImplementedType() # pyright: ignore[reportGeneralTypeIssues]
|
|
218
|
+
|
|
219
|
+
async def get_child(self, entity: EntityHandle, child_name: str) -> EntityHandle:
|
|
220
|
+
"""
|
|
221
|
+
Return the entity contained in this entity with a given name.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
|
|
226
|
+
entity: EntityHandle
|
|
227
|
+
The entity to query
|
|
228
|
+
child_name: str
|
|
229
|
+
The child to search for
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
|
|
234
|
+
EntityHandle
|
|
235
|
+
The :class:`EntityHandle` requested
|
|
236
|
+
|
|
237
|
+
Raises
|
|
238
|
+
------
|
|
239
|
+
|
|
240
|
+
NotADirectoryError
|
|
241
|
+
If the requested entity is not a directory
|
|
242
|
+
EntityNotFoundInBlobStorageError
|
|
243
|
+
If the requested entity is not found
|
|
244
|
+
"""
|
|
245
|
+
# deliberately not implemented
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
async def get_parent(self, entity: EntityHandle) -> EntityHandle | None:
|
|
249
|
+
"""
|
|
250
|
+
Return the entity containing in this entity.
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
|
|
255
|
+
entity: EntityHandle
|
|
256
|
+
The entity to query
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
|
|
261
|
+
Optional[EntityHandle]
|
|
262
|
+
The :class:`EntityHandle` requested, or None if the entity is stored at the root of its scope.
|
|
263
|
+
|
|
264
|
+
Raises
|
|
265
|
+
------
|
|
266
|
+
|
|
267
|
+
EntityNotFoundInBlobStorageError
|
|
268
|
+
If the requested entity is not found.
|
|
269
|
+
"""
|
|
270
|
+
# deliberately not implemented
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
async def get_unreferenced_entities(
|
|
274
|
+
self,
|
|
275
|
+
context: str,
|
|
276
|
+
live_handles: list[EntityHandle],
|
|
277
|
+
) -> list[EntityHandle]:
|
|
278
|
+
"""
|
|
279
|
+
Return a list of all entities within the context that are not referenced by the given live entity handles.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
|
|
284
|
+
context: str
|
|
285
|
+
A label for storage scopes that enables them to be categorized and configured given
|
|
286
|
+
a common system wide configuration.
|
|
287
|
+
It defines the bounds over which methods supporting garbage collection are constrained.
|
|
288
|
+
|
|
289
|
+
live_handles: list[EntityHandle]
|
|
290
|
+
A list of handles that are expected to refer to blobs.
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
|
|
295
|
+
list[EntityHandle]
|
|
296
|
+
The list of all entities within the context that are not referenced by the given live entity handles.
|
|
297
|
+
|
|
298
|
+
Raises
|
|
299
|
+
------
|
|
300
|
+
|
|
301
|
+
ValueError
|
|
302
|
+
If the live_handles do not belong to the provided context.
|
|
303
|
+
"""
|
|
304
|
+
# deliberately not implemented
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
async def get_synchronous(self) -> "ansys.bdm.api.istorage_scope.IReadStorageScope":
|
|
308
|
+
"""
|
|
309
|
+
Return a object with a synchronous interface to the same scope.
|
|
310
|
+
"""
|
|
311
|
+
# deliberately not implemented
|
|
312
|
+
...
|
|
313
|
+
|
|
314
|
+
async def _copy_nested_dictionary(
|
|
315
|
+
self,
|
|
316
|
+
nested_dict: RecursiveDictionaryOfEntityHandles,
|
|
317
|
+
current_path: anyio.Path,
|
|
318
|
+
glob: str | None,
|
|
319
|
+
) -> None:
|
|
320
|
+
for key, value in nested_dict.items():
|
|
321
|
+
validate_path_component(key)
|
|
322
|
+
dest_path = current_path / key
|
|
323
|
+
if isinstance(value, EntityHandle):
|
|
324
|
+
if not glob or dest_path.match(glob):
|
|
325
|
+
await dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
326
|
+
await self.get_copy(value, Path(dest_path))
|
|
327
|
+
else:
|
|
328
|
+
if not glob or dest_path.match(glob):
|
|
329
|
+
await dest_path.mkdir(parents=True, exist_ok=True)
|
|
330
|
+
# we cannot stop iterating. even if the directory does not match glob,
|
|
331
|
+
# there may be files within it that do match glob.
|
|
332
|
+
# Don't create the directory if it doesn't match, though, to avoid orphaned directories that would need
|
|
333
|
+
# to be cleaned up later.
|
|
334
|
+
await self._copy_nested_dictionary(value, dest_path, glob)
|
|
335
|
+
|
|
336
|
+
async def get_copy_from_dictionary(
|
|
337
|
+
self,
|
|
338
|
+
root: "PathLike[str]",
|
|
339
|
+
source: RecursiveDictionaryOfEntityHandles,
|
|
340
|
+
glob: str | None = None,
|
|
341
|
+
) -> None:
|
|
342
|
+
"""
|
|
343
|
+
Create a recursive directory structure containing copies of entities from source.
|
|
344
|
+
|
|
345
|
+
This method creates a directory structure on the file system at root containing
|
|
346
|
+
copies of the entities in source. If glob is None, all files at or within source
|
|
347
|
+
are copied to root. Note that file and directory names used when creating the
|
|
348
|
+
directory structure at root are always taken from the keys in source, not from
|
|
349
|
+
the original_name attribute of the EntityHandle objects.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
root : PathLike[str]
|
|
354
|
+
The root directory to create the directory structure in.
|
|
355
|
+
source : RecursiveDictionaryOfEntityHandles
|
|
356
|
+
A recursive nested dictionary of EntityHandle objects.
|
|
357
|
+
glob : str | None, optional
|
|
358
|
+
A glob pattern to filter which files and directories to copy. Behaves
|
|
359
|
+
identically to Path.match() and is interpreted as relative to root using
|
|
360
|
+
the keys in source as the path components. Default is None.
|
|
361
|
+
|
|
362
|
+
Raises
|
|
363
|
+
------
|
|
364
|
+
NotADirectoryError
|
|
365
|
+
If root points to an existing file.
|
|
366
|
+
"""
|
|
367
|
+
abs_dest_path = await anyio.Path(Path(root)).resolve()
|
|
368
|
+
if await abs_dest_path.is_file():
|
|
369
|
+
raise NotADirectoryError("destination path must be a directory")
|
|
370
|
+
if await abs_dest_path.exists():
|
|
371
|
+
await aioshutil.rmtree(abs_dest_path)
|
|
372
|
+
await abs_dest_path.mkdir(parents=True)
|
|
373
|
+
# An implementation must perform matching against source not the file system. Callers expect the minimum number
|
|
374
|
+
# of copies between BDM and root. An implementation which copied the whole of source to root then deleted
|
|
375
|
+
# non-matching entities is not acceptable.
|
|
376
|
+
await self._copy_nested_dictionary(source, abs_dest_path, glob)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class IAsyncStorageScope(IAsyncReadStorageScope, Protocol):
|
|
380
|
+
"""
|
|
381
|
+
Represents access to a Blob Data Management system. Allows consumers
|
|
382
|
+
to produce and consume :class:`EntityHandle` instances.
|
|
383
|
+
|
|
384
|
+
This is the asynchronous version of the :class:`IStorageScope` interface.
|
|
385
|
+
|
|
386
|
+
On dispose, files within the :func:`storage_root` that have not been passed to
|
|
387
|
+
:func:`store()` will be deleted.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
async def __aenter__(self) -> "IAsyncStorageScope":
|
|
391
|
+
"""
|
|
392
|
+
Track the set of files that are stored centrally.
|
|
393
|
+
"""
|
|
394
|
+
# deliberately not implemented
|
|
395
|
+
...
|
|
396
|
+
|
|
397
|
+
async def store(
|
|
398
|
+
self,
|
|
399
|
+
from_: "PathLike[str]",
|
|
400
|
+
mime_type: str | None = None,
|
|
401
|
+
encoding: str | None = None,
|
|
402
|
+
) -> EntityHandle:
|
|
403
|
+
"""
|
|
404
|
+
Create an :class:`EntityHandle` from a file on disk.
|
|
405
|
+
|
|
406
|
+
Many blob handler implementations will try to optimize performance and the file contents
|
|
407
|
+
may not be immediately read from disk. To avoid class 3 type errors, the file MUST NOT be
|
|
408
|
+
externally modified after calling this method.
|
|
409
|
+
|
|
410
|
+
Parameters
|
|
411
|
+
----------
|
|
412
|
+
|
|
413
|
+
from_: PathLike[str]
|
|
414
|
+
The local file on disk which contains the contents for the generated :class:`EntityHandle`
|
|
415
|
+
mime_type: Optional[str]
|
|
416
|
+
Mime type of this file, if known. If None is passed in, the IAsyncStorageScope SHOULD
|
|
417
|
+
use file extension to determine the mime type.
|
|
418
|
+
encoding: Optional[str]
|
|
419
|
+
The Internet Assigned Numbers Authority (IANA) registered encoding name used for textual data.
|
|
420
|
+
This MAY be None if not known and SHOULD NOT be set for binary mime types.
|
|
421
|
+
|
|
422
|
+
Returns
|
|
423
|
+
-------
|
|
424
|
+
An :class:`EntityHandle` that rerpesents the contents of the read file at the moment
|
|
425
|
+
this method is invoked
|
|
426
|
+
"""
|
|
427
|
+
# deliberately not implemented
|
|
428
|
+
...
|
|
429
|
+
|
|
430
|
+
async def store_stream(
|
|
431
|
+
self,
|
|
432
|
+
from_: ByteReceiveStream | bytes,
|
|
433
|
+
relative_location: Path | None = None,
|
|
434
|
+
mime_type: str | None = None,
|
|
435
|
+
encoding: str | None = None,
|
|
436
|
+
) -> EntityHandle:
|
|
437
|
+
"""
|
|
438
|
+
Fully reads a passed in stream and returns a handle for the given content.
|
|
439
|
+
|
|
440
|
+
Parameters
|
|
441
|
+
----------
|
|
442
|
+
|
|
443
|
+
from_ : Union[ByteReceiveStream, bytes]
|
|
444
|
+
The stream or in-memory bytes from which the new entity will be created
|
|
445
|
+
relative_location : Optional[Path]
|
|
446
|
+
The nominal relative path of the entity. The path is relative to the storage_root of
|
|
447
|
+
the scope. No data is expected at this location.
|
|
448
|
+
The filename from this path will be used as the original name of the entity.
|
|
449
|
+
If this parameter is not set then the implementation will generate a unique location.
|
|
450
|
+
mime_type: Optional[str]
|
|
451
|
+
Mime type of this file, if known. If None is passed in, the IAsyncStorageScope SHOULD
|
|
452
|
+
use file extension to determine the mime type.
|
|
453
|
+
encoding: Optional[str]
|
|
454
|
+
The Internet Assigned Numbers Authority (IANA) registered encoding name used for textual data.
|
|
455
|
+
This MAY be None if not known and SHOULD NOT be set for binary mime types.
|
|
456
|
+
|
|
457
|
+
Returns
|
|
458
|
+
-------
|
|
459
|
+
|
|
460
|
+
EntityHandle
|
|
461
|
+
The :class:`EntityHandle` that represents the passed in data.
|
|
462
|
+
"""
|
|
463
|
+
# deliberately not implemented
|
|
464
|
+
...
|
|
465
|
+
|
|
466
|
+
async def begin_store(
|
|
467
|
+
self,
|
|
468
|
+
relative_location: Path | None = None,
|
|
469
|
+
mime_type: str | None = None,
|
|
470
|
+
encoding: str | None = None,
|
|
471
|
+
) -> IAsyncEntityWriter:
|
|
472
|
+
"""
|
|
473
|
+
Create an :class:`EntityWriter` by writing to a stream object.
|
|
474
|
+
|
|
475
|
+
Parameters
|
|
476
|
+
----------
|
|
477
|
+
|
|
478
|
+
relative_location : Optional[Path]
|
|
479
|
+
The nominal relative path of the entity. The path is relative to the storage_root of
|
|
480
|
+
the scope. No data is expected at this location.
|
|
481
|
+
The filename from this path will be used as the original name of the entity.
|
|
482
|
+
If this parameter is not set then the implementation will generate a unique location.
|
|
483
|
+
mime_type: Optional[str]
|
|
484
|
+
Mime type of this file, if known. If None is passed in, the IAsyncStorageScope SHOULD
|
|
485
|
+
use file extension to determine the mime type.
|
|
486
|
+
encoding: Optional[str]
|
|
487
|
+
The Internet Assigned Numbers Authority (IANA) registered encoding name used for textual data.
|
|
488
|
+
This MAY be None if not known and SHOULD NOT be set for binary mime types.
|
|
489
|
+
|
|
490
|
+
Returns
|
|
491
|
+
-------
|
|
492
|
+
|
|
493
|
+
IAsyncEntityWriter
|
|
494
|
+
An object which allows you to write to a stream and retrieve the handle.
|
|
495
|
+
"""
|
|
496
|
+
# deliberately not implemented
|
|
497
|
+
...
|
|
498
|
+
|
|
499
|
+
async def get_storage_root(self) -> Path:
|
|
500
|
+
"""
|
|
501
|
+
A local filesystem directory that can be used to stage files.
|
|
502
|
+
|
|
503
|
+
Since this folder is managed by the BDM implementation, there may be performance
|
|
504
|
+
benefits to storing files here.
|
|
505
|
+
|
|
506
|
+
This directory usually is lazy instantiated on the first call to this function.
|
|
507
|
+
"""
|
|
508
|
+
# deliberately not implemented
|
|
509
|
+
...
|
|
510
|
+
|
|
511
|
+
async def get_stored_entities(self) -> AsyncIterator[EntityHandle]:
|
|
512
|
+
"""
|
|
513
|
+
The set of :class:`EntityHandle` instances that have been stored via this scope.
|
|
514
|
+
"""
|
|
515
|
+
# Note: python requires a yield keyword in a function to actually transform the
|
|
516
|
+
# function's signature to a generator or iterator.
|
|
517
|
+
# So this workaround adds a dummy yield to force the Python interpreter to
|
|
518
|
+
# transform the function's signature to a generator and to please static linters.
|
|
519
|
+
yield NotImplementedType() # pyright: ignore[reportGeneralTypeIssues]
|
|
520
|
+
|
|
521
|
+
async def destroy(self, *entities: EntityHandle) -> None:
|
|
522
|
+
"""
|
|
523
|
+
Delete the entities referenced by the handle.
|
|
524
|
+
|
|
525
|
+
As :class:`EntityHandle` instances are intended to be simple immutable structures
|
|
526
|
+
that can be passed across process and service boundaries, their lifespan must be managed
|
|
527
|
+
by an external system. This call allows the resources associated with a BlobHandle
|
|
528
|
+
to be removed.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
|
|
533
|
+
*entities: EntityHandle
|
|
534
|
+
The entities to delete
|
|
535
|
+
"""
|
|
536
|
+
# deliberately not implemented
|
|
537
|
+
...
|
|
538
|
+
|
|
539
|
+
async def get_synchronous(self) -> "ansys.bdm.api.istorage_scope.IStorageScope":
|
|
540
|
+
"""
|
|
541
|
+
Return an object with a synchronous interface to the same scope.
|
|
542
|
+
"""
|
|
543
|
+
# deliberately not implemented
|
|
544
|
+
...
|
|
545
|
+
|
|
546
|
+
async def store_to_dictionary(
|
|
547
|
+
self,
|
|
548
|
+
root: "PathLike[str]",
|
|
549
|
+
glob: str | None = None,
|
|
550
|
+
) -> RecursiveDictionaryOfEntityHandles:
|
|
551
|
+
"""
|
|
552
|
+
Create a recursive nested dictionary of EntityHandles from a directory.
|
|
553
|
+
|
|
554
|
+
This method creates a recursive nested dictionary containing EntityHandle objects for files and nested
|
|
555
|
+
dictionaries for directories. If glob is None, all files at or within root are included in the result.
|
|
556
|
+
|
|
557
|
+
Parameters
|
|
558
|
+
----------
|
|
559
|
+
root : PathLike[str]
|
|
560
|
+
The root directory to store to a nested dictionary.
|
|
561
|
+
glob : str | None, optional
|
|
562
|
+
A glob pattern to filter which files and directories to include in the result. Behaves identically to
|
|
563
|
+
Path.rglob() and is interpreted as relative to root. Default is None.
|
|
564
|
+
|
|
565
|
+
Returns
|
|
566
|
+
-------
|
|
567
|
+
RecursiveDictionaryOfEntityHandles
|
|
568
|
+
A recursive nested dictionary of EntityHandle objects.
|
|
569
|
+
|
|
570
|
+
Raises
|
|
571
|
+
------
|
|
572
|
+
FileNotFoundError
|
|
573
|
+
If root does not exist.
|
|
574
|
+
NotADirectoryError
|
|
575
|
+
If root is a file.
|
|
576
|
+
NotFoundInLocalStorageRootError
|
|
577
|
+
If root is not within the storage root of storage scope.
|
|
578
|
+
"""
|
|
579
|
+
abs_root_path = await anyio.Path(Path(root)).resolve()
|
|
580
|
+
|
|
581
|
+
storage_root = await self.get_storage_root()
|
|
582
|
+
try:
|
|
583
|
+
abs_root_path.relative_to(storage_root)
|
|
584
|
+
except ValueError as e:
|
|
585
|
+
raise NotFoundInLocalStorageRootError(
|
|
586
|
+
f"root path is not within the storage root. storage_root={storage_root}, root={root}",
|
|
587
|
+
) from e
|
|
588
|
+
|
|
589
|
+
if not await abs_root_path.exists():
|
|
590
|
+
raise FileNotFoundError(f"root path does not exist: {root}")
|
|
591
|
+
|
|
592
|
+
if await abs_root_path.is_file():
|
|
593
|
+
raise NotADirectoryError(f"root path is a file: {root}")
|
|
594
|
+
|
|
595
|
+
result_dict = RecursiveDictionaryOfEntityHandles()
|
|
596
|
+
async for path in abs_root_path.rglob(glob or "*"):
|
|
597
|
+
subdirs = path.parent.relative_to(abs_root_path).parts
|
|
598
|
+
subresult_dict = get_and_create_nested_dict(subdirs, result_dict)
|
|
599
|
+
if await path.is_file():
|
|
600
|
+
subresult_dict[path.name] = await self.store(path)
|
|
601
|
+
else:
|
|
602
|
+
# needed, otherwise empty directories are not created in get_and_create_nested_dict
|
|
603
|
+
subresult_dict[path.name] = RecursiveDictionaryOfEntityHandles()
|
|
604
|
+
|
|
605
|
+
return result_dict
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
from io import RawIOBase
|
|
18
|
+
from types import TracebackType
|
|
19
|
+
from typing import Protocol
|
|
20
|
+
|
|
21
|
+
from ansys.bdm.api.entity_handle import EntityHandle
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class IEntityWriter(Protocol):
|
|
25
|
+
"""
|
|
26
|
+
An object used to create a new entity from a stream of data.
|
|
27
|
+
|
|
28
|
+
Instances are created from :func:`IStorageScope.begin_store()`
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __enter__(self) -> "IEntityWriter":
|
|
32
|
+
"""Start writing data that will comprise an entity."""
|
|
33
|
+
# deliberately not implemented
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
def __exit__(
|
|
37
|
+
self,
|
|
38
|
+
__exc_type: type[BaseException] | None, # noqa: PYI063
|
|
39
|
+
__exc_value: BaseException | None,
|
|
40
|
+
__traceback: TracebackType | None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Finish collecting data and store entity.
|
|
44
|
+
|
|
45
|
+
This method will throw an exception if __enter__ has not been called.
|
|
46
|
+
"""
|
|
47
|
+
# deliberately not implemented
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def stream(self) -> RawIOBase:
|
|
52
|
+
"""
|
|
53
|
+
Return object that can be written to, which will be stored as the entity.
|
|
54
|
+
|
|
55
|
+
This method will throw an exception if __enter__ has not been called.
|
|
56
|
+
|
|
57
|
+
This method will throw an exception if __exit__ has been called.
|
|
58
|
+
"""
|
|
59
|
+
# deliberately not implemented
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def handle(self) -> EntityHandle:
|
|
64
|
+
"""
|
|
65
|
+
Return the entity created by this object.
|
|
66
|
+
|
|
67
|
+
This method will throw an exception if __exit__ has not been called.
|
|
68
|
+
"""
|
|
69
|
+
# deliberately not implemented
|
|
70
|
+
...
|