ansys-mechanical-core 0.11.12__py3-none-any.whl → 0.11.14__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.
Files changed (29) hide show
  1. ansys/mechanical/core/__init__.py +0 -1
  2. ansys/mechanical/core/_version.py +48 -48
  3. ansys/mechanical/core/embedding/app.py +681 -610
  4. ansys/mechanical/core/embedding/background.py +11 -2
  5. ansys/mechanical/core/embedding/enum_importer.py +0 -1
  6. ansys/mechanical/core/embedding/global_importer.py +50 -0
  7. ansys/mechanical/core/embedding/imports.py +30 -58
  8. ansys/mechanical/core/embedding/initializer.py +6 -4
  9. ansys/mechanical/core/embedding/logger/__init__.py +219 -219
  10. ansys/mechanical/core/embedding/messages.py +190 -0
  11. ansys/mechanical/core/embedding/resolver.py +48 -41
  12. ansys/mechanical/core/embedding/rpc/__init__.py +36 -0
  13. ansys/mechanical/core/embedding/rpc/client.py +259 -0
  14. ansys/mechanical/core/embedding/rpc/server.py +403 -0
  15. ansys/mechanical/core/embedding/rpc/utils.py +118 -0
  16. ansys/mechanical/core/embedding/runtime.py +22 -0
  17. ansys/mechanical/core/embedding/transaction.py +51 -0
  18. ansys/mechanical/core/feature_flags.py +1 -0
  19. ansys/mechanical/core/ide_config.py +226 -212
  20. ansys/mechanical/core/mechanical.py +2399 -2324
  21. ansys/mechanical/core/misc.py +176 -176
  22. ansys/mechanical/core/pool.py +712 -712
  23. ansys/mechanical/core/run.py +321 -321
  24. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/METADATA +40 -27
  25. ansys_mechanical_core-0.11.14.dist-info/RECORD +52 -0
  26. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/WHEEL +1 -1
  27. ansys_mechanical_core-0.11.12.dist-info/RECORD +0 -45
  28. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/entry_points.txt +0 -0
  29. {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info/licenses}/LICENSE +0 -0
@@ -76,12 +76,21 @@ class BackgroundApp:
76
76
  """
77
77
  return BackgroundApp.__app
78
78
 
79
- def post(self, callable: typing.Callable):
80
- """Post callable method to the background app thread."""
79
+ def _post(self, callable: typing.Callable, try_post: bool = False):
81
80
  if BackgroundApp.__stopped:
82
81
  raise RuntimeError("Cannot use BackgroundApp after stopping it.")
82
+ if try_post:
83
+ return BackgroundApp.__poster.try_post(callable)
83
84
  return BackgroundApp.__poster.post(callable)
84
85
 
86
+ def post(self, callable: typing.Callable):
87
+ """Post callable method to the background app thread."""
88
+ return self._post(callable)
89
+
90
+ def try_post(self, callable: typing.Callable):
91
+ """Try post callable method to the background app thread."""
92
+ return self._post(callable, try_post=True)
93
+
85
94
  def stop(self) -> None:
86
95
  """Stop the background app thread."""
87
96
  if BackgroundApp.__stopped:
@@ -25,7 +25,6 @@
25
25
  A useful subset of what is imported by
26
26
  Ansys Inc/v{NNN}/ACT/apis/Mechanical.py
27
27
  """
28
-
29
28
  import clr
30
29
 
31
30
  clr.AddReference("Ansys.Mechanical.DataModel")
@@ -0,0 +1,50 @@
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+ """Import Mechanical globals."""
23
+
24
+ from ansys.mechanical.core.embedding.app import is_initialized
25
+
26
+ if not is_initialized():
27
+ raise Exception("Globals cannot be imported until the embedded app is initialized.")
28
+
29
+ import clr
30
+
31
+ clr.AddReference("Ansys.Mechanical.DataModel")
32
+ clr.AddReference("Ansys.ACT.Interfaces")
33
+
34
+
35
+ clr.AddReference("System.Collections")
36
+ clr.AddReference("Ansys.ACT.WB1")
37
+ clr.AddReference("Ansys.Mechanical.DataModel")
38
+
39
+ # from Ansys.ACT.Mechanical import Transaction
40
+ # When ansys-pythonnet issue #14 is fixed, uncomment above
41
+ from Ansys.ACT.Core.Math import Point2D, Point3D # noqa isort: skip
42
+ from Ansys.ACT.Math import Vector3D # noqa isort: skip
43
+ from Ansys.Core.Units import Quantity # noqa isort: skip
44
+ from Ansys.Mechanical.DataModel import MechanicalEnums # noqa isort: skip
45
+ from Ansys.Mechanical.Graphics import Point, SectionPlane # noqa isort: skip
46
+
47
+ from ansys.mechanical.core.embedding.transaction import Transaction # noqa isort: skip
48
+
49
+ import System # noqa isort: skip
50
+ import Ansys # noqa isort: skip
@@ -46,36 +46,37 @@ def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> t
46
46
  To also import all the enums, set the parameter enums to true.
47
47
  """
48
48
  vars = global_entry_points(app)
49
- import clr # isort: skip
50
-
51
- clr.AddReference("System.Collections")
52
- clr.AddReference("Ansys.ACT.WB1")
53
- clr.AddReference("Ansys.Mechanical.DataModel")
54
- # from Ansys.ACT.Mechanical import Transaction
55
- # When ansys-pythonnet issue #14 is fixed, uncomment above
56
- from Ansys.ACT.Core.Math import Point2D, Point3D
57
- from Ansys.ACT.Math import Vector3D
58
- from Ansys.ACT.Mechanical.Fields import VariableDefinitionType
59
- from Ansys.Core.Units import Quantity
60
- from Ansys.Mechanical.DataModel import MechanicalEnums
61
- from Ansys.Mechanical.Graphics import Point, SectionPlane
62
-
63
- import System # isort: skip
64
- import Ansys # isort: skip
65
-
66
- vars["Quantity"] = Quantity
67
- vars["System"] = System
68
- vars["Ansys"] = Ansys
49
+
50
+ from ansys.mechanical.core.embedding.app import is_initialized
51
+ from ansys.mechanical.core.embedding.transaction import Transaction
52
+
69
53
  vars["Transaction"] = Transaction
70
- vars["MechanicalEnums"] = MechanicalEnums
71
- # Graphics
72
- vars["Point"] = Point
73
- vars["SectionPlane"] = SectionPlane
74
- # Math
75
- vars["Point2D"] = Point2D
76
- vars["Point3D"] = Point3D
77
- vars["Vector3D"] = Vector3D
78
- vars["VariableDefinitionType"] = VariableDefinitionType
54
+
55
+ # Import modules if the app is initialized
56
+ if is_initialized():
57
+ from ansys.mechanical.core.embedding.global_importer import (
58
+ Ansys,
59
+ MechanicalEnums,
60
+ Point,
61
+ Point2D,
62
+ Point3D,
63
+ Quantity,
64
+ SectionPlane,
65
+ System,
66
+ Vector3D,
67
+ )
68
+
69
+ vars["Quantity"] = Quantity
70
+ vars["System"] = System
71
+ vars["Ansys"] = Ansys
72
+ vars["MechanicalEnums"] = MechanicalEnums
73
+ # Graphics
74
+ vars["Point"] = Point
75
+ vars["SectionPlane"] = SectionPlane
76
+ # Math
77
+ vars["Point2D"] = Point2D
78
+ vars["Point3D"] = Point3D
79
+ vars["Vector3D"] = Vector3D
79
80
 
80
81
  if enums:
81
82
  vars.update(get_all_enums())
@@ -95,32 +96,3 @@ def get_all_enums() -> typing.Dict[str, typing.Any]:
95
96
  if type(the_enum).__name__ == "CLRMetatype":
96
97
  enums[attr] = the_enum
97
98
  return enums
98
-
99
-
100
- class Transaction: # When ansys-pythonnet issue #14 is fixed, this class will be removed
101
- """
102
- A class to speed up bulk user interactions using Ansys ACT Mechanical Transaction.
103
-
104
- Example
105
- -------
106
- >>> with Transaction() as transaction:
107
- ... pass # Perform bulk user interactions here
108
- ...
109
- """
110
-
111
- def __init__(self):
112
- """Initialize the Transaction class."""
113
- import clr
114
-
115
- clr.AddReference("Ansys.ACT.WB1")
116
- import Ansys
117
-
118
- self._transaction = Ansys.ACT.Mechanical.Transaction()
119
-
120
- def __enter__(self):
121
- """Enter the context of the transaction."""
122
- return self
123
-
124
- def __exit__(self, exc_type, exc_val, exc_tb):
125
- """Exit the context of the transaction and disposes of resources."""
126
- self._transaction.Dispose()
@@ -30,6 +30,7 @@ import warnings
30
30
 
31
31
  from ansys.mechanical.core.embedding.loader import load_clr
32
32
  from ansys.mechanical.core.embedding.resolver import resolve
33
+ from ansys.mechanical.core.mechanical import LOG
33
34
 
34
35
  INITIALIZED_VERSION = None
35
36
  """Constant for the initialized version."""
@@ -85,7 +86,6 @@ def _get_latest_default_version() -> int:
85
86
  If multiple versions are detected, select the latest one, as no specific version is provided.
86
87
  """
87
88
  awp_roots = [value for key, value in os.environ.items() if key.startswith("AWP_ROOT")]
88
-
89
89
  if not awp_roots:
90
90
  raise Exception("No Mechanical installations found.")
91
91
 
@@ -94,13 +94,15 @@ def _get_latest_default_version() -> int:
94
94
  folder = os.path.basename(os.path.normpath(path))
95
95
  version = folder.split("v")[-1]
96
96
  versions_found.append(int(version))
97
+
98
+ LOG.info(f"Available versions of Mechanical: {versions_found}")
99
+
97
100
  latest_version = max(versions_found)
98
101
 
99
102
  if len(awp_roots) > 1:
100
- warnings.warn(
103
+ LOG.warning(
101
104
  f"Multiple versions of Mechanical found! Using latest version {latest_version} ..."
102
105
  )
103
-
104
106
  return latest_version
105
107
 
106
108
 
@@ -159,7 +161,7 @@ def __check_loaded_libs(version: int = None): # pragma: no cover
159
161
  # For 2025 R1, PyMechanical will crash on shutdown if libX11.so is already loaded
160
162
  # before starting Mechanical
161
163
  if __is_lib_loaded("libX11.so"):
162
- warnings.warn(
164
+ LOG.warning(
163
165
  "libX11.so is loaded prior to initializing the Embedded Instance of Mechanical.\
164
166
  Python will crash on shutdown..."
165
167
  )
@@ -1,219 +1,219 @@
1
- # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
- # SPDX-License-Identifier: MIT
3
- #
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
22
-
23
- """Embedding logger.
24
-
25
- Module to interact with the built-in logging system of Mechanical.
26
-
27
- Usage
28
- -----
29
-
30
- Configuring logger
31
- ~~~~~~~~~~~~~~~~~~
32
-
33
- Configuring the logger can be done using the :class:`Configuration <ansys.mechanical.core.embedding.logger.Configuration>` class:
34
-
35
- .. code:: python
36
- import ansys.mechanical.core as mech
37
- from ansys.mechanical.core.embedding.logger import Configuration, Logger
38
-
39
- Configuration.configure(level=logging.INFO, to_stdout=True, base_directory=None)
40
- app = mech.App(version=251)
41
-
42
- Then, the :class:`Logger <ansys.mechanical.core.embedding.logger.Logger>` class can be used to write messages to the log:
43
-
44
- .. code:: python
45
-
46
- Logger.error("message")
47
-
48
-
49
- """
50
-
51
- import logging
52
- import os
53
- import typing
54
-
55
- from ansys.mechanical.core.embedding import initializer
56
- from ansys.mechanical.core.embedding.logger import environ, linux_api, sinks, windows_api
57
-
58
- LOGGING_SINKS: typing.Set[int] = set()
59
- """Constant for logging sinks."""
60
-
61
- LOGGING_CONTEXT: str = "PYMECHANICAL"
62
- """Constant for logging context."""
63
-
64
-
65
- def _get_backend() -> (
66
- typing.Union[windows_api.APIBackend, linux_api.APIBackend, environ.EnvironBackend]
67
- ):
68
- """Get the appropriate logger backend.
69
-
70
- Before embedding is initialized, logging is configured via environment variables.
71
- After embedding is initialized, logging is configured by making API calls into the
72
- Mechanical logging system.
73
-
74
- However, the API is mostly the same in both cases, though some methods only work
75
- in one of the two backends.
76
-
77
- Setting the base directory only works before initializing.
78
- Actually logging a message or flushing the log only works after initializing.
79
- """
80
- # TODO - use abc instead of a union type?
81
- embedding_initialized = initializer.INITIALIZED_VERSION is not None
82
- if not embedding_initialized:
83
- return environ.EnvironBackend()
84
- if os.name == "nt":
85
- return windows_api.APIBackend()
86
- return linux_api.APIBackend()
87
-
88
-
89
- class Configuration:
90
- """Configures logger for Mechanical embedding."""
91
-
92
- @classmethod
93
- def configure(cls, level=logging.WARNING, directory=None, base_directory=None, to_stdout=True):
94
- """Configure the logger for PyMechanical embedding.
95
-
96
- Parameters
97
- ----------
98
- level : int, optional
99
- Level of logging that is defined in the ``logging`` package. The default is 'DEBUG'.
100
- Options are ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, and ``"ERROR"``.
101
- directory : str, optional
102
- Directory to write log file to. The default is ``None``, but by default the log
103
- will appear somewhere in the system temp folder.
104
- base_directory: str, optional
105
- Base directory to write log files to. Each instance of Mechanical will write its
106
- log to a time-stamped subfolder within this directory. This is only possible to set
107
- before Mechanical is initialized.
108
- to_stdout : bool, optional
109
- Whether to write log messages to the standard output, which is the
110
- command line. The default is ``True``.
111
- """
112
- # Set up the global log configuration.
113
- cls.set_log_directory(directory)
114
- cls.set_log_base_directory(base_directory)
115
-
116
- # Set up the sink-specific log configuration and store to global state.
117
- cls._store_stdout_sink_enabled(to_stdout)
118
- file_sink_enabled = directory is not None or base_directory is not None
119
- cls._store_file_sink_enabled(file_sink_enabled)
120
-
121
- # Commit the sink-specific log configuration global state to the backend.
122
- cls._commit_enabled_configuration()
123
- cls.set_log_level(level)
124
-
125
- @classmethod
126
- def set_log_to_stdout(cls, value: bool) -> None:
127
- """Configure logging to write to the standard output."""
128
- cls._store_stdout_sink_enabled(value)
129
- cls._commit_enabled_configuration()
130
-
131
- @classmethod
132
- def set_log_to_file(cls, value: bool) -> None:
133
- """Configure logging to write to a file."""
134
- cls._store_file_sink_enabled(value)
135
- cls._commit_enabled_configuration()
136
-
137
- @classmethod
138
- def set_log_level(cls, level: int) -> None:
139
- """Set the log level for all configured sinks."""
140
- if len(LOGGING_SINKS) == 0:
141
- raise Exception("No logging backend configured!")
142
- cls._commit_level_configuration(level)
143
-
144
- @classmethod
145
- def set_log_directory(cls, value: str) -> None:
146
- """Configure logging to write to a directory."""
147
- if value is None:
148
- return
149
- _get_backend().set_directory(value)
150
-
151
- @classmethod
152
- def set_log_base_directory(cls, directory: str) -> None:
153
- """Configure logging to write in a time-stamped subfolder in this directory."""
154
- if directory is None:
155
- return
156
- _get_backend().set_base_directory(directory)
157
-
158
- @classmethod
159
- def _commit_level_configuration(cls, level: int) -> None:
160
- for sink in LOGGING_SINKS:
161
- _get_backend().set_log_level(level, sink)
162
-
163
- @classmethod
164
- def _commit_enabled_configuration(cls) -> None:
165
- for sink in LOGGING_SINKS:
166
- _get_backend().enable(sink)
167
-
168
- @classmethod
169
- def _store_stdout_sink_enabled(cls, value: bool) -> None:
170
- if value:
171
- LOGGING_SINKS.add(sinks.StandardSinks.CONSOLE)
172
- else:
173
- LOGGING_SINKS.discard(sinks.StandardSinks.CONSOLE)
174
-
175
- @classmethod
176
- def _store_file_sink_enabled(cls, value: bool) -> None:
177
- if value:
178
- LOGGING_SINKS.add(sinks.StandardSinks.STANDARD_LOG_FILE)
179
- else:
180
- LOGGING_SINKS.discard(sinks.StandardSinks.STANDARD_LOG_FILE)
181
-
182
-
183
- class Logger:
184
- """Provides the ``Logger`` class for embedding."""
185
-
186
- @classmethod
187
- def flush(cls):
188
- """Flush the log."""
189
- _get_backend().flush()
190
-
191
- @classmethod
192
- def can_log_message(cls, level: int) -> bool:
193
- """Get whether a message at this level is logged."""
194
- return _get_backend().can_log_message(level)
195
-
196
- @classmethod
197
- def debug(cls, msg: str):
198
- """Write a debug message to the log."""
199
- _get_backend().log_message(logging.DEBUG, LOGGING_CONTEXT, msg)
200
-
201
- @classmethod
202
- def error(cls, msg: str):
203
- """Write a error message to the log."""
204
- _get_backend().log_message(logging.ERROR, LOGGING_CONTEXT, msg)
205
-
206
- @classmethod
207
- def info(cls, msg: str):
208
- """Write an info message to the log."""
209
- _get_backend().log_message(logging.INFO, LOGGING_CONTEXT, msg)
210
-
211
- @classmethod
212
- def warning(cls, msg: str):
213
- """Write a warning message to the log."""
214
- _get_backend().log_message(logging.WARNING, LOGGING_CONTEXT, msg)
215
-
216
- @classmethod
217
- def fatal(cls, msg: str):
218
- """Write a fatal message to the log."""
219
- _get_backend().log_message(logging.FATAL, LOGGING_CONTEXT, msg)
1
+ # Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
2
+ # SPDX-License-Identifier: MIT
3
+ #
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ """Embedding logger.
24
+
25
+ Module to interact with the built-in logging system of Mechanical.
26
+
27
+ Usage
28
+ -----
29
+
30
+ Configuring logger
31
+ ~~~~~~~~~~~~~~~~~~
32
+
33
+ Configuring the logger can be done using the :class:`Configuration <ansys.mechanical.core.embedding.logger.Configuration>` class:
34
+
35
+ .. code:: python
36
+ import ansys.mechanical.core as mech
37
+ from ansys.mechanical.core.embedding.logger import Configuration, Logger
38
+
39
+ Configuration.configure(level=logging.INFO, to_stdout=True, base_directory=None)
40
+ app = mech.App(version=251)
41
+
42
+ Then, the :class:`Logger <ansys.mechanical.core.embedding.logger.Logger>` class can be used to write messages to the log:
43
+
44
+ .. code:: python
45
+
46
+ Logger.error("message")
47
+
48
+
49
+ """
50
+
51
+ import logging
52
+ import os
53
+ import typing
54
+
55
+ from ansys.mechanical.core.embedding import initializer
56
+ from ansys.mechanical.core.embedding.logger import environ, linux_api, sinks, windows_api
57
+
58
+ LOGGING_SINKS: typing.Set[int] = set()
59
+ """Constant for logging sinks."""
60
+
61
+ LOGGING_CONTEXT: str = "PYMECHANICAL"
62
+ """Constant for logging context."""
63
+
64
+
65
+ def _get_backend() -> (
66
+ typing.Union[windows_api.APIBackend, linux_api.APIBackend, environ.EnvironBackend]
67
+ ):
68
+ """Get the appropriate logger backend.
69
+
70
+ Before embedding is initialized, logging is configured via environment variables.
71
+ After embedding is initialized, logging is configured by making API calls into the
72
+ Mechanical logging system.
73
+
74
+ However, the API is mostly the same in both cases, though some methods only work
75
+ in one of the two backends.
76
+
77
+ Setting the base directory only works before initializing.
78
+ Actually logging a message or flushing the log only works after initializing.
79
+ """
80
+ # TODO - use abc instead of a union type?
81
+ embedding_initialized = initializer.INITIALIZED_VERSION is not None
82
+ if not embedding_initialized:
83
+ return environ.EnvironBackend()
84
+ if os.name == "nt":
85
+ return windows_api.APIBackend()
86
+ return linux_api.APIBackend()
87
+
88
+
89
+ class Configuration:
90
+ """Configures logger for Mechanical embedding."""
91
+
92
+ @classmethod
93
+ def configure(cls, level=logging.WARNING, directory=None, base_directory=None, to_stdout=True):
94
+ """Configure the logger for PyMechanical embedding.
95
+
96
+ Parameters
97
+ ----------
98
+ level : int, optional
99
+ Level of logging that is defined in the ``logging`` package. The default is 'DEBUG'.
100
+ Options are ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, and ``"ERROR"``.
101
+ directory : str, optional
102
+ Directory to write log file to. The default is ``None``, but by default the log
103
+ will appear somewhere in the system temp folder.
104
+ base_directory: str, optional
105
+ Base directory to write log files to. Each instance of Mechanical will write its
106
+ log to a time-stamped subfolder within this directory. This is only possible to set
107
+ before Mechanical is initialized.
108
+ to_stdout : bool, optional
109
+ Whether to write log messages to the standard output, which is the
110
+ command line. The default is ``True``.
111
+ """
112
+ # Set up the global log configuration.
113
+ cls.set_log_directory(directory)
114
+ cls.set_log_base_directory(base_directory)
115
+
116
+ # Set up the sink-specific log configuration and store to global state.
117
+ cls._store_stdout_sink_enabled(to_stdout)
118
+ file_sink_enabled = directory is not None or base_directory is not None
119
+ cls._store_file_sink_enabled(file_sink_enabled)
120
+
121
+ # Commit the sink-specific log configuration global state to the backend.
122
+ cls._commit_enabled_configuration()
123
+ cls.set_log_level(level)
124
+
125
+ @classmethod
126
+ def set_log_to_stdout(cls, value: bool) -> None:
127
+ """Configure logging to write to the standard output."""
128
+ cls._store_stdout_sink_enabled(value)
129
+ cls._commit_enabled_configuration()
130
+
131
+ @classmethod
132
+ def set_log_to_file(cls, value: bool) -> None:
133
+ """Configure logging to write to a file."""
134
+ cls._store_file_sink_enabled(value)
135
+ cls._commit_enabled_configuration()
136
+
137
+ @classmethod
138
+ def set_log_level(cls, level: int) -> None:
139
+ """Set the log level for all configured sinks."""
140
+ if len(LOGGING_SINKS) == 0:
141
+ raise Exception("No logging backend configured!")
142
+ cls._commit_level_configuration(level)
143
+
144
+ @classmethod
145
+ def set_log_directory(cls, value: str) -> None:
146
+ """Configure logging to write to a directory."""
147
+ if value is None:
148
+ return
149
+ _get_backend().set_directory(value)
150
+
151
+ @classmethod
152
+ def set_log_base_directory(cls, directory: str) -> None:
153
+ """Configure logging to write in a time-stamped subfolder in this directory."""
154
+ if directory is None:
155
+ return
156
+ _get_backend().set_base_directory(directory)
157
+
158
+ @classmethod
159
+ def _commit_level_configuration(cls, level: int) -> None:
160
+ for sink in LOGGING_SINKS:
161
+ _get_backend().set_log_level(level, sink)
162
+
163
+ @classmethod
164
+ def _commit_enabled_configuration(cls) -> None:
165
+ for sink in LOGGING_SINKS:
166
+ _get_backend().enable(sink)
167
+
168
+ @classmethod
169
+ def _store_stdout_sink_enabled(cls, value: bool) -> None:
170
+ if value:
171
+ LOGGING_SINKS.add(sinks.StandardSinks.CONSOLE)
172
+ else:
173
+ LOGGING_SINKS.discard(sinks.StandardSinks.CONSOLE)
174
+
175
+ @classmethod
176
+ def _store_file_sink_enabled(cls, value: bool) -> None:
177
+ if value:
178
+ LOGGING_SINKS.add(sinks.StandardSinks.STANDARD_LOG_FILE)
179
+ else:
180
+ LOGGING_SINKS.discard(sinks.StandardSinks.STANDARD_LOG_FILE)
181
+
182
+
183
+ class Logger:
184
+ """Provides the ``Logger`` class for embedding."""
185
+
186
+ @classmethod
187
+ def flush(cls):
188
+ """Flush the log."""
189
+ _get_backend().flush()
190
+
191
+ @classmethod
192
+ def can_log_message(cls, level: int) -> bool:
193
+ """Get whether a message at this level is logged."""
194
+ return _get_backend().can_log_message(level)
195
+
196
+ @classmethod
197
+ def debug(cls, msg: str):
198
+ """Write a debug message to the log."""
199
+ _get_backend().log_message(logging.DEBUG, LOGGING_CONTEXT, msg)
200
+
201
+ @classmethod
202
+ def error(cls, msg: str):
203
+ """Write a error message to the log."""
204
+ _get_backend().log_message(logging.ERROR, LOGGING_CONTEXT, msg)
205
+
206
+ @classmethod
207
+ def info(cls, msg: str):
208
+ """Write an info message to the log."""
209
+ _get_backend().log_message(logging.INFO, LOGGING_CONTEXT, msg)
210
+
211
+ @classmethod
212
+ def warning(cls, msg: str):
213
+ """Write a warning message to the log."""
214
+ _get_backend().log_message(logging.WARNING, LOGGING_CONTEXT, msg)
215
+
216
+ @classmethod
217
+ def fatal(cls, msg: str):
218
+ """Write a fatal message to the log."""
219
+ _get_backend().log_message(logging.FATAL, LOGGING_CONTEXT, msg)