nitlsconfig 1.0.0a1__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.
- nitlsconfig-1.0.0a1/LICENSE +20 -0
- nitlsconfig-1.0.0a1/PKG-INFO +127 -0
- nitlsconfig-1.0.0a1/README.md +98 -0
- nitlsconfig-1.0.0a1/pyproject.toml +110 -0
- nitlsconfig-1.0.0a1/src/nitlsconfig/__init__.py +97 -0
- nitlsconfig-1.0.0a1/src/nitlsconfig/cli.py +653 -0
- nitlsconfig-1.0.0a1/src/nitlsconfig/grpc_channel.py +374 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2022, National Instruments Corp.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included
|
|
12
|
+
in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: nitlsconfig
|
|
3
|
+
Version: 1.0.0a1
|
|
4
|
+
Summary: Python API for reading nitlsconfig configurations and creating gRPC client channels from them
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: nitlsconfig,tls,mtls,grpc,configuration
|
|
7
|
+
Author: NI
|
|
8
|
+
Author-email: opensource@ni.com
|
|
9
|
+
Maintainer: Philip Thong
|
|
10
|
+
Maintainer-email: philip.thong@emerson.com
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: POSIX
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Provides-Extra: grpc
|
|
25
|
+
Requires-Dist: grpcio (>=1.49.0,<2.0) ; extra == "grpc"
|
|
26
|
+
Project-URL: Repository, https://github.com/ni/nitlsconfig-python
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# nitlsconfig
|
|
30
|
+
|
|
31
|
+
Python API that reads nitlsconfig configurations through the `nitlsconfig` command line,
|
|
32
|
+
and builds gRPC client channels from them.
|
|
33
|
+
|
|
34
|
+
Installed and imported as `nitlsconfig`; developed at
|
|
35
|
+
[ni/nitlsconfig-python](https://github.com/ni/nitlsconfig-python).
|
|
36
|
+
|
|
37
|
+
## Runtime dependencies
|
|
38
|
+
|
|
39
|
+
- nitlsconfig executable, discoverable, or explicit path to NITLSCONFIG_CLI
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
Reading NI-TLS configuration is pure Python and has no third-party dependencies:
|
|
44
|
+
|
|
45
|
+
- `pip install nitlsconfig`
|
|
46
|
+
|
|
47
|
+
The gRPC channel factory additionally needs grpcio, which is an optional extra:
|
|
48
|
+
|
|
49
|
+
- `pip install nitlsconfig[grpc]`
|
|
50
|
+
|
|
51
|
+
## Creating a gRPC channel
|
|
52
|
+
|
|
53
|
+
`create_grpc_client_channel` reads the local NI-TLS client configuration and returns a
|
|
54
|
+
`grpc.Channel` secured accordingly. The `server_address` hostname or address is used to
|
|
55
|
+
select matching target-specific NI-TLS settings. Pass the channel straight to any NI
|
|
56
|
+
gRPC Python API:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import nidcpower
|
|
60
|
+
import nitlsconfig
|
|
61
|
+
|
|
62
|
+
with nitlsconfig.create_grpc_client_channel("localhost", 31763) as channel:
|
|
63
|
+
options = nidcpower.GrpcSessionOptions(channel, "")
|
|
64
|
+
with nidcpower.Session("Dev1", grpc_options=options) as session:
|
|
65
|
+
...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The channel is mutually authenticated, one-way TLS, or insecure depending on how
|
|
69
|
+
the machine is configured; no code change is needed to move between them. The
|
|
70
|
+
channel is owned by the caller - NI driver APIs never close it.
|
|
71
|
+
|
|
72
|
+
Retries are opt-in:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
channel = nitlsconfig.create_grpc_client_channel(
|
|
76
|
+
"localhost", 31763, retry_policy=nitlsconfig.RetryPolicy()
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`TlsConfigurationError` is raised when TLS is enabled but the configuration is
|
|
81
|
+
unusable. Accessing any of these names without the `grpc` extra installed raises
|
|
82
|
+
`ImportError` telling you which extra to install.
|
|
83
|
+
|
|
84
|
+
## Reading configurations
|
|
85
|
+
```python
|
|
86
|
+
import nitlsconfig
|
|
87
|
+
|
|
88
|
+
# List configured services
|
|
89
|
+
clients = nitlsconfig.ClientConfig.list_services()
|
|
90
|
+
servers = nitlsconfig.ServerConfig.list_services()
|
|
91
|
+
|
|
92
|
+
if clients:
|
|
93
|
+
# Read one client configuration
|
|
94
|
+
client_info = nitlsconfig.ClientConfig(clients[0])
|
|
95
|
+
print(client_info.service_name)
|
|
96
|
+
print(client_info.certificate_mode)
|
|
97
|
+
print(client_info.certificate_chain_location.scheme)
|
|
98
|
+
print(client_info.certificate_chain_location.path)
|
|
99
|
+
print(client_info.certificate_chain_contents)
|
|
100
|
+
|
|
101
|
+
# Inspect target-specific configurations
|
|
102
|
+
for known_server in client_info.known_servers:
|
|
103
|
+
print(known_server.server_name)
|
|
104
|
+
print(known_server.server_mode)
|
|
105
|
+
print(known_server.trusted_certificates_location)
|
|
106
|
+
|
|
107
|
+
if servers:
|
|
108
|
+
# Read one server configuration
|
|
109
|
+
server_info = nitlsconfig.ServerConfig(servers[0])
|
|
110
|
+
print(server_info.service_name)
|
|
111
|
+
print(server_info.certificate_mode)
|
|
112
|
+
print(server_info.client_mode)
|
|
113
|
+
print(server_info.certificate_chain_location.scheme)
|
|
114
|
+
print(server_info.certificate_key_location.scheme)
|
|
115
|
+
print(server_info.trusted_certificates_location.scheme)
|
|
116
|
+
print(server_info.trusted_certificates_contents)
|
|
117
|
+
print(server_info.certificate_key_contents)
|
|
118
|
+
|
|
119
|
+
# Enumerate trusted certificates
|
|
120
|
+
for cert in server_info.trusted_certificates:
|
|
121
|
+
print(cert.display_name)
|
|
122
|
+
print(cert.trusted_certificate_location.path)
|
|
123
|
+
print(cert.trusted_certificate_contents)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# nitlsconfig
|
|
2
|
+
|
|
3
|
+
Python API that reads nitlsconfig configurations through the `nitlsconfig` command line,
|
|
4
|
+
and builds gRPC client channels from them.
|
|
5
|
+
|
|
6
|
+
Installed and imported as `nitlsconfig`; developed at
|
|
7
|
+
[ni/nitlsconfig-python](https://github.com/ni/nitlsconfig-python).
|
|
8
|
+
|
|
9
|
+
## Runtime dependencies
|
|
10
|
+
|
|
11
|
+
- nitlsconfig executable, discoverable, or explicit path to NITLSCONFIG_CLI
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Reading NI-TLS configuration is pure Python and has no third-party dependencies:
|
|
16
|
+
|
|
17
|
+
- `pip install nitlsconfig`
|
|
18
|
+
|
|
19
|
+
The gRPC channel factory additionally needs grpcio, which is an optional extra:
|
|
20
|
+
|
|
21
|
+
- `pip install nitlsconfig[grpc]`
|
|
22
|
+
|
|
23
|
+
## Creating a gRPC channel
|
|
24
|
+
|
|
25
|
+
`create_grpc_client_channel` reads the local NI-TLS client configuration and returns a
|
|
26
|
+
`grpc.Channel` secured accordingly. The `server_address` hostname or address is used to
|
|
27
|
+
select matching target-specific NI-TLS settings. Pass the channel straight to any NI
|
|
28
|
+
gRPC Python API:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import nidcpower
|
|
32
|
+
import nitlsconfig
|
|
33
|
+
|
|
34
|
+
with nitlsconfig.create_grpc_client_channel("localhost", 31763) as channel:
|
|
35
|
+
options = nidcpower.GrpcSessionOptions(channel, "")
|
|
36
|
+
with nidcpower.Session("Dev1", grpc_options=options) as session:
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The channel is mutually authenticated, one-way TLS, or insecure depending on how
|
|
41
|
+
the machine is configured; no code change is needed to move between them. The
|
|
42
|
+
channel is owned by the caller - NI driver APIs never close it.
|
|
43
|
+
|
|
44
|
+
Retries are opt-in:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
channel = nitlsconfig.create_grpc_client_channel(
|
|
48
|
+
"localhost", 31763, retry_policy=nitlsconfig.RetryPolicy()
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`TlsConfigurationError` is raised when TLS is enabled but the configuration is
|
|
53
|
+
unusable. Accessing any of these names without the `grpc` extra installed raises
|
|
54
|
+
`ImportError` telling you which extra to install.
|
|
55
|
+
|
|
56
|
+
## Reading configurations
|
|
57
|
+
```python
|
|
58
|
+
import nitlsconfig
|
|
59
|
+
|
|
60
|
+
# List configured services
|
|
61
|
+
clients = nitlsconfig.ClientConfig.list_services()
|
|
62
|
+
servers = nitlsconfig.ServerConfig.list_services()
|
|
63
|
+
|
|
64
|
+
if clients:
|
|
65
|
+
# Read one client configuration
|
|
66
|
+
client_info = nitlsconfig.ClientConfig(clients[0])
|
|
67
|
+
print(client_info.service_name)
|
|
68
|
+
print(client_info.certificate_mode)
|
|
69
|
+
print(client_info.certificate_chain_location.scheme)
|
|
70
|
+
print(client_info.certificate_chain_location.path)
|
|
71
|
+
print(client_info.certificate_chain_contents)
|
|
72
|
+
|
|
73
|
+
# Inspect target-specific configurations
|
|
74
|
+
for known_server in client_info.known_servers:
|
|
75
|
+
print(known_server.server_name)
|
|
76
|
+
print(known_server.server_mode)
|
|
77
|
+
print(known_server.trusted_certificates_location)
|
|
78
|
+
|
|
79
|
+
if servers:
|
|
80
|
+
# Read one server configuration
|
|
81
|
+
server_info = nitlsconfig.ServerConfig(servers[0])
|
|
82
|
+
print(server_info.service_name)
|
|
83
|
+
print(server_info.certificate_mode)
|
|
84
|
+
print(server_info.client_mode)
|
|
85
|
+
print(server_info.certificate_chain_location.scheme)
|
|
86
|
+
print(server_info.certificate_key_location.scheme)
|
|
87
|
+
print(server_info.trusted_certificates_location.scheme)
|
|
88
|
+
print(server_info.trusted_certificates_contents)
|
|
89
|
+
print(server_info.certificate_key_contents)
|
|
90
|
+
|
|
91
|
+
# Enumerate trusted certificates
|
|
92
|
+
for cert in server_info.trusted_certificates:
|
|
93
|
+
print(cert.display_name)
|
|
94
|
+
print(cert.trusted_certificate_location.path)
|
|
95
|
+
print(cert.trusted_certificate_contents)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nitlsconfig"
|
|
3
|
+
version = "1.0.0a1"
|
|
4
|
+
license = "MIT"
|
|
5
|
+
description = "Python API for reading nitlsconfig configurations and creating gRPC client channels from them"
|
|
6
|
+
authors = [{name = "NI", email = "opensource@ni.com"}]
|
|
7
|
+
maintainers = [
|
|
8
|
+
{name = "Philip Thong", email = "philip.thong@emerson.com"},
|
|
9
|
+
]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
keywords = ["nitlsconfig", "tls", "mtls", "grpc", "configuration"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: Microsoft :: Windows",
|
|
17
|
+
"Operating System :: POSIX",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
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
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.9"
|
|
27
|
+
dynamic = ["dependencies"]
|
|
28
|
+
|
|
29
|
+
# Reading NI-TLS configuration is pure Python. Only nitlsconfig.grpc_channel
|
|
30
|
+
# needs grpcio, so the binary wheel is opt-in.
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
grpc = ["grpcio>=1.49.0,<2.0"]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
repository = "https://github.com/ni/nitlsconfig-python"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["poetry-core>=1.9.0"]
|
|
39
|
+
build-backend = "poetry.core.masonry.api"
|
|
40
|
+
|
|
41
|
+
[tool.poetry]
|
|
42
|
+
packages = [{ include = "nitlsconfig", from = "src" }]
|
|
43
|
+
requires-poetry = '>=2.1,<3.0'
|
|
44
|
+
|
|
45
|
+
[tool.poetry.dependencies]
|
|
46
|
+
python = ">=3.9,<4.0"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
[tool.poetry.group.lint.dependencies]
|
|
50
|
+
bandit = { version = ">=1.7", extras = ["toml"] }
|
|
51
|
+
ni-python-styleguide = ">=0.4.1"
|
|
52
|
+
mypy = ">=1.0"
|
|
53
|
+
pyright = { version = ">=1.1.400", extras = ["nodejs"] }
|
|
54
|
+
|
|
55
|
+
[tool.poetry.group.test.dependencies]
|
|
56
|
+
pytest = ">=7.2"
|
|
57
|
+
pytest-cov = ">=4.0"
|
|
58
|
+
pytest-mock = ">=3.0"
|
|
59
|
+
pytest-env = ">=1.0,<2.0"
|
|
60
|
+
cryptography = ">=41.0"
|
|
61
|
+
|
|
62
|
+
[tool.poetry.group.docs]
|
|
63
|
+
optional = true
|
|
64
|
+
|
|
65
|
+
[tool.poetry.group.docs.dependencies]
|
|
66
|
+
# The latest Sphinx requires a recent Python version.
|
|
67
|
+
Sphinx = [
|
|
68
|
+
{ version = ">=8.1", python = ">=3.10,<3.11" },
|
|
69
|
+
{ version = ">=8.2", python = "^3.11" },
|
|
70
|
+
]
|
|
71
|
+
sphinx-rtd-theme = ">=1.0.0"
|
|
72
|
+
sphinx-autoapi = ">=1.8.4"
|
|
73
|
+
m2r2 = ">=0.3.2"
|
|
74
|
+
toml = ">=0.10.2"
|
|
75
|
+
|
|
76
|
+
[tool.poetry.scripts]
|
|
77
|
+
nitlsconfig-read = "nitlsconfig.cli:nitlsconfig_main"
|
|
78
|
+
|
|
79
|
+
[tool.pytest_env]
|
|
80
|
+
env_files = [".env"]
|
|
81
|
+
|
|
82
|
+
[tool.ni-python-styleguide]
|
|
83
|
+
extend_exclude = ".tox,docs"
|
|
84
|
+
application-import-names = "nitlsconfig,tests"
|
|
85
|
+
|
|
86
|
+
[tool.black]
|
|
87
|
+
extend-exclude = '\.tox/|docs/'
|
|
88
|
+
line-length = 100
|
|
89
|
+
|
|
90
|
+
[tool.mypy]
|
|
91
|
+
files = "src/nitlsconfig/,tests/"
|
|
92
|
+
namespace_packages = true
|
|
93
|
+
strict = true
|
|
94
|
+
|
|
95
|
+
[[tool.mypy.overrides]]
|
|
96
|
+
# grpcio does not ship a py.typed marker.
|
|
97
|
+
module = "grpc.*"
|
|
98
|
+
ignore_missing_imports = true
|
|
99
|
+
|
|
100
|
+
[tool.bandit]
|
|
101
|
+
skips = [
|
|
102
|
+
"B101", # assert_used
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[tool.pytest.ini_options]
|
|
106
|
+
addopts = "--doctest-modules --strict-markers"
|
|
107
|
+
testpaths = ["src/nitlsconfig", "tests"]
|
|
108
|
+
|
|
109
|
+
[tool.pyright]
|
|
110
|
+
include = ["src/", "tests/"]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Python package to read settings from nitlsconfig and build connections from them.
|
|
2
|
+
|
|
3
|
+
Reading configuration is pure Python and has no third-party dependencies. The
|
|
4
|
+
gRPC channel factory needs grpcio, which is an optional extra::
|
|
5
|
+
|
|
6
|
+
pip install nitlsconfig[grpc]
|
|
7
|
+
|
|
8
|
+
The gRPC names below are therefore resolved lazily: importing this package never
|
|
9
|
+
imports grpcio, so a caller that only reads NI-TLS configuration does not pay
|
|
10
|
+
for a binary dependency it will not use. Additional transports can be added the
|
|
11
|
+
same way without changing what a bare install requires.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from importlib.metadata import version
|
|
15
|
+
from importlib.util import find_spec
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
from nitlsconfig.cli import (
|
|
19
|
+
CertificateLocation,
|
|
20
|
+
ClientCertMode,
|
|
21
|
+
ClientConfig,
|
|
22
|
+
ClientServerMode,
|
|
23
|
+
CommandFailedError,
|
|
24
|
+
ExecutableNotFoundError,
|
|
25
|
+
InvalidOutputError,
|
|
26
|
+
LocationScheme,
|
|
27
|
+
NitlsconfigCliError,
|
|
28
|
+
ServerCertMode,
|
|
29
|
+
ServerClientMode,
|
|
30
|
+
ServerConfig,
|
|
31
|
+
TrustedCertificateData,
|
|
32
|
+
KnownServerData,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
# Imported eagerly for type checkers and editors, which do not run __getattr__.
|
|
37
|
+
from nitlsconfig.grpc_channel import (
|
|
38
|
+
DEFAULT_SERVICE_NAME,
|
|
39
|
+
RetryPolicy,
|
|
40
|
+
TlsConfigurationError,
|
|
41
|
+
create_grpc_client_channel,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__version__ = version("nitlsconfig")
|
|
45
|
+
|
|
46
|
+
# Names re-exported from nitlsconfig.grpc_channel, which requires grpcio.
|
|
47
|
+
# A plain list literal, because pyright only tracks __all__ through a small set
|
|
48
|
+
# of literal forms; anything computed makes it give up on the export list.
|
|
49
|
+
_GRPC_EXPORTS = [
|
|
50
|
+
"DEFAULT_SERVICE_NAME",
|
|
51
|
+
"RetryPolicy",
|
|
52
|
+
"TlsConfigurationError",
|
|
53
|
+
"create_grpc_client_channel",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"__version__",
|
|
58
|
+
"CertificateLocation",
|
|
59
|
+
"ClientCertMode",
|
|
60
|
+
"ClientConfig",
|
|
61
|
+
"ClientServerMode",
|
|
62
|
+
"LocationScheme",
|
|
63
|
+
"ServerCertMode",
|
|
64
|
+
"ServerClientMode",
|
|
65
|
+
"ServerConfig",
|
|
66
|
+
"NitlsconfigCliError",
|
|
67
|
+
"ExecutableNotFoundError",
|
|
68
|
+
"CommandFailedError",
|
|
69
|
+
"InvalidOutputError",
|
|
70
|
+
"TrustedCertificateData",
|
|
71
|
+
"KnownServerData",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# The gRPC names are public API, but only on an install that can supply them.
|
|
75
|
+
# Listing them unconditionally would make `from nitlsconfig import *` raise
|
|
76
|
+
# ImportError without the grpc extra, since star-import resolves every name in
|
|
77
|
+
# __all__. find_spec only locates grpcio; it does not import it, so the lazy
|
|
78
|
+
# __getattr__ below still decides when grpcio is actually loaded.
|
|
79
|
+
if find_spec("grpc") is not None:
|
|
80
|
+
# pyright only tracks __all__ through inline literals, so it cannot follow
|
|
81
|
+
# this and warns that the export list may be incomplete. The TYPE_CHECKING
|
|
82
|
+
# block above already declares these names for static consumers.
|
|
83
|
+
__all__ += _GRPC_EXPORTS # pyright: ignore[reportUnsupportedDunderAll]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def __getattr__(name: str) -> Any:
|
|
87
|
+
"""Resolve gRPC exports on first use, so importing this package does not need grpcio."""
|
|
88
|
+
if name in _GRPC_EXPORTS:
|
|
89
|
+
try:
|
|
90
|
+
from nitlsconfig import grpc_channel
|
|
91
|
+
except ImportError as exc: # pragma: no cover - requires an install without the extra
|
|
92
|
+
raise ImportError(
|
|
93
|
+
f"nitlsconfig.{name} requires grpcio, which is not installed. "
|
|
94
|
+
"Install it with: pip install nitlsconfig[grpc]"
|
|
95
|
+
) from exc
|
|
96
|
+
return getattr(grpc_channel, name)
|
|
97
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|