portforward 0.7.4__cp312-cp312-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,253 @@
1
+ """
2
+ Easy Kubernetes Port-Forward For Python
3
+ """
4
+
5
+ __version__ = "0.7.4"
6
+
7
+ import asyncio
8
+ import contextlib
9
+ import ipaddress
10
+ import os
11
+ from enum import Enum
12
+ from pathlib import Path
13
+ from typing import Generator, Optional, Union
14
+
15
+ from portforward import _portforward
16
+
17
+
18
+ class PortforwardError(Exception):
19
+ """Will be raised when something went wrong while the port-forward process."""
20
+
21
+
22
+ class LogLevel(Enum):
23
+ DEBUG = 0
24
+ INFO = 1
25
+ WARN = 2
26
+ ERROR = 3
27
+ OFF = 4
28
+
29
+
30
+ @contextlib.contextmanager
31
+ def forward(
32
+ namespace: str,
33
+ pod_or_service: str,
34
+ from_port: int,
35
+ to_port: int,
36
+ config_path: Optional[str] = None,
37
+ waiting: float = 0.1,
38
+ log_level: LogLevel = LogLevel.INFO,
39
+ kube_context: str = "",
40
+ bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
41
+ ) -> Generator["PortForwarder", None, None]:
42
+ """
43
+ Connects to a **pod or service** and tunnels traffic from a local port to
44
+ this target. It uses the kubectl kube config from the home dir if no path
45
+ is provided.
46
+
47
+ The libary will figure out for you if it has to target a pod or service.
48
+
49
+ It will fall back to in-cluster-config in case no kube config file exists.
50
+
51
+ (Best consumed as context manager.)
52
+
53
+ Example:
54
+ >>> import portforward
55
+ >>> with portforward.forward("test", "web-svc", 9000, 80):
56
+ >>> # Do work
57
+
58
+ :param namespace: Target namespace
59
+ :param pod_or_service: Name of target Pod or service
60
+ :param from_port: Local port, or 0 to use any free port
61
+ :param to_port: Port inside the pod
62
+ :param config_path: Path for loading kube config
63
+ :param waiting: Delay in seconds
64
+ :param log_level: Level of logging
65
+ :param kube_context: Target kubernetes context (fallback is current context)
66
+ :param bind_ip: To which IP shall the portforward be bind
67
+ :return: forwarder to manual stop the forwarding
68
+ """
69
+
70
+ forwarder = PortForwarder(
71
+ namespace,
72
+ pod_or_service,
73
+ from_port,
74
+ to_port,
75
+ config_path,
76
+ waiting,
77
+ log_level,
78
+ kube_context,
79
+ bind_ip,
80
+ )
81
+
82
+ try:
83
+ forwarder.forward()
84
+
85
+ yield forwarder
86
+
87
+ except RuntimeError as err:
88
+ # Suppress extension exception
89
+ raise PortforwardError(err) from None
90
+
91
+ finally:
92
+ forwarder.stop()
93
+
94
+
95
+ class PortForwarder:
96
+ """Use the same args as the `portforward.forward` method."""
97
+
98
+ def __init__(
99
+ self,
100
+ namespace: str,
101
+ pod_or_service: str,
102
+ from_port: int,
103
+ to_port: int,
104
+ config_path: Optional[str] = None,
105
+ waiting: float = 0.1,
106
+ log_level: LogLevel = LogLevel.INFO,
107
+ kube_context: str = "",
108
+ bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
109
+ ) -> None:
110
+ self._async_forwarder = AsyncPortForwarder(
111
+ namespace,
112
+ pod_or_service,
113
+ from_port,
114
+ to_port,
115
+ config_path,
116
+ waiting,
117
+ log_level,
118
+ kube_context,
119
+ bind_ip,
120
+ )
121
+
122
+ def forward(self):
123
+ asyncio.run(self._async_forwarder.forward())
124
+
125
+ def stop(self):
126
+ asyncio.run(self._async_forwarder.stop())
127
+
128
+ @property
129
+ def is_stopped(self):
130
+ return self._async_forwarder.is_stopped
131
+
132
+ @property
133
+ def from_port(self):
134
+ """The local port that was actually used for the portforward."""
135
+ return self._async_forwarder.from_port
136
+
137
+
138
+ class AsyncPortForwarder:
139
+ """Use the same args as the `portforward.forward` method."""
140
+
141
+ def __init__(
142
+ self,
143
+ namespace: str,
144
+ pod_or_service: str,
145
+ from_port: int,
146
+ to_port: int,
147
+ config_path: Optional[str] = None,
148
+ waiting: float = 0.1,
149
+ log_level: LogLevel = LogLevel.INFO,
150
+ kube_context: str = "",
151
+ bind_ip: Union[ipaddress.IPv4Address, ipaddress.IPv6Address, str, None] = None,
152
+ ) -> None:
153
+ self.namespace: str = _validate_str("namespace", namespace)
154
+ self.pod_or_service: str = _validate_str("pod_or_service", pod_or_service)
155
+ self.to_port: int = _validate_port("to_port", to_port)
156
+ self.log_level: LogLevel = _validate_log(log_level)
157
+ self.waiting: float = waiting
158
+
159
+ self.config_path: str = _config_path(config_path)
160
+ self.kube_context: str = _kube_context(kube_context)
161
+
162
+ _validate_port("from_port", from_port)
163
+ bind_ip = _validate_ip_address(bind_ip)
164
+
165
+ self.actual_pod_name: str = ""
166
+ self.from_port: int = 0
167
+ self._is_stopped: bool = False
168
+ self.bind_address: str = f"{bind_ip}:{from_port}"
169
+
170
+ async def forward(self):
171
+ (self.actual_pod_name, self.from_port) = await _portforward.forward(
172
+ self.namespace,
173
+ self.pod_or_service,
174
+ self.bind_address,
175
+ self.to_port,
176
+ self.config_path,
177
+ self.log_level.value,
178
+ self.kube_context,
179
+ )
180
+ self._is_stopped = False
181
+
182
+ async def stop(self):
183
+ await _portforward.stop(
184
+ self.namespace, self.actual_pod_name, self.to_port, self.log_level.value
185
+ )
186
+ self._is_stopped = True
187
+
188
+ def is_stopped(self):
189
+ return self._is_stopped
190
+
191
+
192
+ # ===== PRIVATE =====
193
+
194
+
195
+ def _validate_str(arg_name, arg) -> str:
196
+ if arg is None or not isinstance(arg, str):
197
+ raise ValueError(f"{arg_name}={arg} is not a valid str")
198
+
199
+ if len(arg) == 0:
200
+ raise ValueError(f"{arg_name} cannot be an empty str")
201
+
202
+ return arg
203
+
204
+
205
+ def _validate_port(arg_name, arg) -> int:
206
+ in_range = arg is not None and 0 <= arg < 65536
207
+ if arg is None or not isinstance(arg, int) or not in_range:
208
+ raise ValueError(f"{arg_name}={arg} is not a valid port")
209
+
210
+ return arg
211
+
212
+
213
+ def _validate_log(log_level):
214
+ if not isinstance(log_level, LogLevel):
215
+ raise ValueError(f"log_level={log_level} is not a valid LogLevel")
216
+
217
+ return log_level
218
+
219
+
220
+ def _validate_ip_address(ip_address):
221
+ if not ip_address:
222
+ return "127.0.0.1"
223
+
224
+ if isinstance(ip_address, ipaddress.IPv4Address) or isinstance(
225
+ ip_address, ipaddress.IPv4Address
226
+ ):
227
+ return str(ip_address)
228
+
229
+ return str(ipaddress.ip_address(ip_address))
230
+
231
+
232
+ def _config_path(config_path_arg) -> str:
233
+ if config_path_arg and not isinstance(config_path_arg, str):
234
+ raise ValueError(f"config_path={config_path_arg} is not a valid str")
235
+
236
+ elif config_path_arg:
237
+ return config_path_arg
238
+
239
+ alt_path = str(Path.home() / ".kube" / "config")
240
+
241
+ config_path = os.environ.get("KUBECONFIG", alt_path)
242
+
243
+ return config_path if os.path.isfile(config_path) else ""
244
+
245
+
246
+ def _kube_context(context):
247
+ if not context:
248
+ return ""
249
+
250
+ if not isinstance(context, str):
251
+ raise ValueError(f"kube_context={context} is not a valid str")
252
+
253
+ return context
@@ -0,0 +1,17 @@
1
+ """
2
+ Rust native module / Python C Extension
3
+ """
4
+
5
+ async def forward(
6
+ namespace: str,
7
+ pod_or_service: str,
8
+ bind_address: str,
9
+ to_port: int,
10
+ config_path: str,
11
+ log_level: int,
12
+ kube_context: str,
13
+ ) -> None:
14
+ pass
15
+
16
+ async def stop(namespace: str, actual_pod: str, to_port: int, log_level: int) -> None:
17
+ pass
portforward/py.typed ADDED
File without changes
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: portforward
3
+ Version: 0.7.4
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.7
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ License-File: LICENSE
17
+ License-File: AUTHORS.rst
18
+ Summary: Easy Kubernetes Port-Forward For Python
19
+ Keywords: portforward,kubernetes,k8s
20
+ Author-email: Sebastian Ziemann <corka149@mailbox.org>
21
+ License: MIT License
22
+ Requires-Python: >=3.7
23
+ Description-Content-Type: text/x-rst; charset=UTF-8
24
+ Project-URL: Documentation, https://portforward.readthedocs.io
25
+ Project-URL: Repository, https://github.com/pytogo/portforward.git
26
+ Project-URL: Changelog, https://github.com/pytogo/portforward/blob/main/HISTORY.rst
27
+
28
+ ===========
29
+ portforward
30
+ ===========
31
+
32
+
33
+ .. image:: https://img.shields.io/pypi/v/portforward.svg
34
+ :target: https://pypi.python.org/pypi/portforward
35
+
36
+ .. image:: https://img.shields.io/pypi/status/portforward.svg
37
+ :target: https://pypi.python.org/pypi/portforward
38
+
39
+ .. image:: https://img.shields.io/pypi/dm/portforward
40
+ :alt: PyPI - Downloads
41
+
42
+ .. image:: https://readthedocs.org/projects/portforward/badge/?version=latest
43
+ :target: https://portforward.readthedocs.io/en/latest/?version=latest
44
+ :alt: Documentation Status
45
+
46
+ .. image:: https://github.com/pytogo/portforward/actions/workflows/python-app.yml/badge.svg
47
+ :target: https://github.com/pytogo/portforward/actions
48
+ :alt: Build status
49
+
50
+
51
+
52
+ Easy Kubernetes Port-Forward For Python
53
+
54
+
55
+ * Free software: MIT license
56
+ * Documentation: https://portforward.readthedocs.io.
57
+
58
+
59
+ Installation
60
+ -----------------------------
61
+
62
+ Wheels are available for:
63
+
64
+ * **Windows** (architectures: ``x64``, ``x86``)
65
+ * **macOS** (architectures: ``x86_64``, ``aarch64``)
66
+ * **Linux** (architectures: ``x86_64``, ``x86``, ``aarch64``, ``armv7``, ``s390x``, ``ppc64le``)
67
+
68
+ Musllinux wheels (Alpine‑compatible) are provided for ``x86_64``, ``x86``, ``aarch64`` and ``armv7``.
69
+
70
+ with Python versions:
71
+
72
+ * 3.9
73
+ * 3.10
74
+ * 3.11
75
+ * 3.12
76
+ * 3.13
77
+
78
+ **Requirements for installation from source**
79
+
80
+ The following things are required when there is no wheel available for the target system.
81
+
82
+ * `Rust` installed and available in the path (https://www.rust-lang.org/tools/install)
83
+ * `Python` (at least v3.7 - below was never tested but might work)
84
+
85
+ Pip knows how to install ``portforward``.
86
+
87
+ .. code-block::
88
+
89
+ pip install portforward
90
+
91
+
92
+ Quickstart
93
+ ----------
94
+
95
+ .. code-block:: Python
96
+
97
+ import requests
98
+
99
+ import portforward
100
+
101
+
102
+ def main():
103
+ namespace = "test"
104
+ pod_name = "web" # You can also use a service name instead
105
+ local_port = 9000 # from port
106
+ pod_port = 80 # to port
107
+
108
+ # No path to kube config provided - will use default from $HOME/.kube/config
109
+ with portforward.forward(namespace, pod_name, local_port, pod_port):
110
+ response = requests.get("http://localhost:9000")
111
+ print(f"Done: \n'{response.status_code}'\n'{response.text[:20]}...'")
112
+
113
+
114
+ if __name__ == "__main__":
115
+ main()
116
+
117
+
118
+ Features
119
+ --------
120
+
121
+ * Native Kubernetes port-forwarding with the ``.kube/config`` from the home dir
122
+ or any other path to config.
123
+ * Portforward for pods and services - the lib will first look for a pod with matching name then for
124
+ a service
125
+ * Waiting for a pod to become ready
126
+ * Multiple forwards per pod or service
127
+ * As context manager, sync or async client
128
+
129
+
130
+ Development
131
+ -----------
132
+
133
+ In case you want to develop on this library itself please take a look at the CONTRIBUTING page.
134
+
135
+ Credits
136
+ -------
137
+
138
+ This project is enabled by PyO3_.
139
+
140
+ .. _PyO3: https://pyo3.rs
141
+
@@ -0,0 +1,10 @@
1
+ portforward-0.7.4.dist-info/METADATA,sha256=-OQ4-uTwnz8ThazX_5DSGK8BrtWP-i-31QXM4Xtm-wg,4069
2
+ portforward-0.7.4.dist-info/WHEEL,sha256=Ap6nlw0cH0rhF7wSMkRKCr81qRCCUY4y7-Tz6vs1yZ4,108
3
+ portforward-0.7.4.dist-info/licenses/AUTHORS.rst,sha256=YQwE3_FUEuGVHbxYUa4NQMlG28QBGwcCYEoa2MP9Xgk,163
4
+ portforward-0.7.4.dist-info/licenses/LICENSE,sha256=n2aq0UH0YZkyF_5hougjNi5gzXIUapEEqEUn6FwAcFE,1075
5
+ portforward.libs/libgcc_s-98a1ef30.so.1,sha256=XOVRhHznCIpbSdhFoozhla-OfRqBtXftKPQ4cSMKjrs,433441
6
+ portforward/__init__.py,sha256=ApNMJAC2afdE3BvXXEHJA0aZ6AnXhLONcwfkVYLdS78,6872
7
+ portforward/_portforward.cpython-312-x86_64-linux-musl.so,sha256=hvpyKOE7KQ_3f2OvUD-sMFnlPIaQD2bn1c6ge1Mp4w0,16392825
8
+ portforward/_portforward.pyi,sha256=KZSR_2INAUDtpwbVFA9e6cLs3KBwgMw-hgtirmMRZgY,336
9
+ portforward/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ portforward-0.7.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.10.2)
3
+ Root-Is-Purelib: false
4
+ Tag: cp312-cp312-musllinux_1_2_x86_64
@@ -0,0 +1,13 @@
1
+ =======
2
+ Credits
3
+ =======
4
+
5
+ Development Lead
6
+ ----------------
7
+
8
+ * Sebastian Ziemann <corka149@mailbox.org>
9
+
10
+ Contributors
11
+ ------------
12
+
13
+ None yet. Why not be the first?
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021, Sebastian Ziemann
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.
Binary file