orchid-python-api 5.25.3__py3-none-any.whl → 5.25.4__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 (57) hide show
  1. orchid_python_api/examples/search_data_frames.py +1 -1
  2. {orchid_python_api-5.25.3.dist-info → orchid_python_api-5.25.4.dist-info}/METADATA +4 -3
  3. orchid_python_api-5.25.4.dist-info/RECORD +38 -0
  4. {orchid_python_api-5.25.3.dist-info → orchid_python_api-5.25.4.dist-info}/WHEEL +1 -1
  5. ReleaseNotes.md +0 -730
  6. copy_orchid_examples.py +0 -88
  7. copy_orchid_low_level_examples.py +0 -93
  8. copy_orchid_manual_examples.py +0 -93
  9. copy_orchid_tutorials.py +0 -88
  10. orchid/VERSION +0 -1
  11. orchid/__init__.py +0 -42
  12. orchid/__version__.py +0 -18
  13. orchid/base.py +0 -31
  14. orchid/base_time_series_adapter.py +0 -91
  15. orchid/configuration.py +0 -162
  16. orchid/convert.py +0 -44
  17. orchid/core.py +0 -149
  18. orchid/dom_project_object.py +0 -28
  19. orchid/dot_net.py +0 -68
  20. orchid/dot_net_disposable.py +0 -64
  21. orchid/dot_net_dom_access.py +0 -241
  22. orchid/measurement.py +0 -35
  23. orchid/native_data_frame_adapter.py +0 -247
  24. orchid/native_fiber_data.py +0 -73
  25. orchid/native_fiber_data_set_info.py +0 -28
  26. orchid/native_monitor_adapter.py +0 -67
  27. orchid/native_project_user_data_adapter.py +0 -137
  28. orchid/native_stage_adapter.py +0 -631
  29. orchid/native_stage_part_adapter.py +0 -50
  30. orchid/native_subsurface_point.py +0 -70
  31. orchid/native_time_series_adapter.py +0 -54
  32. orchid/native_trajectory_adapter.py +0 -246
  33. orchid/native_treatment_calculations.py +0 -158
  34. orchid/native_treatment_curve_adapter.py +0 -60
  35. orchid/native_well_adapter.py +0 -134
  36. orchid/net_date_time.py +0 -328
  37. orchid/net_enumerable.py +0 -72
  38. orchid/net_fracture_diagnostics_factory.py +0 -55
  39. orchid/net_quantity.py +0 -620
  40. orchid/net_stage_qc.py +0 -62
  41. orchid/physical_quantity.py +0 -37
  42. orchid/project.py +0 -182
  43. orchid/project_store.py +0 -269
  44. orchid/reference_origins.py +0 -34
  45. orchid/script_adapter_context.py +0 -81
  46. orchid/searchable_data_frames.py +0 -44
  47. orchid/searchable_project_objects.py +0 -190
  48. orchid/searchable_stage_parts.py +0 -73
  49. orchid/searchable_stages.py +0 -29
  50. orchid/unit_system.py +0 -173
  51. orchid/utils.py +0 -14
  52. orchid/validation.py +0 -52
  53. orchid/version.py +0 -37
  54. orchid_python_api-5.25.3.dist-info/LICENSE +0 -176
  55. orchid_python_api-5.25.3.dist-info/RECORD +0 -88
  56. {orchid_python_api-5.25.3.dist-info → orchid_python_api-5.25.4.dist-info}/entry_points.txt +0 -0
  57. /LICENSE → /orchid_python_api-5.25.4.dist-info/licenses/LICENSE +0 -0
orchid/configuration.py DELETED
@@ -1,162 +0,0 @@
1
- # Copyright (c) 2017-2025 KAPPA
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- # This file is part of Orchid and related technologies.
16
- #
17
-
18
- import glob
19
- import logging
20
- import os
21
- import pathlib
22
- from typing import Dict, Any
23
- import warnings
24
- import toolz.curried as toolz
25
- import yaml
26
-
27
- from orchid.version import get_orchid_sdk_version
28
-
29
-
30
- _logger = logging.getLogger(__name__)
31
-
32
-
33
- class ConfigurationError(Exception):
34
- pass
35
-
36
-
37
- # Constants for environment variable names
38
- ORCHID_ROOT_ENV_VAR = 'ORCHID_ROOT'
39
- ORCHID_TRAINING_DATA_ENV_VAR = 'ORCHID_TRAINING_DATA'
40
-
41
-
42
- def get_environment_configuration() -> Dict[str, Dict[str, str]]:
43
- """
44
- Gets the API configuration from the system environment.
45
-
46
- Returns:
47
- The configuration, if any, calculated from the system environment.
48
- """
49
- environment_configuration = {'orchid': {key: os.environ[env_var] for key, env_var in
50
- [('root', ORCHID_ROOT_ENV_VAR), ('training_data', ORCHID_TRAINING_DATA_ENV_VAR)] if env_var in os.environ}}
51
- _logger.debug(f'environment configuration = {environment_configuration}')
52
- return environment_configuration
53
-
54
-
55
- def get_fallback_configuration() -> Dict[str, Dict[str, str]]:
56
- """
57
- Returns final fallback API configuration.
58
-
59
- Returns:
60
- A Python dictionary with the default (always available configuration).
61
-
62
- Warning:
63
- Although we have striven to make the default configuration a working configuration, we can only ensure
64
- that the default configuration meets the minimal "syntax" required by the Python API. For example, if
65
- Orchid is **not** installed in the default location, and the `directory` key is not overridden by a
66
- higher priority configuration, the Python API will **fail** to load the Orchid assemblies and throw
67
- an exception at runtime.
68
- """
69
-
70
- # Symbolically, the standard location for the installed Orchid binaries is
71
- # `$ProgramFiles/Reveal Energy Services, Inc/Orchid/<version-specific-directory>`. The following code
72
- # calculates an actual location by substituting the current version number for the symbol,
73
- # `<version-specific-directory>`.
74
- program_files_path = os.environ.get("ProgramFiles")
75
- fallback = {}
76
- if program_files_path is not None:
77
- orchid_version = get_orchid_sdk_version() + '.*'
78
- python_api_lib_pattern_path = os.path.join(program_files_path, 'Reveal Energy Services', 'Orchid', orchid_version, 'Orchid-'+orchid_version, 'PythonApiLibs')
79
- matching_paths = glob.glob(python_api_lib_pattern_path)
80
- if len(matching_paths) == 1:
81
- python_api_lib_path = matching_paths[0]
82
- _logger.info(f"PythonApiLibs path found : {python_api_lib_path}")
83
- fallback = {'orchid': {'root': str(matching_paths[0])}}
84
- elif len(matching_paths) == 0:
85
- _logger.info(f"PythonApiLibs path not found for {str(python_api_lib_pattern_path)}")
86
- else:
87
- warnings.warn(f'Fallback configuration found multiple matches for {str(python_api_lib_pattern_path)}')
88
- _logger.debug(f'fallback configuration={fallback}')
89
- return fallback
90
-
91
-
92
- def get_file_configuration() -> Dict[str, Any]:
93
- """
94
- Returns the API configuration read from the file system.
95
-
96
- Returns:
97
- A python dictionary with the default (always available configuration).
98
- """
99
-
100
- # This code looks for the configuration file, `python_api.yaml`, in the `.orchid` sub-directory of the
101
- # user-specific (and system-specific) home directory. See the Python documentation of `home()` for
102
- # details.
103
- file = {}
104
- file_config_path = pathlib.Path.home().joinpath('.orchid', 'python_api.yaml')
105
- if file_config_path.exists():
106
- with file_config_path.open('r') as in_stream:
107
- file = yaml.full_load(in_stream)
108
- _logger.debug(f'file configuration={file}')
109
- return file
110
-
111
-
112
- def get_configuration() -> Dict[str, Dict[str, Any]]:
113
- """
114
- Calculate the configuration for the Python API.
115
-
116
- Returns: The Python API configuration.
117
- """
118
-
119
- fallback_configuration = get_fallback_configuration()
120
- file_configuration = get_file_configuration()
121
- env_configuration = get_environment_configuration()
122
-
123
- configuration_dict = merge_configurations(fallback_configuration, file_configuration, env_configuration)
124
- if not configuration_dict.get('orchid') or (configuration_dict.get('orchid') and not configuration_dict['orchid'].get('root')):
125
- raise ConfigurationError("You must create an environment variable ORCHID_ROOT or a config file to set up the path to the PythonApiLibs folder since it's not in the default location")
126
-
127
- _logger.debug(f'result configuration={configuration_dict}')
128
- return configuration_dict
129
-
130
-
131
- def merge_configurations(fallback_configuration: Dict[str, Dict[str, str]], file_configuration: Dict[str, Dict[str, Any]], env_configuration: Dict[str, Dict[str, str]]) -> Dict[str, Dict[str, Any]]:
132
- # The rules for merging these configurations is not the same as a simple dictionary. The rules are:
133
- # - If two different configurations share a top-level key, merge the second level dictionaries.
134
- # - Then merge the distinct top-level keys.
135
- distinct_top_level_keys = set(toolz.concat([fallback_configuration.keys(),
136
- file_configuration.keys(),
137
- env_configuration.keys()]))
138
- result = {}
139
- for top_level_key in distinct_top_level_keys:
140
- fallback_child_configuration = fallback_configuration.get(top_level_key, {})
141
- file_child_configuration = file_configuration.get(top_level_key, {})
142
- env_child_configuration = env_configuration.get(top_level_key, {})
143
- child_configuration = toolz.merge(fallback_child_configuration,
144
- file_child_configuration,
145
- env_child_configuration)
146
- result[top_level_key] = child_configuration
147
- return result
148
-
149
-
150
- def training_data_path() -> pathlib.Path:
151
- """
152
- Returns the path of the directory containing the Orchid training data.
153
-
154
- Returns:
155
- The Orchid training data path.
156
-
157
- Raises:
158
- This function raises KeyError if the training directory path is not available from the package
159
- configuration.
160
- """
161
- result = pathlib.Path(toolz.get_in(['orchid', 'training_data'], get_configuration()))
162
- return result
orchid/convert.py DELETED
@@ -1,44 +0,0 @@
1
- #
2
- # This file is part of Orchid and related technologies.
3
- #
4
- # Copyright (c) 2017-2025 KAPPA. All Rights Reserved.
5
- #
6
- # LEGAL NOTICE:
7
- # Orchid contains trade secrets and otherwise confidential information
8
- # owned by KAPPA. Access to and use of this information is
9
- # strictly limited and controlled by the Company. This file may not be copied,
10
- # distributed, or otherwise disclosed outside of the Company's facilities
11
- # except under appropriate precautions to maintain the confidentiality hereof,
12
- # and may not be used in any way not expressly authorized by the Company.
13
- #
14
-
15
- from typing import Union
16
-
17
- import toolz.curried as toolz
18
-
19
- from orchid import (
20
- measurement as om,
21
- unit_system as units,
22
- )
23
-
24
-
25
- @toolz.curry
26
- def to_unit(target_unit: Union[units.UsOilfield, units.Metric], source_measurement: om.Quantity):
27
- """
28
- Convert a `Measurement` instance to the same measurement in `target_unit`.
29
-
30
- The order of arguments allows easier conversion of a sequence of `Measurement` instances (with the same
31
- unit) to another unit. For example, if client code wished to convert a sequence of force measurements from
32
- US oilfield units to metric units (that is, pound-force to Newtons). Code to perform this conversion might
33
- be similar to the following:
34
-
35
- > make_metric_force = to_unit(units.Metric.FORCE)
36
- > metric_force_measurements = [make_metric_force(f) for f in us_oilfield_force_measurements]
37
- > # alternatively,
38
- > # metric_force_measurements = toolz.map(make_metric_force, us_oilfield_force_measurements)
39
-
40
- Args:
41
- source_measurement: The Measurement instance to convert.
42
- target_unit: The units to which I convert `source_measurement`.
43
- """
44
- return source_measurement.to(target_unit.value.unit)
orchid/core.py DELETED
@@ -1,149 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- # Copyright (c) 2017-2025 KAPPA
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
- # This file is part of Orchid and related technologies.
18
- #
19
-
20
-
21
- from typing import Optional
22
-
23
- import deal
24
- import option
25
-
26
- from orchid.project import Project
27
- from orchid.project_store import ProjectStore
28
-
29
- # To support doctests only
30
- import shutil
31
-
32
- import orchid
33
-
34
-
35
- # TODO: change `ifrac_pathname` to be `str` or `pathlib.Path`
36
- @deal.pre(lambda ifrac_pathname: len(ifrac_pathname.strip()) != 0)
37
- @deal.pre(lambda ifrac_pathname: ifrac_pathname is not None)
38
- def load_project(ifrac_pathname: str) -> Project:
39
- """
40
- Return the project for the specified `.ifrac` file.
41
-
42
- Args:
43
- ifrac_pathname: The path identifying the data file of the project of interest.
44
-
45
- Returns:
46
- The project of interest.
47
-
48
- Examples:
49
- >>> load_path = orchid.training_data_path().joinpath('frankNstein_Bakken_UTM13_FEET.ifrac')
50
- >>> loaded_project = orchid.load_project(str(load_path))
51
- >>> loaded_project.name
52
- 'frankNstein_Bakken_UTM13_FEET'
53
- """
54
- loader = ProjectStore(ifrac_pathname.strip())
55
- result = Project(loader)
56
- return result
57
-
58
-
59
- # TODO: change `ifrac_pathname` to be `str` or `pathlib.Path`
60
- @deal.pre(lambda project, _: project is not None)
61
- @deal.pre(lambda _, ifrac_pathname: ifrac_pathname is not None)
62
- @deal.pre(lambda _, ifrac_pathname: len(ifrac_pathname) != 0)
63
- @deal.pre(lambda _, ifrac_pathname: len(ifrac_pathname.strip()) != 0)
64
- def save_project(project: Project, ifrac_pathname: str) -> None:
65
- """
66
- Return the project for the specified `.ifrac` file.
67
-
68
- Args:
69
- ifrac_pathname: The path identifying the data file of the project of interest.
70
- project: The project of interest.
71
-
72
- Examples:
73
- >>> # Test saving changed project
74
- >>> load_path = orchid.training_data_path().joinpath('frankNstein_Bakken_UTM13_FEET.ifrac')
75
- >>> loaded_project = orchid.load_project(str(load_path))
76
- >>> save_path = load_path.with_name(f'salvus{load_path.suffix}')
77
- >>> orchid.save_project(loaded_project, str(save_path))
78
- >>> save_path.exists()
79
- True
80
- >>> save_path.unlink()
81
- """
82
-
83
- store = ProjectStore(ifrac_pathname.strip())
84
- store.save_project(project)
85
-
86
-
87
- # TODO: change `ifrac_pathname` to be `str` or `pathlib.Path`
88
- @deal.pre(lambda _: _.project is not None)
89
- @deal.pre(lambda _: _.project is not None)
90
- @deal.pre(lambda _: _.source_pathname is not None)
91
- @deal.pre(lambda _: len(_.source_pathname) != 0)
92
- @deal.pre(lambda _: len(_.source_pathname.strip()) != 0)
93
- @deal.pre(lambda _: (_.maybe_target_pathname is None or
94
- (_.maybe_target_pathname is not None and len(_.maybe_target_pathname) != 0)))
95
- @deal.pre(lambda _: (_.maybe_target_pathname is None or
96
- (_.maybe_target_pathname is not None and len(_.maybe_target_pathname.strip()) != 0)))
97
- def optimized_but_possibly_unsafe_save(project: Project, source_pathname: str,
98
- maybe_target_pathname: Optional[str] = None):
99
- """
100
- Saves `project`, optionally to `maybe_to_pathname` is an optimized, but possibly "unsafe" manner.
101
-
102
- If `maybe_to_pathname` is supplied and is not `None`, it must be a string representing a valid pathname. If a file
103
- with that pathname already exists, it will be overwritten (unless it is the same path as `source_pathname`)
104
-
105
- This method is unsafe because it only writes some data from `project`; the remainder of the data is simply
106
- (bulk) copied from the `.ifrac` file, `source_pathname`.
107
-
108
- This method assumes that `project` was originally loaded from `project_pathname` and was then changed in
109
- such a way that the "bulk" data **was not** changed. If this assumption is not true, the project saved in
110
- `to_pathname` will **not** contain all the changes to `project`.
111
-
112
- Specifically, this method **does not** save changes to data like:
113
-
114
- - Trajectories
115
- - Treatment curves
116
- - Monitor curves
117
-
118
- We believe that this method will generally finish more quickly than `save_project`; however, we cannot
119
- guarantee this behavior. We encourage the developer calling this method to perform her own performance tests
120
- and to understand if his use case meets the assumptions made by this method.
121
-
122
- Args:
123
- project: The project to be saved.
124
- source_pathname: The pathname of the `.ifrac` file from which `project` was loaded.
125
- maybe_target_pathname: The optional pathname of the `.ifrac` file in which to store `project`.
126
-
127
- Examples:
128
- >>> # Test optimized but possibly unsafe save of project
129
- >>> load_path = orchid.training_data_path().joinpath('Project_frankNstein_Permian_UTM13_FEET.ifrac')
130
- >>> loaded_project = orchid.load_project(str(load_path))
131
- >>> save_path = load_path.with_name(f'salva intuta{load_path.suffix}')
132
- >>> orchid.optimized_but_possibly_unsafe_save(loaded_project, str(load_path), str(save_path))
133
- >>> save_path.exists()
134
- True
135
- >>> save_path.unlink()
136
- >>> # Test optimized but possibly unsafe save of project in loaded location
137
- >>> source_path = orchid.training_data_path().joinpath('Project_frankNstein_Permian_UTM13_FEET.ifrac')
138
- >>> load_path = source_path.with_name(f'idem salvum filum{source_path.suffix}')
139
- >>> # ignore returned result for doctest
140
- >>> _to_path = shutil.copyfile(str(source_path), str(load_path))
141
- >>> loaded_project = orchid.load_project(str(load_path))
142
- >>> orchid.optimized_but_possibly_unsafe_save(loaded_project, str(load_path))
143
- >>> load_path.exists()
144
- True
145
- >>> load_path.unlink()
146
-
147
- """
148
- store = ProjectStore(source_pathname.strip())
149
- store.optimized_but_possibly_unsafe_save(project, option.maybe(maybe_target_pathname))
@@ -1,28 +0,0 @@
1
- #
2
- # This file is part of Orchid and related technologies.
3
- #
4
- # Copyright (c) 2017-2025 KAPPA. All Rights Reserved.
5
- #
6
- # LEGAL NOTICE:
7
- # Orchid contains trade secrets and otherwise confidential information
8
- # owned by KAPPA. Access to and use of this information is
9
- # strictly limited and controlled by the Company. This file may not be copied,
10
- # distributed, or otherwise disclosed outside of the Company's facilities
11
- # except under appropriate precautions to maintain the confidentiality hereof,
12
- # and may not be used in any way not expressly authorized by the Company.
13
- #
14
-
15
- import option
16
-
17
- from orchid import dot_net_dom_access as dna
18
-
19
-
20
- def transform_display_name(net_display_name):
21
- maybe_display_name = option.maybe(net_display_name)
22
- return maybe_display_name.expect('Unexpected value, `None`, for `display_name`.')
23
-
24
-
25
- class DomProjectObject(dna.IdentifiedDotNetAdapter):
26
- name = dna.dom_property('name', 'The name of this data frame.')
27
- display_name = dna.transformed_dom_property('display_name', 'The display name of this data frame.',
28
- transform_display_name)
orchid/dot_net.py DELETED
@@ -1,68 +0,0 @@
1
- # Copyright (c) 2017-2025 KAPPA
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- #
15
- # This file is part of Orchid and related technologies.
16
- #
17
-
18
-
19
- import os
20
- import pathlib
21
-
22
- import orchid.configuration
23
- import orchid.script_adapter_context as sac
24
-
25
- import toolz.curried as toolz
26
-
27
- from pythonnet import load
28
- load('coreclr')
29
-
30
- # noinspection PyPackageRequirements
31
- import clr
32
-
33
-
34
- def add_orchid_assemblies() -> None:
35
- """
36
- Add references to the Orchid assemblies needed by the Python API.
37
-
38
- Although not all modules in the `orchid` package need .NET types from all the available Orchid assemblies,
39
- I believe the additional cost of adding those references is far less than the cost of maintaining the
40
- copy-paste, boilerplate code that results without this common function.
41
- :return:
42
- """
43
- clr.AddReference('Orchid.Common')
44
- clr.AddReference('Orchid.FractureDiagnostics')
45
- clr.AddReference('Orchid.FractureDiagnostics.Factories')
46
- clr.AddReference('UnitsNet')
47
- clr.AddReference('System.Collections')
48
- return None
49
-
50
-
51
- def app_settings_path() -> str:
52
- """
53
- Return the pathname of the `appSettings.json` file needed by the `SDKFacade` assembly.
54
-
55
- :return: The required pathname.
56
- """
57
- result = os.fspath(pathlib.Path(toolz.get_in(['orchid', 'root'], orchid.configuration.get_configuration())).joinpath('appSettings.json'))
58
- return result
59
-
60
-
61
- def prepare_imports() -> None:
62
- # This function call must occur *after*
63
- # - Importing clr
64
- # - Adding a reference to `Orchid.FractureDiagnostics.SDKFacade`
65
- # - Importing ScriptAdapter from `Orchid.FractureDiagnostics.SDKFacade`
66
- # - The call to `append_orchid_assemblies_directory_path`
67
- with sac.ScriptAdapterContext():
68
- orchid.dot_net.add_orchid_assemblies()
@@ -1,64 +0,0 @@
1
- #
2
- # This file is part of Orchid and related technologies.
3
- #
4
- # Copyright (c) 2017-2025 KAPPA. All Rights Reserved.
5
- #
6
- # LEGAL NOTICE:
7
- # Orchid contains trade secrets and otherwise confidential information
8
- # owned by KAPPA. Access to and use of this information is
9
- # strictly limited and controlled by the Company. This file may not be copied,
10
- # distributed, or otherwise disclosed outside of the Company's facilities
11
- # except under appropriate precautions to maintain the confidentiality hereof,
12
- # and may not be used in any way not expressly authorized by the Company.
13
- #
14
- """Wrapper function to help deal with IDisposable classes.
15
-
16
- This implementation is based on code in the discussion of
17
- [Python.NET issue 79](https://github.com/pythonnet/pythonnet/issues/79#issuecomment-187107566).
18
- """
19
-
20
- from inspect import isclass, isfunction
21
- from contextlib import contextmanager
22
-
23
- __all__ = ['disposable']
24
-
25
- """Contextmanager wrapper around IDisposables."""
26
-
27
-
28
- @contextmanager
29
- def disposable(obj_or_class, *args, **kwargs):
30
- """
31
- Contextmanager wrapper around IDisposables.
32
-
33
- Example:
34
-
35
- >>> with disposable(dbConnection):
36
- >>> blah()
37
-
38
- Args:
39
- obj_or_class: The .NET object or class with a `Dispose` method.
40
- *args: Positional arguments needed to manage the context managed by `__enter__/__exit__`.
41
- **kwargs: Keyword arguments needed to manage the context managed by `__exit__/__exit_-`.
42
-
43
- Returns:
44
- The instance of the object or class implementing `IDispose`.
45
- """
46
-
47
- if isclass(obj_or_class) or isfunction(obj_or_class):
48
- obj = obj_or_class(*args, **kwargs)
49
- else:
50
- obj = obj_or_class
51
-
52
- if hasattr(obj, '__enter__') and hasattr(obj, '__exit__'):
53
- # Already a contextmanager, skip
54
- return obj
55
-
56
- if not hasattr(obj, 'Dispose') or not callable(obj.Dispose):
57
- # Likely not IDisposable, currently isinstance doesn't work for
58
- # interfaces, so we have to check that in this explicit way
59
- return obj
60
-
61
- try:
62
- yield obj
63
- finally:
64
- obj.Dispose()