portforward 0.6.2__tar.gz → 0.7.1__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.

Potentially problematic release.


This version of portforward might be problematic. Click here for more details.

Files changed (37) hide show
  1. {portforward-0.6.2 → portforward-0.7.1}/.github/workflows/release.yml +3 -3
  2. {portforward-0.6.2 → portforward-0.7.1}/Cargo.lock +2 -2
  3. {portforward-0.6.2 → portforward-0.7.1}/Cargo.toml +2 -2
  4. {portforward-0.6.2 → portforward-0.7.1}/HISTORY.rst +9 -0
  5. {portforward-0.6.2 → portforward-0.7.1}/PKG-INFO +2 -2
  6. {portforward-0.6.2 → portforward-0.7.1}/README.rst +1 -1
  7. {portforward-0.6.2 → portforward-0.7.1}/pyproject.toml +2 -2
  8. {portforward-0.6.2 → portforward-0.7.1}/python/portforward/__init__.py +10 -10
  9. {portforward-0.6.2 → portforward-0.7.1}/src/portforward.rs +5 -3
  10. {portforward-0.6.2 → portforward-0.7.1}/tests/test_portforward.py +27 -2
  11. {portforward-0.6.2 → portforward-0.7.1}/.editorconfig +0 -0
  12. {portforward-0.6.2 → portforward-0.7.1}/.github/ISSUE_TEMPLATE.md +0 -0
  13. {portforward-0.6.2 → portforward-0.7.1}/.github/workflows/python-app.yml +0 -0
  14. {portforward-0.6.2 → portforward-0.7.1}/.gitignore +0 -0
  15. {portforward-0.6.2 → portforward-0.7.1}/.readthedocs.yml +0 -0
  16. {portforward-0.6.2 → portforward-0.7.1}/AUTHORS.rst +0 -0
  17. {portforward-0.6.2 → portforward-0.7.1}/CONTRIBUTING.rst +0 -0
  18. {portforward-0.6.2 → portforward-0.7.1}/LICENSE +0 -0
  19. {portforward-0.6.2 → portforward-0.7.1}/Makefile +0 -0
  20. {portforward-0.6.2 → portforward-0.7.1}/docs/Makefile +0 -0
  21. {portforward-0.6.2 → portforward-0.7.1}/docs/authors.rst +0 -0
  22. {portforward-0.6.2 → portforward-0.7.1}/docs/conf.py +0 -0
  23. {portforward-0.6.2 → portforward-0.7.1}/docs/contributing.rst +0 -0
  24. {portforward-0.6.2 → portforward-0.7.1}/docs/docs/conf.rst +0 -0
  25. {portforward-0.6.2 → portforward-0.7.1}/docs/docs/modules.rst +0 -0
  26. {portforward-0.6.2 → portforward-0.7.1}/docs/history.rst +0 -0
  27. {portforward-0.6.2 → portforward-0.7.1}/docs/index.rst +0 -0
  28. {portforward-0.6.2 → portforward-0.7.1}/docs/installation.rst +0 -0
  29. {portforward-0.6.2 → portforward-0.7.1}/docs/make.bat +0 -0
  30. {portforward-0.6.2 → portforward-0.7.1}/docs/modules.rst +0 -0
  31. {portforward-0.6.2 → portforward-0.7.1}/docs/portforward.rst +0 -0
  32. {portforward-0.6.2 → portforward-0.7.1}/python/portforward/_portforward.pyi +0 -0
  33. {portforward-0.6.2 → portforward-0.7.1}/python/portforward/py.typed +0 -0
  34. {portforward-0.6.2 → portforward-0.7.1}/requirements-dev.txt +0 -0
  35. {portforward-0.6.2 → portforward-0.7.1}/src/lib.rs +0 -0
  36. {portforward-0.6.2 → portforward-0.7.1}/tests/conftest.py +0 -0
  37. {portforward-0.6.2 → portforward-0.7.1}/tests/resources.yaml +0 -0
@@ -21,7 +21,7 @@ jobs:
21
21
  strategy:
22
22
  matrix:
23
23
  target: [x86_64, x86, aarch64]
24
- version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
24
+ version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
25
25
  steps:
26
26
  - uses: actions/checkout@v3
27
27
  - uses: actions/setup-python@v4
@@ -45,7 +45,7 @@ jobs:
45
45
  strategy:
46
46
  matrix:
47
47
  target: [x64, x86]
48
- version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
48
+ version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
49
49
  steps:
50
50
  - uses: actions/checkout@v3
51
51
  - uses: actions/setup-python@v4
@@ -69,7 +69,7 @@ jobs:
69
69
  strategy:
70
70
  matrix:
71
71
  target: [x86_64, aarch64]
72
- version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
72
+ version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
73
73
  steps:
74
74
  - uses: actions/checkout@v3
75
75
  - uses: actions/setup-python@v4
@@ -1,6 +1,6 @@
1
1
  # This file is automatically @generated by Cargo.
2
2
  # It is not intended for manual editing.
3
- version = 3
3
+ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "ahash"
@@ -1142,7 +1142,7 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
1142
1142
 
1143
1143
  [[package]]
1144
1144
  name = "portforward"
1145
- version = "0.6.2"
1145
+ version = "0.7.1"
1146
1146
  dependencies = [
1147
1147
  "anyhow",
1148
1148
  "env_logger",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "portforward"
3
- version = "0.6.2"
3
+ version = "0.7.1"
4
4
  edition = "2021"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -28,7 +28,7 @@ log = "0.4.17"
28
28
  env_logger = "0.10.0"
29
29
  # tokio
30
30
  tokio = { version = "1.27.0", features = ["full"] }
31
- tokio-util = "0.7.0"
31
+ tokio-util = "0.7.1"
32
32
  tokio-stream = { version = "0.1.9", features = ["net"] }
33
33
  # k8s
34
34
  kube = { version = "^0.81.0", default-features = false, features = ["admission"] }
@@ -2,6 +2,15 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 0.7.1 (2024-12-15)
6
+ ------------------
7
+ * Allow "/" in strings
8
+ * Drop support for Python 3.8
9
+
10
+ 0.7.0 (2024-10-18)
11
+ ------------------
12
+ * Allow binding to a local random free port
13
+
5
14
  0.6.2 (2024-06-19)
6
15
  ------------------
7
16
  * Allow defining binding ip
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portforward
3
- Version: 0.6.2
3
+ Version: 0.7.1
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -64,11 +64,11 @@ Wheels are available for:
64
64
 
65
65
  with Python versions:
66
66
 
67
- * 3.8
68
67
  * 3.9
69
68
  * 3.10
70
69
  * 3.11
71
70
  * 3.12
71
+ * 3.13
72
72
 
73
73
  **Requirements for installation from source**
74
74
 
@@ -40,11 +40,11 @@ Wheels are available for:
40
40
 
41
41
  with Python versions:
42
42
 
43
- * 3.8
44
43
  * 3.9
45
44
  * 3.10
46
45
  * 3.11
47
46
  * 3.12
47
+ * 3.13
48
48
 
49
49
  **Requirements for installation from source**
50
50
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "portforward"
7
- version = "0.6.2"
7
+ version = "0.7.1"
8
8
  authors = [{ name = "Sebastian Ziemann", email = "corka149@mailbox.org" }]
9
9
  description = "Easy Kubernetes Port-Forward For Python"
10
10
  readme = "README.rst"
@@ -34,7 +34,7 @@ module-name = "portforward._portforward"
34
34
  python-source = "python"
35
35
 
36
36
  [tool.bumpversion]
37
- current_version = "0.6.2"
37
+ current_version = "0.7.1"
38
38
  tag = true
39
39
  commit = true
40
40
 
@@ -2,7 +2,7 @@
2
2
  Easy Kubernetes Port-Forward For Python
3
3
  """
4
4
 
5
- __version__ = "0.6.2"
5
+ __version__ = "0.7.1"
6
6
 
7
7
  import asyncio
8
8
  import contextlib
@@ -57,7 +57,7 @@ def forward(
57
57
 
58
58
  :param namespace: Target namespace
59
59
  :param pod_or_service: Name of target Pod or service
60
- :param from_port: Local port
60
+ :param from_port: Local port, or 0 to use any free port
61
61
  :param to_port: Port inside the pod
62
62
  :param config_path: Path for loading kube config
63
63
  :param waiting: Delay in seconds
@@ -129,6 +129,11 @@ class PortForwarder:
129
129
  def is_stopped(self):
130
130
  return self._async_forwarder.is_stopped
131
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
+
132
137
 
133
138
  class AsyncPortForwarder:
134
139
  """Use the same args as the `portforward.forward` method."""
@@ -158,11 +163,12 @@ class AsyncPortForwarder:
158
163
  bind_ip = _validate_ip_address(bind_ip)
159
164
 
160
165
  self.actual_pod_name: str = ""
166
+ self.from_port: int = 0
161
167
  self._is_stopped: bool = False
162
168
  self.bind_address: str = f"{bind_ip}:{from_port}"
163
169
 
164
170
  async def forward(self):
165
- self.actual_pod_name = await _portforward.forward(
171
+ (self.actual_pod_name, self.from_port) = await _portforward.forward(
166
172
  self.namespace,
167
173
  self.pod_or_service,
168
174
  self.bind_address,
@@ -193,14 +199,11 @@ def _validate_str(arg_name, arg) -> str:
193
199
  if len(arg) == 0:
194
200
  raise ValueError(f"{arg_name} cannot be an empty str")
195
201
 
196
- if "/" in arg:
197
- raise ValueError(f"{arg_name} contains illegal character '/'")
198
-
199
202
  return arg
200
203
 
201
204
 
202
205
  def _validate_port(arg_name, arg) -> int:
203
- in_range = arg and 0 < arg < 65536
206
+ in_range = arg is not None and 0 <= arg < 65536
204
207
  if arg is None or not isinstance(arg, int) or not in_range:
205
208
  raise ValueError(f"{arg_name}={arg} is not a valid port")
206
209
 
@@ -247,7 +250,4 @@ def _kube_context(context):
247
250
  if not isinstance(context, str):
248
251
  raise ValueError(f"kube_context={context} is not a valid str")
249
252
 
250
- if "/" in context:
251
- raise ValueError("kube_context contains illegal character '/'")
252
-
253
253
  return context
@@ -34,9 +34,10 @@ pub struct ForwardConfig {
34
34
  kube_context: String,
35
35
  }
36
36
 
37
- /// Creates a connection to a pod. It returns the actual pod name for the portforward.
37
+ /// Creates a connection to a pod. It returns a `(pod_name, from_port)` tuple
38
+ /// with the actual pod name and local port used for the portforward.
38
39
  /// It differs from `pod_or_service` when `pod_or_service` represents a service.
39
- pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {
40
+ pub async fn forward(config: ForwardConfig) -> anyhow::Result<(String, u16)> {
40
41
  debug!("{:?}", config);
41
42
 
42
43
  let client_config = load_config(&config.config_path, &config.kube_context).await?;
@@ -58,6 +59,7 @@ pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {
58
59
 
59
60
  let addr = SocketAddr::from_str(&config.bind_address).with_context(move || config.bind_address)?;
60
61
  let tcp_listener = TcpListener::bind(addr).await?;
62
+ let from_port = tcp_listener.local_addr()?.port();
61
63
  let forward_task = setup_forward_task(
62
64
  tcp_listener,
63
65
  rx,
@@ -68,7 +70,7 @@ pub async fn forward(config: ForwardConfig) -> anyhow::Result<String> {
68
70
 
69
71
  tokio::spawn(forward_task);
70
72
 
71
- return Ok(q_name.pod_name);
73
+ return Ok((q_name.pod_name, from_port));
72
74
  }
73
75
 
74
76
  async fn load_config(
@@ -124,18 +124,43 @@ def test_service_portforward_with_success(kind_cluster: KindCluster):
124
124
  response: requests.Response = requests.get(url_2)
125
125
  pytest.fail("Portforward should be closed after leaving the context manager")
126
126
 
127
+ def test_portforward_from_port_zero_assigns_port(kind_cluster: KindCluster):
128
+ # Arrange
129
+ _create_test_resources(kind_cluster)
130
+
131
+ pod_name = "test-pod"
132
+ config = str(kind_cluster.kubeconfig_path.absolute())
133
+
134
+ local_port = 0 # from port
135
+ pod_port = 3000 # to port
136
+
137
+ pf = portforward.forward(
138
+ TEST_NAMESPACE,
139
+ pod_name,
140
+ local_port,
141
+ pod_port,
142
+ config_path=config,
143
+ kube_context=TEST_CONTEXT,
144
+ )
145
+
146
+ # Act & Assert
147
+ with pf as forwarder:
148
+ assert not forwarder.is_stopped()
149
+ assert forwarder.from_port != 0
150
+ url = f"http://localhost:{forwarder.from_port}/ping"
151
+ response: requests.Response = requests.get(url)
152
+ assert response.status_code == 200
153
+
127
154
 
128
155
  @pytest.mark.parametrize(
129
156
  "namespace,pod,from_port,to_port",
130
157
  [
131
158
  # Namespace
132
159
  ("", "web", 9000, 80),
133
- ("/test", "web", 9000, 80),
134
160
  (1337, "web", 9000, 80),
135
161
  (None, "web", 9000, 80),
136
162
  # Pod name
137
163
  ("test", "", 9000, 80),
138
- ("test", "web/", 9000, 80),
139
164
  ("test", 1337, 9000, 80),
140
165
  ("test", None, 9000, 80),
141
166
  # From port
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes