Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ Read a pod
from lightkube import Client
from lightkube.resources.core_v1 import Pod

client = Client()
pod = client.get(Pod, name="my-pod", namespace="default")
print(pod.namespace.uid)
with Client() as client:
pod = client.get(Pod, name="my-pod", namespace="default")
print(pod.namespace.uid)
```

List nodes
Expand All @@ -50,9 +50,9 @@ List nodes
from lightkube import Client
from lightkube.resources.core_v1 import Node

client = Client()
for node in client.list(Node):
print(node.metadata.name)
with Client() as client:
for node in client.list(Node):
print(node.metadata.name)
```

### Create
Expand Down
39 changes: 39 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,45 @@ client = Client(config=config)
* config file defined in `KUBECONFIG` environment variable.
* configuration file present on the default location (`~/.kube/config`).

## Closing the client

Both `Client` and `AsyncClient` hold an underlying httpx connection pool that should be closed
when the client is no longer needed, in order to release open file descriptors.

The recommended approach is to use the client as a context manager:

```python
from lightkube import Client

with Client() as client:
pod = client.get(Pod, name="my-pod")
```

```python
from lightkube import AsyncClient

async with AsyncClient() as client:
pod = await client.get(Pod, name="my-pod")
```

You can also close the client explicitly:

```python
client = Client()
try:
pod = client.get(Pod, name="my-pod")
finally:
client.close()
```

```python
client = AsyncClient()
try:
pod = await client.get(Pod, name="my-pod")
finally:
await client.aclose() # or await client.close()
```

## Proxy configuration

The constructor `KubeConfig.from_server()` will build a simple configuration useful to connect to a non protected
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lightkube"
version = "0.21.0"
version = "0.22.0"
description = "Lightweight kubernetes client library"
readme = "README.md"
authors = [
Expand Down
17 changes: 15 additions & 2 deletions src/lightkube/core/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,19 @@ async def apply(
dry_run=dry_run,
)

async def close(self):
"""Close the underline httpx client"""
async def close(self) -> None:
"""Close the underlying httpx client and release the connection pool."""
await self._client.close()

async def aclose(self) -> None:
"""Close the underlying httpx client and release the connection pool.

Alias for :meth:`close` following the httpx naming convention.
"""
await self.close()

async def __aenter__(self) -> "AsyncClient":
return self

async def __aexit__(self, *args: object) -> None:
await self.close()
10 changes: 10 additions & 0 deletions src/lightkube/core/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ def __init__(
dry_run=dry_run,
)

def close(self) -> None:
"""Close the underlying httpx client and release the connection pool."""
self._client.close()

def __enter__(self) -> "Client":
return self

def __exit__(self, *args: object) -> None:
self.close()

@property
def namespace(self):
"""Return the default namespace that will be used when a namespace has not been specified"""
Expand Down
3 changes: 3 additions & 0 deletions src/lightkube/core/generic_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ def list_chunks(self, br: BasicRequest) -> Iterator[Tuple[str, Iterator]]:
def list(self, br: BasicRequest) -> ListIterable:
return ListIterable(self.list_chunks(br))

def close(self):
self._client.close()


class GenericAsyncClient(GenericClient):
AdapterClient = staticmethod(client_adapter.AsyncClient)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,30 @@ async def test_apply_global(client: lightkube.AsyncClient):
assert node.metadata.name == "xx"
assert req.calls[0][0].headers["Content-Type"] == "application/apply-patch+yaml"
await client.close()


@pytest.mark.asyncio
async def test_close(client: lightkube.AsyncClient) -> None:
httpx_client = client._client._client
assert not httpx_client.is_closed
await client.close()
assert httpx_client.is_closed


@pytest.mark.asyncio
async def test_aclose(kubeconfig) -> None:
config = KubeConfig.from_file(str(kubeconfig))
client = lightkube.AsyncClient(config=config)
httpx_client = client._client._client
assert not httpx_client.is_closed
await client.aclose()
assert httpx_client.is_closed


@pytest.mark.asyncio
async def test_async_context_manager(kubeconfig) -> None:
config = KubeConfig.from_file(str(kubeconfig))
async with lightkube.AsyncClient(config=config) as client:
httpx_client = client._client._client
assert not httpx_client.is_closed
assert httpx_client.is_closed
15 changes: 15 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,3 +882,18 @@ def test_apply_global(client: lightkube.Client) -> None:
node = client.apply(Node(metadata=ObjectMeta(name="xz")), field_manager="test", dry_run=True)
assert node.metadata.name == "xz"
assert req.calls[0][0].url.params["dryRun"] == "All"


def test_close(client: lightkube.Client) -> None:
httpx_client = client._client._client
assert not httpx_client.is_closed
client.close()
assert httpx_client.is_closed


def test_context_manager(kubeconfig: Path) -> None:
config = KubeConfig.from_file(str(kubeconfig))
with lightkube.Client(config=config) as client:
httpx_client = client._client._client
assert not httpx_client.is_closed
assert httpx_client.is_closed
Loading
Loading