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.
@@ -0,0 +1,135 @@
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 typing import Protocol
18
+
19
+ from ansys.bdm.api.iasync_storage_scope import (
20
+ IAsyncReadStorageScope,
21
+ IAsyncStorageScope,
22
+ )
23
+ from ansys.bdm.api.istorage_scope import IReadStorageScope, IStorageScope
24
+
25
+
26
+ class IReadStorageScopeFactory(Protocol):
27
+ """
28
+ Factory pattern for creating instances of IReadStorageScope and IAsyncReadStorageScope.
29
+ """
30
+
31
+ def create_storage_scope(
32
+ self,
33
+ context: str,
34
+ template_vars: dict[str, str],
35
+ ) -> IReadStorageScope:
36
+ """
37
+ Create a new read only storage scope.
38
+
39
+ Parameters
40
+ ----------
41
+ context: str
42
+ TODO: How to word what this genericaly does.
43
+ template_vars: Dict[str, str]
44
+ Template variables that may be used by the configuration system \
45
+ to return an appropriate :class:`IStorageScope`. TODO: Define whether certain fields \
46
+ need to be passed in?
47
+
48
+ Returns
49
+ -------
50
+ IReadStorageScope
51
+ A configured and ready to use :class:`IReadStorageScope`
52
+ """
53
+ # deliberately not implemented
54
+ ...
55
+
56
+ async def create_async_storage_scope(
57
+ self,
58
+ context: str,
59
+ template_vars: dict[str, str],
60
+ ) -> IAsyncReadStorageScope:
61
+ """
62
+ Create a new asynchronous read only storage scope.
63
+
64
+ Parameters
65
+ ----------
66
+ context: str
67
+ TODO: How to word what this genericaly does.
68
+ template_vars: Dict[str, str]
69
+ Template variables that may be used by the configuration system \
70
+ to return an appropriate :class:`IAsyncStorageScope`. TODO: Define whether certain fields \
71
+ need to be passed in?
72
+
73
+ Returns
74
+ -------
75
+ IAsyncReadStorageScope
76
+ A configured and ready to use :class:`IAsyncReadStorageScope`
77
+ """
78
+ # deliberately not implemented
79
+ ...
80
+
81
+
82
+ class IStorageScopeFactory(IReadStorageScopeFactory, Protocol):
83
+ """
84
+ Factory pattern for creating instances of IStorageScope and IAsyncStorageScope.
85
+ """
86
+
87
+ def create_storage_scope(
88
+ self,
89
+ context: str,
90
+ template_vars: dict[str, str],
91
+ ) -> IStorageScope:
92
+ """
93
+ Create a new storage scope.
94
+
95
+ Parameters
96
+ ----------
97
+ context: str
98
+ TODO: How to word what this genericaly does.
99
+ template_vars: Dict[str, str]
100
+ Template variables that may be used by the configuration system \
101
+ to return an appropriate :class:`IStorageScope`. TODO: Define whether certain fields \
102
+ need to be passed in?
103
+
104
+ Returns
105
+ -------
106
+ IStorageScope
107
+ A configured and ready to use :class:`IStorageScope`
108
+ """
109
+ # deliberately not implemented
110
+ ...
111
+
112
+ async def create_async_storage_scope(
113
+ self,
114
+ context: str,
115
+ template_vars: dict[str, str],
116
+ ) -> IAsyncStorageScope:
117
+ """
118
+ Create a new asynchronous storage scope.
119
+
120
+ Parameters
121
+ ----------
122
+ context: str
123
+ TODO: How to word what this genericaly does.
124
+ template_vars: Dict[str, str]
125
+ Template variables that may be used by the configuration system \
126
+ to return an appropriate :class:`IAsyncStorageScope`. TODO: Define whether certain fields \
127
+ need to be passed in?
128
+
129
+ Returns
130
+ -------
131
+ IAsyncStorageScope
132
+ A configured and ready to use :class:`IAsyncStorageScope`
133
+ """
134
+ # deliberately not implemented
135
+ ...
ansys/bdm/api/py.typed ADDED
File without changes
@@ -0,0 +1,132 @@
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
+ import re
18
+ from typing import Any
19
+
20
+ from pydantic import GetCoreSchemaHandler
21
+ from pydantic_core import CoreSchema, core_schema
22
+
23
+ from ansys.bdm.api.entity_handle import EntityHandle
24
+
25
+
26
+ class RecursiveDictionaryOfEntityHandles(
27
+ dict[str, "EntityHandle | RecursiveDictionaryOfEntityHandles"],
28
+ ):
29
+ """A recursive dictionary structure for storing entity handles in a nested hierarchy that maps a filesystem-like
30
+ directory structure."""
31
+
32
+ # Defining it as a class instead of a type, in case in the future we want to add methods that operate on the
33
+ # recursive dict (e.g., filter) and don't require the storage scope.
34
+
35
+ @classmethod
36
+ def __get_pydantic_core_schema__(
37
+ cls,
38
+ source_type: Any,
39
+ handler: GetCoreSchemaHandler,
40
+ ) -> CoreSchema:
41
+ """Generate Pydantic core schema to be able to serialize/deserialize this structure with Pydantic.
42
+
43
+ We cannot use a simple:
44
+ > return core_schema.no_info_after_validator_function(cls, handler(dict))
45
+ because it would not instantiate the nested EntityHandles, leaving them as plain dictionaries.
46
+ """
47
+ entity_handle_schema = handler.generate_schema(EntityHandle)
48
+
49
+ dict_schema = core_schema.dict_schema(
50
+ keys_schema=core_schema.str_schema(),
51
+ values_schema=core_schema.union_schema(
52
+ [
53
+ entity_handle_schema,
54
+ core_schema.definition_reference_schema(
55
+ "RecursiveDictionaryOfEntityHandles",
56
+ ),
57
+ ],
58
+ ),
59
+ )
60
+
61
+ validated_schema = core_schema.no_info_after_validator_function(
62
+ cls,
63
+ dict_schema,
64
+ )
65
+
66
+ return core_schema.definitions_schema(
67
+ schema=validated_schema,
68
+ definitions=[
69
+ core_schema.dict_schema(
70
+ keys_schema=core_schema.str_schema(),
71
+ values_schema=core_schema.union_schema(
72
+ [
73
+ entity_handle_schema,
74
+ core_schema.definition_reference_schema(
75
+ "RecursiveDictionaryOfEntityHandles",
76
+ ),
77
+ ],
78
+ ),
79
+ ref="RecursiveDictionaryOfEntityHandles",
80
+ ),
81
+ ],
82
+ )
83
+
84
+
85
+ def get_and_create_nested_dict(
86
+ parts: tuple[str, ...],
87
+ value: RecursiveDictionaryOfEntityHandles,
88
+ ) -> RecursiveDictionaryOfEntityHandles:
89
+ if not parts:
90
+ return value
91
+ if parts[0] not in value:
92
+ value[parts[0]] = RecursiveDictionaryOfEntityHandles()
93
+
94
+ # Ensure the value at parts[0] is a RecursiveDictionaryOfEntityHandles
95
+ nested_value = value[parts[0]]
96
+ if not isinstance(nested_value, RecursiveDictionaryOfEntityHandles):
97
+ # If it's an EntityHandle, we need to convert it to a nested dict
98
+ # This shouldn't normally happen in correct usage, but we handle it for type safety
99
+ nested_value = RecursiveDictionaryOfEntityHandles()
100
+ value[parts[0]] = nested_value
101
+
102
+ return get_and_create_nested_dict(
103
+ parts[1:],
104
+ nested_value,
105
+ )
106
+
107
+
108
+ def validate_path_component(name: str) -> None:
109
+ """Validate that a string is suitable for use as a file or directory name."""
110
+ # Would be great if we could validate at assign or construction time, e.g.,:
111
+ # > my_dict['invalid/string'] = my_entity
112
+ # or
113
+ # > my_dict = RecursiveDictionaryOfEntityHandles({'invalid/string': my_entity})
114
+ # but given the definition of RecursiveDictionaryOfEntityHandles, we are left with validation only within
115
+ # get_copy_from_dictionary()
116
+
117
+ if not name or not name.strip():
118
+ raise ValueError("Path component cannot be empty")
119
+ if name in (".", ".."): # special names for current and parent directory
120
+ raise ValueError(f"Path component cannot be '{name}'")
121
+ if name.endswith((".", " ")): # Windows rule
122
+ raise ValueError(f"Path component '{name}' cannot end with a dot or space")
123
+
124
+ # Check for invalid characters
125
+ # - Windows: < > : " / \\ | ? * and control characters (0-31)
126
+ # - Linux: / and null character
127
+ invalid_chars = r'[<>:"/\\|?*\x00-\x1f]'
128
+ if re.search(invalid_chars, name):
129
+ raise ValueError(
130
+ f"Path component '{name}' contains invalid characters. "
131
+ f'The following characters are not allowed: < > : " / \\ | ? * and control characters',
132
+ )
@@ -0,0 +1,45 @@
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
+
18
+ class NotFoundInLocalStorageRootError(Exception):
19
+ """Exception raised when attempting to access object that doesn't exist within the local storage root."""
20
+
21
+
22
+ class EntityNotFoundInBlobStorageError(Exception):
23
+ """Exception raised when attempting to access entity using handle where the referenced entity does not exist."""
24
+
25
+
26
+ class CannotGenerateStreamForDirectoryError(Exception):
27
+ """Exception raised when attempting to generate a stream for a directory entity."""
28
+
29
+
30
+ class EntityWriterHasNotCompletedWritingDataError(Exception):
31
+ """Exception raised when attempting to get handle from EntityWriter before the writing context is closed."""
32
+
33
+
34
+ class EntityWriterIsNotWritingDataError(Exception):
35
+ """Exception raised when attempting to access stream from EntityWriter
36
+ after the writing context is closed or before it is opened."""
37
+
38
+
39
+ class EntityWriterHasWrittenDataError(Exception):
40
+ """Exception raised when attempting to write to stream from EntityWriter
41
+ after the writing context is closed."""
42
+
43
+
44
+ class InvalidContextError(Exception):
45
+ """Exception raised when unknown context when creating a scope."""
@@ -0,0 +1,15 @@
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.
@@ -0,0 +1,92 @@
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 anyio import EndOfStream
18
+
19
+ from ansys.bdm.api.entity_handle import EntityHandle
20
+ from ansys.bdm.api.iasync_storage_scope import IAsyncStorageScope
21
+ from ansys.bdm.base.encoder import encode_text
22
+
23
+
24
+ class AsyncStorageScopeBase(IAsyncStorageScope):
25
+ """
26
+ A partial implementation of ``IAsyncStorageScope`` that provides implementations
27
+ of some methods based on other more fundamental methods. ``AsyncStorageScopeBase``
28
+ can be used as a base class for implementations of ``IAsyncStorageScope``.
29
+
30
+ """
31
+
32
+ async def get_bytes(self, entity: EntityHandle) -> bytes:
33
+ """
34
+ Return the content of the referenced blob.
35
+
36
+ Parameters
37
+ ----------
38
+ entity: EntityHandle
39
+ The handle to the data to realize
40
+
41
+ Returns
42
+ -------
43
+ bytes
44
+ the contents of the EntityHandle.
45
+
46
+ Raises
47
+ ------
48
+
49
+ CannotGenerateStreamForDirectoryError
50
+ If the entity requested is a collection
51
+ """
52
+ stream = await self.get_stream(entity)
53
+ result = bytearray()
54
+ while True:
55
+ try:
56
+ data = await stream.receive()
57
+ result.extend(data)
58
+ except EndOfStream:
59
+ break
60
+ return bytes(result)
61
+
62
+ async def get_text(self, entity: EntityHandle, encoding: str | None = None) -> str:
63
+ """
64
+ Return the content of the referenced blob.
65
+
66
+ Parameters
67
+ ----------
68
+ entity: EntityHandle
69
+ The handle to the data to realize
70
+ encoding: Optional[str]
71
+ The name of an encoding. When this argument is not None the bytes
72
+ of ``entity`` will be read using that encoding.
73
+
74
+ Returns
75
+ -------
76
+ str
77
+ the contents of the blob referenced by ``entity`` in text form with the encoding determined
78
+ in order of preference:
79
+ - the encoding argument if not None
80
+ - the encoding field of the entity argument if set
81
+ - the BOM of the referenced entity if it contains one; or
82
+ - UTF-8
83
+
84
+ Raises
85
+ ------
86
+
87
+ CannotGenerateStreamForDirectoryError
88
+ If the entity requested is a collection
89
+ """
90
+
91
+ data = await self.get_bytes(entity)
92
+ return encode_text(data, entity, encoding)
@@ -0,0 +1,80 @@
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 ansys.bdm.api.entity_handle import EntityHandle
18
+ from ansys.bdm.api.istorage_scope import IStorageScope
19
+ from ansys.bdm.base.encoder import encode_text
20
+
21
+
22
+ class StorageScopeBase(IStorageScope):
23
+ """
24
+ A partial implementation of ``IStorageScope`` that provides implementations
25
+ of some methods based on other more fundamental methods. ``StorageScopeBase``
26
+ can be used as a base class for implementations of ``IStorageScope``.
27
+
28
+ """
29
+
30
+ def get_bytes(self, entity: EntityHandle) -> bytes:
31
+ """
32
+ Return the content of the referenced blob.
33
+
34
+ Parameters
35
+ ----------
36
+ entity: EntityHandle
37
+ The handle to the data to realize
38
+
39
+ Returns
40
+ -------
41
+ bytes
42
+ the contents of the EntityHandle.
43
+
44
+ Raises
45
+ ------
46
+
47
+ CannotGenerateStreamForDirectoryError
48
+ If the entity requested is a collection
49
+ """
50
+ return self.get_stream(entity).readall()
51
+
52
+ def get_text(self, entity: EntityHandle, encoding: str | None = None) -> str:
53
+ """
54
+ Return the content of the referenced blob.
55
+
56
+ Parameters
57
+ ----------
58
+ entity: EntityHandle
59
+ The handle to the data to realize
60
+ encoding: Optional[str]
61
+ The name of an encoding. When this argument is not None the bytes
62
+ of ``entity`` will be read using that encoding.
63
+
64
+ Returns
65
+ -------
66
+ str
67
+ the contents of the EntityHandle in text form with the encoding determined
68
+ in order of preference:
69
+ - the encoding argument if not None
70
+ - the encoding field of the entity argument if set
71
+ - the BOM of the referenced entity if it contains one; or
72
+ - UTF-8
73
+
74
+ Raises
75
+ ------
76
+
77
+ CannotGenerateStreamForDirectoryError
78
+ If the entity requested is a collection
79
+ """
80
+ return encode_text(self.get_bytes(entity), entity, encoding)
@@ -0,0 +1,89 @@
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 ansys.bdm.api.entity_handle import EntityHandle
18
+
19
+
20
+ def decode_bom(bom: bytes) -> tuple[str | None, int]:
21
+ """
22
+ Return a tuple consisting of
23
+ 1) the name of the encoding stored in the BOM prefix of the given byte data; and
24
+ 2) the length of BOM in bytes.
25
+
26
+ If the byte data does not contain a BOM then a tuple ``(None, 0)`` is returned.
27
+
28
+ Parameters
29
+ ----------
30
+ bom: bytes
31
+ byte data that may or may not be prefixed with a BOM
32
+
33
+ Returns
34
+ -------
35
+ Tuple[Optional[str], int]
36
+ a tuple consisting of
37
+ 1) the name of the encoding stored in the BOM prefix of the given byte data; and
38
+ 2) the length of BOM in bytes.
39
+
40
+ If the byte data does not contain a BOM then a tuple ``(None, 0)`` is returned.
41
+ """
42
+ if bom[0] == 0xEF and bom[1] == 0xBB and bom[2] == 0xBF:
43
+ return ("utf-8", 3)
44
+ if bom[0] == 0xFF and bom[1] == 0xFE and bom[2] == 0 and bom[3] == 0:
45
+ return ("utf-32-le", 4)
46
+ if bom[0] == 0xFF and bom[1] == 0xFE:
47
+ return ("utf-16-le", 2)
48
+ if bom[0] == 0xFE and bom[1] == 0xFF:
49
+ return ("utf-16-be", 2)
50
+ if bom[0] == 0 and bom[1] == 0 and bom[2] == 0xFE and bom[3] == 0xFF:
51
+ return ("utf-32-be", 4)
52
+ return (None, 0)
53
+
54
+
55
+ def encode_text(data: bytes, handle: EntityHandle, encoding: str | None = None) -> str:
56
+ """
57
+ Return a string which is derived from the given byte data
58
+
59
+ Parameters
60
+ ----------
61
+ data: bytes
62
+ byte data that may or may not be prefixed with a BOM that provides the data for the returned string
63
+ handle: EntityHandle
64
+ the entity handle that refers to the object that contains the given data
65
+ encoding: Optional[str]
66
+ The name of an encoding. When this argument is not None the returned
67
+ string will be decoded using the given encoding.
68
+
69
+ Returns
70
+ -------
71
+ str
72
+ a string containing the byte data with the encoding determined
73
+ in order of preference by:
74
+ - the encoding argument if not None
75
+ - the encoding field of the entity argument if set
76
+ - the BOM of the referenced entity if it contains one; or
77
+ - UTF-8
78
+ """
79
+ if encoding is None:
80
+ if handle.encoding is not None:
81
+ encoding = handle.encoding
82
+ else:
83
+ encoding, bom_length = decode_bom(data)
84
+
85
+ if encoding is not None:
86
+ data = data[bom_length:]
87
+ else:
88
+ encoding = "utf-8"
89
+ return str(data, encoding=encoding)
File without changes