Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new wait for idle #1245

Merged
merged 19 commits into from
Dec 20, 2024
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
9 changes: 7 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ jobs:
- "3.4/stable"
- "3.5/stable"
- "3.6/stable"
new_wait_for_idle:
- "True"
- "False"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down Expand Up @@ -116,7 +119,9 @@ jobs:
# # set model defaults
# juju model-defaults apt-http-proxy=$PROXY apt-https-proxy=$PROXY juju-http-proxy=$PROXY juju-https-proxy=$PROXY snap-http-proxy=$PROXY snap-https-proxy=$PROXY
# juju model-defaults
- run: uvx -p ${{ matrix.python }} tox -e integration
- run: uvx -p ${{ matrix.python }} tox -s -e integration
env:
JUJU_NEW_WAIT_FOR_IDLE: ${{ matrix.new_wait_for_idle }}

integration-quarantine:
name: Quarantined Integration Tests
Expand Down Expand Up @@ -144,4 +149,4 @@ jobs:
with:
provider: lxd
juju-channel: ${{ matrix.juju }}
- run: uvx -p ${{ matrix.python }} tox -e integration-quarantine
- run: uvx -p ${{ matrix.python }} tox -s -e integration-quarantine
12 changes: 12 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Changelog
---------

3.6.1.0
^^^^^^^

Friday 20th Dec 2024

## What's Changed
* add 3.5.5 schema and update SCHEMAS.md by @james-garner-canonical in https://github.com/juju/python-libjuju/pull/1223
* feat: larger default websockets frame size by @dimaqq in https://github.com/juju/python-libjuju/pull/1239
* deprecate juju.jasyncio by @EdmilsonRodrigues in https://github.com/juju/python-libjuju/pull/1221
* remove juju.loop, deprecated before 3.0 by @dimaqq in https://github.com/juju/python-libjuju/pull/1242
* new wait for idle implementation, behind a feature flag ``JUJU_NEW_WAIT_FOR_IDLE`` in https://github.com/juju/python-libjuju/pull/1245

3.6.0.0
^^^^^^^

Expand Down
3 changes: 3 additions & 0 deletions docs/readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Bug reports: https://github.com/juju/python-libjuju/issues

Documentation: https://pythonlibjuju.readthedocs.io/en/latest/

Supported Python versions: 3.8 through 3.13
Supported Juju versions: 3.1 through 3.6


Design Notes
------------
Expand Down
72 changes: 45 additions & 27 deletions juju/client/facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from collections import defaultdict
from glob import glob
from pathlib import Path
from typing import Any, Mapping, Sequence
from typing import Any, Mapping, Sequence, TypeVar, overload

import packaging.version
import typing_inspect
Expand Down Expand Up @@ -183,7 +183,7 @@ def ref_type(self, obj):
return self.get_ref_type(obj["$ref"])


CLASSES = {}
CLASSES: dict[str, type[Type]] = {}
factories = codegen.Capture()


Expand Down Expand Up @@ -479,37 +479,52 @@ def ReturnMapping(cls): # noqa: N802
def decorator(f):
@functools.wraps(f)
async def wrapper(*args, **kwargs):
nonlocal cls
reply = await f(*args, **kwargs)
if cls is None:
return reply
if "error" in reply:
cls = CLASSES["Error"]
if typing_inspect.is_generic_type(cls) and issubclass(
typing_inspect.get_origin(cls), Sequence
):
parameters = typing_inspect.get_parameters(cls)
result = []
item_cls = parameters[0]
for item in reply:
result.append(item_cls.from_json(item))
"""
if 'error' in item:
cls = CLASSES['Error']
else:
cls = item_cls
result.append(cls.from_json(item))
"""
else:
result = cls.from_json(reply["response"])

return result
return _convert_response(reply, cls=cls)

return wrapper

return decorator


@overload
def _convert_response(response: dict[str, Any], *, cls: type[SomeType]) -> SomeType: ...


@overload
def _convert_response(response: dict[str, Any], *, cls: None) -> dict[str, Any]: ...


def _convert_response(response: dict[str, Any], *, cls: type[Type] | None) -> Any:
if cls is None:
return response
if "error" in response:
# TODO: I don't think this ever happens,
# errors are handled by Connection.rpc(),
# though, admittedly the shape is different.
cls = CLASSES["Error"]
if typing_inspect.is_generic_type(cls) and issubclass(
typing_inspect.get_origin(cls), Sequence
):
# TODO: I'm not sure this ever happens either.
parameters = typing_inspect.get_parameters(cls)
result = []
item_cls = parameters[0]
for item in response:
result.append(item_cls.from_json(item))
"""
if 'error' in item:
cls = CLASSES['Error']
else:
cls = item_cls
result.append(cls.from_json(item))
"""
else:
result = cls.from_json(response["response"])

return result


def make_func(cls, name, description, params, result, _async=True):
indent = " "
args = Args(cls.schema, params)
Expand Down Expand Up @@ -663,7 +678,7 @@ async def rpc(self, msg: dict[str, _RichJson]) -> _Json:
return result

@classmethod
def from_json(cls, data):
def from_json(cls, data: Type | str | dict[str, Any] | list[Any]) -> Type | None:
def _parse_nested_list_entry(expr, result_dict):
if isinstance(expr, str):
if ">" in expr or ">=" in expr:
Expand Down Expand Up @@ -742,6 +757,9 @@ def get(self, key, default=None):
return getattr(self, attr, default)


SomeType = TypeVar("SomeType", bound=Type)


class Schema(dict):
def __init__(self, schema):
self.name = schema["Name"]
Expand Down
Loading
Loading