Skip to content

Commit

Permalink
Add explicit sync and asyncio APIs (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobtomlinson authored Apr 30, 2023
1 parent b1331f9 commit 6cf29b2
Show file tree
Hide file tree
Showing 20 changed files with 1,710 additions and 1,032 deletions.
38 changes: 38 additions & 0 deletions docs/asyncio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Asyncio

`kr8s` uses `asyncio` under the hood when interacting with the Kubernetes API. However, it exposes a standard synchronous API by default.

```python
import kr8s

api = kr8s.api()
pods = api.get("pods")
```

For users that want it the `asyncio` API is also available via `kr8s.asyncio`.

```python
import kr8s.asyncio

api = kr8s.asyncio.api()
pods = await api.get("pods")
```

Submodules including `kr8s.objects` and `kr8s.portforward` also have `asyncio` equivalents at `kr8s.asyncio.objects` and `kr8s.asyncio.portforward`.

```python
from kr8s.asyncio.object import Pod

pod = Pod({
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "my-pod",
},
"spec": {
"containers": [{"name": "pause", "image": "gcr.io/google_containers/pause",}]
},
})

await pod.create()
```
16 changes: 10 additions & 6 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kr8s

api = kr8s.api()

version = await api.version()
version = api.version()
print(version)
```

Expand All @@ -21,7 +21,7 @@ The client API is inspired by `kubectl` rather than the Kubernetes API directly
import kr8s

api = kr8s.api()
pods = await api.get("pods", namespace=kr8s.ALL)
pods = api.get("pods", namespace=kr8s.ALL)

for pod in pods:
print(pod.name)
Expand All @@ -33,12 +33,16 @@ For situations where there may not be an appropriate method to call or you want

To make API requests for resources more convenience `call_api` allows building the url via various kwargs.

```{note}
Note that `call_api` is only available via the [asyncio API](asyncio).
```

For example to get all pods you could make the following low-level call.

```python
import kr8s
import kr8s.asyncio

api = kr8s.api()
api = kr8s.asyncio.api()
async with api.call_api("GET", url="pods", namespace="") as r:
pods_response = await r.json()

Expand Down Expand Up @@ -87,7 +91,7 @@ api2 = kr8s.api()
```python
from kr8s.objects import Pod

pod = await Pod.get("some-pod")
pod = Pod.get("some-pod")
# pod.api is a pointer to api despite not being passed a reference due to caching
```

Expand All @@ -107,7 +111,7 @@ api2 = kr8s.Api(bypass_factory=True)
```python
from kr8s.objects import Pod
pod = await Pod.get("some-pod", api=api2)
pod = Pod.get("some-pod", api=api2)
# be sure to pass a reference around as caching will no longer work
```
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"show-module-summary",
"imported-members",
]
autoapi_ignore = ["*tests*", "*conftest*"]
autoapi_ignore = ["*tests*", "*conftest*", "*asyncio*"]
# autoapi_python_use_implicit_namespaces = True
autoapi_keep_files = True
# autoapi_generate_api_docs = False
Expand Down
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ $ pip install kr8s
import kr8s

api = kr8s.api()
pods = await api.get("pods")
pods = api.get("pods")
```

### Object API
Expand All @@ -44,7 +44,7 @@ pod = Pod({
},
})

await pod.create()
pod.create()
```


Expand All @@ -62,6 +62,7 @@ installation
authentication
client
object
asyncio
```

```{toctree}
Expand Down
20 changes: 10 additions & 10 deletions docs/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Responses from the Client API are usually objects from [](#kr8s.objects) which r
import kr8s

api = kr8s.api()
pods = await api.get("pods", namespace=kr8s.ALL)
pods = api.get("pods", namespace=kr8s.ALL)
pod = pods[0]
print(type(pod))
# <class 'kr8s.objects.Pod'>
Expand Down Expand Up @@ -50,27 +50,27 @@ Objects also have helper methods for interacting with Kubernetes resources.

```python
# Patch the Pod
await pod.patch({"metadata": {"labels": {"foo": "bar"}}})
pod.patch({"metadata": {"labels": {"foo": "bar"}}})

# Check the Pod exists
await pod.exists()
pod.exists()
# True

# Update the object with the latest state from the API
await pod.refresh()
pod.refresh()

# Delete the Pod
await pod.delete()
pod.delete()
```

Some objects also have additional methods that are unique to them.

```python
# Get Pod logs
logs = await pod.logs()
logs = pod.logs()

# Check if Pod containers are ready
await pod.ready()
pod.ready()
# True
```

Expand All @@ -95,15 +95,15 @@ pod = Pod({
},
})

await pod.create()
pod.create()
```

Get a Pod reference by name.

```python
from kr8s.object import Pod

pod = await Pod.get("my-pod")
pod = Pod.get("my-pod")
```

When creating new objects they will not have a client reference because they are created directly. In this case the object will call the [](#kr8s.api) factory function which will either create a new client if none exists or will grab the first client from the cache if one was created somewhere else in your code.
Expand Down Expand Up @@ -192,7 +192,7 @@ class CustomObject(APIObject):

api = kr8s.api()

cos = await api.get("customobjects") # Will return a list of CustomObject instances
cos = api.get("customobjects") # Will return a list of CustomObject instances
```

```{note}
Expand Down
15 changes: 14 additions & 1 deletion kr8s/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# SPDX-FileCopyrightText: Copyright (c) 2023, Dask Developers, Yuvi Panda, Anaconda Inc, NVIDIA
# SPDX-License-Identifier: BSD 3-Clause License
from ._api import Api, ALL, api # noqa
from functools import partial

from ._api import ALL # noqa
from ._api import Api as _AsyncApi
from ._asyncio import sync as _sync
from ._exceptions import NotFoundError # noqa
from .asyncio import api as _api # noqa

__version__ = "0.0.0"


@_sync
class Api(_AsyncApi):
__doc__ = _AsyncApi.__doc__


api = partial(_api, _asyncio=False)
59 changes: 36 additions & 23 deletions kr8s/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Api(object):
"""

_asyncio = True
_instances = weakref.WeakValueDictionary()

def __init__(self, **kwargs) -> None:
Expand Down Expand Up @@ -141,7 +142,7 @@ async def _get_kind(
watch: bool = False,
) -> dict:
"""Get a Kubernetes resource."""
from .objects import get_class
from ._objects import get_class

if not namespace:
namespace = self.auth.namespace
Expand Down Expand Up @@ -175,6 +176,22 @@ async def get(
field_selector: str = None,
) -> List[object]:
"""Get a Kubernetes resource."""
return await self._get(
kind,
*names,
namespace=namespace,
label_selector=label_selector,
field_selector=field_selector,
)

async def _get(
self,
kind: str,
*names: List[str],
namespace: str = None,
label_selector: str = None,
field_selector: str = None,
) -> List[object]:
async with self._get_kind(
kind,
namespace=namespace,
Expand All @@ -197,6 +214,24 @@ async def watch(
label_selector: str = None,
field_selector: str = None,
since: str = None,
):
"""Watch a Kubernetes resource."""
async for t, object in self._watch(
kind,
namespace=namespace,
label_selector=label_selector,
field_selector=field_selector,
since=since,
):
yield t, object

async def _watch(
self,
kind: str,
namespace: str = None,
label_selector: str = None,
field_selector: str = None,
since: str = None,
):
"""Watch a Kubernetes resource."""
async with self._get_kind(
Expand Down Expand Up @@ -251,25 +286,3 @@ def __version__(self):
from . import __version__

return f"kr8s/{__version__}"


def api(url=None, kubeconfig=None, serviceaccount=None, namespace=None) -> Api:
"""Create a :class:`kr8s.Api` object for interacting with the Kubernetes API.
If a kr8s object already exists with the same arguments, it will be returned.
"""

def _f(**kwargs):
key = frozenset(kwargs.items())
if key in Api._instances:
return Api._instances[key]
if all(k is None for k in kwargs.values()) and list(Api._instances.values()):
return list(Api._instances.values())[0]
return Api(**kwargs, bypass_factory=True)

return _f(
url=url,
kubeconfig=kubeconfig,
serviceaccount=serviceaccount,
namespace=namespace,
)
Loading

0 comments on commit 6cf29b2

Please sign in to comment.