portacom 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
portacom-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mahmoud Komaiha
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.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: portacom
3
+ Version: 0.1.0
4
+ Summary: Call classic COM interfaces through ctypes on Windows, Linux and macOS
5
+ Author-email: Mahmoud Komaiha <mkomaiha@umich.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mkomaiha/portacom
8
+ Project-URL: Repository, https://github.com/mkomaiha/portacom
9
+ Project-URL: Issues, https://github.com/mkomaiha/portacom/issues
10
+ Project-URL: Changelog, https://github.com/mkomaiha/portacom/blob/main/CHANGELOG.md
11
+ Keywords: com,ctypes,winmd,iunknown,ffi,cross-platform
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Operating System :: POSIX :: Linux
21
+ Classifier: Operating System :: MacOS
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # portacom
29
+
30
+ The runtime for bindings generated by
31
+ [winmd2py](https://pypi.org/project/winmd2py/): call classic (IUnknown-based)
32
+ COM interfaces through ctypes on Windows, Linux and macOS, and implement them
33
+ in Python. Pure Python, no dependencies.
34
+
35
+ ```bash
36
+ pip install portacom
37
+ ```
38
+
39
+ Generate bindings from a `.winmd` with `winmd2py`, then (here for MiniCOM's
40
+ example library on Linux, whose `BOOL` and BSTRs differ from Windows'):
41
+
42
+ ```python
43
+ import ctypes, portacom
44
+
45
+ minicom = portacom.Abi("minicom", typedefs={"BOOL": ctypes.c_long}, allocator=portacom.PlainAllocator())
46
+ portacom.use_abi("my_bindings", minicom) # before importing the bindings
47
+ import my_bindings.Example as example
48
+ import my_bindings.Registry as api
49
+
50
+ registry = portacom.co_create_instance(api.Registry, api.IRegistry, library="libExampleLib.so")
51
+ calc = registry.NewCalculator() # [out] arguments are returned
52
+ print(calc.Add(1, 2)) # 3
53
+
54
+
55
+ class Callback(portacom.ComObject): # implement an interface in Python
56
+ _interfaces_ = (example.ICalcCb,)
57
+
58
+ def Message(self, text):
59
+ print("C++ says", text)
60
+
61
+
62
+ calc.SetCallback(Callback())
63
+ calc.QueryInterface(example.ICalc2).GetValue2() # 43; C++ calls back: "C++ says GetValue2 called"
64
+ ```
65
+
66
+ A failed HRESULT raises `portacom.COMError`. BSTRs, VARIANTs, SAFEARRAYs,
67
+ GUIDs and enums convert to and from Python values, and `portacom.Dispatch`
68
+ calls automation (IDispatch) objects by name.
69
+
70
+ For ready-made DirectX Shader Compiler bindings, see
71
+ [portacom-dxc](https://pypi.org/project/portacom-dxc/). Calling conventions,
72
+ ABI profiles and limitations are in the
73
+ [project README](https://github.com/mkomaiha/portacom#readme).
74
+
75
+ MIT license.
@@ -0,0 +1,48 @@
1
+ # portacom
2
+
3
+ The runtime for bindings generated by
4
+ [winmd2py](https://pypi.org/project/winmd2py/): call classic (IUnknown-based)
5
+ COM interfaces through ctypes on Windows, Linux and macOS, and implement them
6
+ in Python. Pure Python, no dependencies.
7
+
8
+ ```bash
9
+ pip install portacom
10
+ ```
11
+
12
+ Generate bindings from a `.winmd` with `winmd2py`, then (here for MiniCOM's
13
+ example library on Linux, whose `BOOL` and BSTRs differ from Windows'):
14
+
15
+ ```python
16
+ import ctypes, portacom
17
+
18
+ minicom = portacom.Abi("minicom", typedefs={"BOOL": ctypes.c_long}, allocator=portacom.PlainAllocator())
19
+ portacom.use_abi("my_bindings", minicom) # before importing the bindings
20
+ import my_bindings.Example as example
21
+ import my_bindings.Registry as api
22
+
23
+ registry = portacom.co_create_instance(api.Registry, api.IRegistry, library="libExampleLib.so")
24
+ calc = registry.NewCalculator() # [out] arguments are returned
25
+ print(calc.Add(1, 2)) # 3
26
+
27
+
28
+ class Callback(portacom.ComObject): # implement an interface in Python
29
+ _interfaces_ = (example.ICalcCb,)
30
+
31
+ def Message(self, text):
32
+ print("C++ says", text)
33
+
34
+
35
+ calc.SetCallback(Callback())
36
+ calc.QueryInterface(example.ICalc2).GetValue2() # 43; C++ calls back: "C++ says GetValue2 called"
37
+ ```
38
+
39
+ A failed HRESULT raises `portacom.COMError`. BSTRs, VARIANTs, SAFEARRAYs,
40
+ GUIDs and enums convert to and from Python values, and `portacom.Dispatch`
41
+ calls automation (IDispatch) objects by name.
42
+
43
+ For ready-made DirectX Shader Compiler bindings, see
44
+ [portacom-dxc](https://pypi.org/project/portacom-dxc/). Calling conventions,
45
+ ABI profiles and limitations are in the
46
+ [project README](https://github.com/mkomaiha/portacom#readme).
47
+
48
+ MIT license.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "portacom"
7
+ version = "0.1.0"
8
+ description = "Call classic COM interfaces through ctypes on Windows, Linux and macOS"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Mahmoud Komaiha", email = "mkomaiha@umich.edu" }]
13
+ requires-python = ">=3.10"
14
+ dependencies = []
15
+ keywords = ["com", "ctypes", "winmd", "iunknown", "ffi", "cross-platform"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Operating System :: Microsoft :: Windows",
25
+ "Operating System :: POSIX :: Linux",
26
+ "Operating System :: MacOS",
27
+ "Topic :: Software Development :: Libraries",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/mkomaiha/portacom"
32
+ Repository = "https://github.com/mkomaiha/portacom"
33
+ Issues = "https://github.com/mkomaiha/portacom/issues"
34
+ Changelog = "https://github.com/mkomaiha/portacom/blob/main/CHANGELOG.md"
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,244 @@
1
+ """portacom: classic COM from Python, through ctypes, on Windows, Linux and macOS.
2
+
3
+ Bindings generated by winmd2py import this runtime. Using them:
4
+
5
+ import ctypes, portacom
6
+ import my_bindings.My.Namespace as api
7
+
8
+ obj = api.CreateThing(api.CLSID_Thing, api.IThing) # riid/ppv: pass the class
9
+ value = obj.GetValue() # [out] arguments are returned
10
+ other = obj.query(api.IOther) # None if not implemented
11
+
12
+ class Callback(portacom.ComObject): # implementing an interface
13
+ _interfaces_ = (api.ICallback,)
14
+
15
+ def Notify(self, message):
16
+ print(message)
17
+
18
+ Errors raise portacom.COMError.
19
+
20
+ A .winmd describes the Windows ABI. Where a library built for Linux or macOS
21
+ differs (BOOL or LONG width, BSTR allocation), declare it in the bindings'
22
+ __init__.py, before their modules are imported (Windows keeps the metadata's):
23
+
24
+ portacom.use_abi(__name__, portacom.Abi("mylib", library="mylib.dll", typedefs={"BOOL": ctypes.c_long}))
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from ._abi import WINDOWS, Abi, abi_for, use_abi
30
+ from ._activation import (
31
+ CLSCTX_ALL,
32
+ CLSCTX_INPROC_SERVER,
33
+ CoClass,
34
+ clsid_from_progid,
35
+ co_create_instance,
36
+ co_initialize,
37
+ create_instance,
38
+ exported_factory,
39
+ )
40
+ from ._dispatch import (
41
+ DISPATCH_METHOD,
42
+ DISPATCH_PROPERTYGET,
43
+ DISPATCH_PROPERTYPUT,
44
+ DISPATCH_PROPERTYPUTREF,
45
+ DISPID_NEWENUM,
46
+ DISPID_PROPERTYPUT,
47
+ DISPID_UNKNOWN,
48
+ DISPID_VALUE,
49
+ DISPPARAMS,
50
+ EXCEPINFO,
51
+ Dispatch,
52
+ DispatchObject,
53
+ EnumVARIANT,
54
+ IDispatch,
55
+ IEnumVARIANT,
56
+ )
57
+ from ._errors import (
58
+ E_ABORT,
59
+ E_ACCESSDENIED,
60
+ E_BOUNDS,
61
+ E_FAIL,
62
+ E_INVALIDARG,
63
+ E_NOINTERFACE,
64
+ E_NOT_SET,
65
+ E_NOTIMPL,
66
+ E_OUTOFMEMORY,
67
+ E_POINTER,
68
+ E_UNEXPECTED,
69
+ REGDB_E_CLASSNOTREG,
70
+ S_FALSE,
71
+ S_OK,
72
+ COMError,
73
+ check,
74
+ failed,
75
+ hresult_text,
76
+ set_callback_error_handler,
77
+ succeeded,
78
+ )
79
+ from ._interface import Function, IUnknown
80
+ from ._loader import library_candidates, load_library, register_library
81
+ from ._marshal import COMOUTPTR, CONST, IN, OPTIONAL, OUT, RETVAL, Param, Unsupported
82
+ from ._memory import (
83
+ Allocator,
84
+ LibraryAllocator,
85
+ OleAllocator,
86
+ PlainAllocator,
87
+ WindowsLayoutAllocator,
88
+ default_allocator,
89
+ free_task_memory,
90
+ )
91
+ from ._platform import ARCH, FUNCTYPE, NATIVE_COM, WCHAR_SIZE, by_arch, wchar_count
92
+ from ._safearray import SAFEARRAY, SafeArray
93
+ from ._server import ComObject
94
+ from ._types import (
95
+ GUID,
96
+ HRESULT,
97
+ PSTR,
98
+ PWSTR,
99
+ VARIANT_BOOL,
100
+ Array,
101
+ BSTRBase,
102
+ Enum,
103
+ Flags,
104
+ LazyModule,
105
+ Ptr,
106
+ Structure,
107
+ Union,
108
+ bitfields,
109
+ finish_layouts,
110
+ layout,
111
+ set_fields,
112
+ )
113
+ from ._variant import (
114
+ VARIANT,
115
+ VT_ARRAY,
116
+ VT_BOOL,
117
+ VT_BSTR,
118
+ VT_CY,
119
+ VT_DATE,
120
+ VT_DISPATCH,
121
+ VT_EMPTY,
122
+ VT_ERROR,
123
+ VT_I1,
124
+ VT_I2,
125
+ VT_I4,
126
+ VT_I8,
127
+ VT_INT,
128
+ VT_NULL,
129
+ VT_R4,
130
+ VT_R8,
131
+ VT_UI1,
132
+ VT_UI2,
133
+ VT_UI4,
134
+ VT_UI8,
135
+ VT_UINT,
136
+ VT_UNKNOWN,
137
+ VT_VARIANT,
138
+ )
139
+
140
+ __version__ = "0.1.0"
141
+
142
+
143
+ # The API for using bindings. What generated code needs is in portacom.codegen;
144
+ # its names are still importable from here (portacom.layout, ...) for one
145
+ # release, but not part of the API.
146
+ __all__ = [
147
+ # using bindings
148
+ "IUnknown",
149
+ "ComObject",
150
+ "Dispatch",
151
+ "DispatchObject",
152
+ "EnumVARIANT",
153
+ "IDispatch",
154
+ "IEnumVARIANT",
155
+ "DISPPARAMS",
156
+ "EXCEPINFO",
157
+ "DISPATCH_METHOD",
158
+ "DISPATCH_PROPERTYGET",
159
+ "DISPATCH_PROPERTYPUT",
160
+ "DISPATCH_PROPERTYPUTREF",
161
+ "DISPID_VALUE",
162
+ "DISPID_UNKNOWN",
163
+ "DISPID_PROPERTYPUT",
164
+ "DISPID_NEWENUM",
165
+ "COMError",
166
+ "GUID",
167
+ "VARIANT",
168
+ "SafeArray",
169
+ "SAFEARRAY",
170
+ "PWSTR",
171
+ "PSTR",
172
+ "BSTRBase",
173
+ "CoClass",
174
+ "create_instance",
175
+ "exported_factory",
176
+ "co_create_instance",
177
+ "co_initialize",
178
+ "clsid_from_progid",
179
+ "CLSCTX_ALL",
180
+ "CLSCTX_INPROC_SERVER",
181
+ "free_task_memory",
182
+ "set_callback_error_handler",
183
+ "check",
184
+ "succeeded",
185
+ "failed",
186
+ "hresult_text",
187
+ "S_OK",
188
+ "S_FALSE",
189
+ "E_ABORT",
190
+ "E_ACCESSDENIED",
191
+ "E_BOUNDS",
192
+ "E_FAIL",
193
+ "E_INVALIDARG",
194
+ "E_NOINTERFACE",
195
+ "E_NOTIMPL",
196
+ "E_NOT_SET",
197
+ "E_OUTOFMEMORY",
198
+ "E_POINTER",
199
+ "E_UNEXPECTED",
200
+ "REGDB_E_CLASSNOTREG",
201
+ "VT_EMPTY",
202
+ "VT_NULL",
203
+ "VT_I1",
204
+ "VT_I2",
205
+ "VT_I4",
206
+ "VT_I8",
207
+ "VT_UI1",
208
+ "VT_UI2",
209
+ "VT_UI4",
210
+ "VT_UI8",
211
+ "VT_INT",
212
+ "VT_UINT",
213
+ "VT_R4",
214
+ "VT_R8",
215
+ "VT_CY",
216
+ "VT_DATE",
217
+ "VT_BSTR",
218
+ "VT_DISPATCH",
219
+ "VT_ERROR",
220
+ "VT_BOOL",
221
+ "VT_VARIANT",
222
+ "VT_UNKNOWN",
223
+ "VT_ARRAY",
224
+ # libraries and their ABI
225
+ "register_library",
226
+ "load_library",
227
+ "Abi",
228
+ "use_abi",
229
+ "WINDOWS",
230
+ "Ptr", # for Abi overrides of pointer types
231
+ "Array",
232
+ "Allocator",
233
+ "OleAllocator",
234
+ "WindowsLayoutAllocator",
235
+ "PlainAllocator",
236
+ "LibraryAllocator",
237
+ "default_allocator",
238
+ # the platform
239
+ "NATIVE_COM",
240
+ "WCHAR_SIZE",
241
+ "ARCH",
242
+ "by_arch",
243
+ "wchar_count",
244
+ ]
@@ -0,0 +1,196 @@
1
+ """ABI profiles: what a library was compiled with that the .winmd cannot say.
2
+
3
+ A .winmd describes the Windows ABI, and the format has no way to say that a
4
+ library built for Linux or macOS differs. So winmd2py generates ABI-neutral
5
+ bindings, and whoever knows the library declares its differences with an Abi
6
+ before importing them (in the bindings' own __init__.py, which winmd2py
7
+ creates once for this):
8
+
9
+ typedefs {typedef name: ctypes type}, for every use of a named typedef
10
+ from the metadata (BOOL, BSTR, ...)
11
+ overrides {"Interface.Method": restype, "Interface.Method.param": type,
12
+ "Function.param": type, "Struct.field": type}, for uses that
13
+ the metadata does not name (LONG, and an IDL "long", are just
14
+ Int32)
15
+ allocator how BSTRs, VARIANTs, task memory and SAFEARRAYs are allocated
16
+ and freed
17
+ library the shared library behind the bindings ("Foo.dll"): where
18
+ co_create_instance() finds CoCreateInstance, and whose own
19
+ exported SysAllocStringLen & co. are used where it has them
20
+
21
+ An Abi describes the library as built for Linux and macOS. On Windows the
22
+ metadata is the truth, so use_abi() binds WINDOWS there, unless the Abi was
23
+ made with on_windows=True. Each generated module asks abi_for() for its Abi
24
+ when it is imported; see use_abi() for how the registration is chosen.
25
+ Without one it is WINDOWS.
26
+ """
27
+
28
+ from ._memory import Allocator, LazyAllocator, LibraryAllocator, default_allocator
29
+ from ._platform import NATIVE_COM
30
+ from ._types import HRESULT, PSTR, PWSTR, VARIANT_BOOL, BSTRBase
31
+
32
+
33
+ class Abi:
34
+ """How a library built for Linux and macOS differs from the Windows ABI
35
+ that its .winmd describes.
36
+
37
+ On Windows the library follows the metadata, so use_abi() binds WINDOWS
38
+ instead; on_windows=True is for the rare library that deviates on
39
+ Windows too, and makes use_abi() bind this Abi there as well.
40
+
41
+ library: the shared library behind the bindings, named as the metadata
42
+ names it ("Foo.dll", which is libFoo.so or libFoo.dylib off Windows; see
43
+ load_library), or a path or a loaded ctypes.CDLL. co_create_instance()
44
+ calls its exported CoCreateInstance when not given a library (off
45
+ Windows; on Windows OLE32's is used), and its own exported
46
+ SysAllocStringLen, SysFreeString, SysStringLen, CoTaskMemAlloc,
47
+ CoTaskMemFree and SafeArray* functions allocate the BSTRs and memory, in
48
+ each family that it exports whole (a LibraryAllocator). It is loaded on
49
+ first use, so making an Abi loads nothing.
50
+
51
+ allocator: how BSTRs, task memory and SAFEARRAYs are allocated. With
52
+ library too, it is the fallback for what the library does not export;
53
+ alone, it is used outright. Without either, the default allocator (OLE
54
+ on Windows, the Windows layout on malloc/free elsewhere).
55
+ """
56
+
57
+ def __init__(self, name, typedefs=None, overrides=None, allocator=None, library=None, on_windows=False):
58
+ self.name = name
59
+ self.typedefs = dict(typedefs or {})
60
+ self.overrides = dict(overrides or {})
61
+ if allocator is not None and not isinstance(allocator, Allocator):
62
+ raise TypeError("allocator must be a portacom.Allocator")
63
+ self.library = library
64
+ self.on_windows = on_windows
65
+ self._fallback = allocator
66
+ self._allocator = allocator if library is None else None
67
+ self._default_allocator = allocator is None and library is None
68
+ # what this ABI's types allocate with, resolved on first use
69
+ self._lazy = LazyAllocator(lambda: self.allocator)
70
+ self._bstr = None
71
+ self._safearray = None
72
+ self._variant = None
73
+
74
+ @property
75
+ def allocator(self):
76
+ if self._allocator is None:
77
+ if self.library is not None:
78
+ self._allocator = LibraryAllocator(self.library, fallback=self._fallback)
79
+ else:
80
+ self._allocator = default_allocator()
81
+ return self._allocator
82
+
83
+ @property
84
+ def BSTR(self):
85
+ """The BSTR type of this ABI, which allocates with its allocator."""
86
+ if self._bstr is None:
87
+ self._bstr = type("BSTR", (BSTRBase,), {"_allocator_": self._lazy, "__qualname__": f"BSTR[{self.name}]"})
88
+ return self._bstr
89
+
90
+ @property
91
+ def SAFEARRAY(self):
92
+ """The SAFEARRAY type of this ABI, which allocates with its allocator."""
93
+ if self._safearray is None:
94
+ from ._safearray import SAFEARRAY
95
+
96
+ self._safearray = type(f"SAFEARRAY[{self.name}]", (SAFEARRAY,), {"_allocator_": self._lazy})
97
+ return self._safearray
98
+
99
+ @property
100
+ def VARIANT(self):
101
+ """The VARIANT type of this ABI, whose BSTRs and arrays use its allocator."""
102
+ if self._variant is None:
103
+ from ._variant import VARIANT, variant_type
104
+
105
+ if self._default_allocator:
106
+ self._variant = VARIANT # the default allocator, as portacom.VARIANT has
107
+ else:
108
+ self._variant = variant_type(self._lazy, self.name)
109
+ return self._variant
110
+
111
+ def typedef(self, name, default):
112
+ """The type for a use of the named typedef."""
113
+ if name in self.typedefs:
114
+ return self.typedefs[name]
115
+ special = _SPECIAL.get(name)
116
+ if special is not None:
117
+ return special(self)
118
+ return default
119
+
120
+ def override(self, key, default):
121
+ return self.overrides.get(key, default)
122
+
123
+ def __repr__(self):
124
+ return f"<Abi {self.name!r}>"
125
+
126
+
127
+ _SPECIAL = {
128
+ "BSTR": lambda abi: abi.BSTR,
129
+ "SAFEARRAY": lambda abi: abi.SAFEARRAY,
130
+ "PWSTR": lambda abi: PWSTR,
131
+ "PCWSTR": lambda abi: PWSTR,
132
+ "PSTR": lambda abi: PSTR,
133
+ "PCSTR": lambda abi: PSTR,
134
+ "HRESULT": lambda abi: HRESULT,
135
+ "VARIANT_BOOL": lambda abi: VARIANT_BOOL,
136
+ }
137
+
138
+ # The Windows ABI, as the metadata describes it
139
+ WINDOWS = Abi("windows")
140
+
141
+ _registered = {} # key (module path or namespace) -> Abi
142
+ _bound = {} # (module path, namespace) -> Abi, for modules already imported
143
+
144
+
145
+ def _matches(key, name):
146
+ return name == key or name.startswith(key + ".")
147
+
148
+
149
+ def _longest(registry, name):
150
+ keys = [key for key in registry if _matches(key, name)]
151
+ return registry[max(keys, key=len)] if keys else None
152
+
153
+
154
+ def _resolve(registry, namespace, module):
155
+ """A key matching the module path wins; then a key matching the namespace."""
156
+ found = _longest(registry, module) if module else None
157
+ if found is None:
158
+ found = _longest(registry, namespace)
159
+ return found or WINDOWS
160
+
161
+
162
+ def use_abi(key, abi):
163
+ """Use abi for the generated bindings that key names. Call it before
164
+ importing them (in the bindings' __init__.py): a module's Abi is fixed
165
+ when it is imported.
166
+
167
+ An Abi describes the library as built for Linux and macOS. On Windows
168
+ the metadata is the truth, so there the bindings get WINDOWS, unless abi
169
+ was made with on_windows=True.
170
+
171
+ key is a generated package's import path ("my_bindings", or
172
+ "my_bindings.Windows.Win32"), or a metadata namespace ("Windows.Win32").
173
+ For a module, the longest key that matches its import path wins; only if
174
+ none does, the longest key that matches its namespace. So a package that
175
+ ships bindings (portacom_dxc registers "portacom_dxc._bindings") is not
176
+ affected by a namespace registered for other bindings of that namespace.
177
+ """
178
+ if not isinstance(abi, Abi):
179
+ raise TypeError(f"use_abi() takes a portacom.Abi, not {type(abi).__name__}")
180
+ if NATIVE_COM and not abi.on_windows:
181
+ abi = WINDOWS
182
+ trial = {**_registered, key: abi}
183
+ for (module, namespace), used in _bound.items():
184
+ if _resolve(trial, namespace, module) is not used:
185
+ raise RuntimeError(
186
+ f"{module} was already imported with {used!r}; call portacom.use_abi({key!r}, ...) "
187
+ f"before importing {module}"
188
+ )
189
+ _registered[key] = abi
190
+
191
+
192
+ def abi_for(namespace, module=None):
193
+ """The Abi for a generated module, called by the module when imported."""
194
+ abi = _resolve(_registered, namespace, module)
195
+ _bound[(module or namespace, namespace)] = abi
196
+ return abi