def test_on_delete_minimal(cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.DELETE) @kopf.on.delete('group', 'version', 'plural') def fn(**_): pass with pytest.deprecated_call(match=r"cease using the internal registries"): handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE assert handlers[0].field is None assert handlers[0].timeout is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_on_create_minimal(mocker): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) @kopf.on.create('group', 'version', 'plural') def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].errors is None assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].cooldown is None assert handlers[0].labels is None assert handlers[0].annotations is None
def test_resumes_ignored_for_non_initial_causes(reason, deleted, cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory( resource=resource, reason=reason, initial=False, body={'metadata': { 'deletionTimestamp': '...' } if deleted else {}}) @kopf.on.resume('group', 'version', 'plural') def fn(**_): pass with pytest.deprecated_call( match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 0
def test_on_field_minimal(mocker): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) @kopf.on.field('group', 'version', 'plural', 'field.subfield') def fn(**_): pass handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].timeout is None assert handlers[0].labels is None assert handlers[0].annotations is None
def test_resumes_selected_for_initial_deletions_when_explicitly_marked( reason, cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=reason, initial=True, body={'metadata': { 'deletionTimestamp': '...' }}) @kopf.on.resume('group', 'version', 'plural', deleted=True) def fn(**_): pass with pytest.deprecated_call( match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn
def test_on_delete_with_cooldown(mocker, optional, cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) with pytest.deprecated_call(match=r"use backoff="): @kopf.on.delete('group', 'version', 'plural', cooldown=78) def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 with pytest.deprecated_call(match=r"use handler.backoff"): assert handlers[0].cooldown == 78
def test_requires_finalizer_multiple_handlers(optional, expected): registry = GlobalRegistry() resource = Resource('group', 'version', 'plural') @kopf.on.create('group', 'version', 'plural', registry=registry) def fn1(**_): pass @kopf.on.delete('group', 'version', 'plural', registry=registry, optional=optional) def fn2(**_): pass with pytest.deprecated_call(match=r"cease using the internal registries"): requires_finalizer = registry.requires_finalizer(resource=resource, cause=CAUSE) assert requires_finalizer == expected
def test_on_resume_with_all_kwargs(mocker, reason): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.resume('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, deleted=True, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].deleted == True assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
async def watch_objs( *, resource: resources.Resource, namespace: Optional[str] = None, timeout: Optional[float] = None, since: Optional[str] = None, session: Optional[auth.APISession] = None, # injected by the decorator ) -> AsyncIterator[bodies.RawEvent]: """ Watch objects of a specific resource type. The cluster-scoped call is used in two cases: * The resource itself is cluster-scoped, and namespacing makes not sense. * The operator serves all namespaces for the namespaced custom resource. Otherwise, the namespace-scoped call is used: * The resource is namespace-scoped AND operator is namespaced-restricted. """ if session is None: raise RuntimeError("API instance is not injected by the decorator.") params: Dict[str, str] = {} params['watch'] = 'true' if since is not None: params['resourceVersion'] = since if timeout is not None: params['timeoutSeconds'] = str(timeout) # TODO: also add cluster-wide resource when --namespace is set? response = await session.get( url=resource.get_url(server=session.server, namespace=namespace, params=params), timeout=aiohttp.ClientTimeout(total=None), ) response.raise_for_status() async with response: async for line in response.content: event = cast(bodies.RawEvent, json.loads(line.decode("utf-8"))) yield event
def test_on_delete_with_most_kwargs(mocker, cause_factory, optional): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.DELETE) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.delete('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, optional=optional, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.DELETE assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when assert handlers[0].field is None assert handlers[0].value is None assert handlers[0].old is None assert handlers[0].new is None
def test_on_field_with_all_kwargs(mocker): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
def test_on_resume_minimal(mocker, reason): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False) @kopf.on.resume('group', 'version', 'plural') def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field is None assert handlers[0].errors is None assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_on_field_warns_with_positional(cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') old = {'field': {'subfield': 'old'}} new = {'field': {'subfield': 'new'}} cause = cause_factory(resource=resource, reason=Reason.UPDATE, old=old, new=new, body=new) with pytest.deprecated_call(match=r"Positional field name is deprecated"): @kopf.on.field('group', 'version', 'plural', 'field.subfield') def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].field == ('field', 'subfield')
def test_on_create_minimal(mocker): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) @kopf.on.create('group', 'version', 'plural') def fn(**_): pass with pytest.deprecated_call( match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].timeout is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_on_update_with_all_kwargs(mocker, cause_factory): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.UPDATE) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.update('group', 'version', 'plural', id='id', registry=registry, errors=ErrorsMode.PERMANENT, timeout=123, retries=456, backoff=78, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass with pytest.deprecated_call( match=r"use registry.resource_changing_handlers"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.UPDATE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].errors == ErrorsMode.PERMANENT assert handlers[0].timeout == 123 assert handlers[0].retries == 456 assert handlers[0].backoff == 78 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
async def list_objs_rv( *, resource: resources.Resource, namespace: Optional[str] = None, context: Optional[auth.APIContext] = None, # injected by the decorator ) -> Tuple[Collection[bodies.Body], str]: """ List the objects of specific resource type. The cluster-scoped call is used in two cases: * The resource itself is cluster-scoped, and namespacing makes not sense. * The operator serves all namespaces for the namespaced custom resource. Otherwise, the namespace-scoped call is used: * The resource is namespace-scoped AND operator is namespaced-restricted. """ if context is None: raise RuntimeError("API instance is not injected by the decorator.") is_namespaced = await discovery.is_namespaced(resource=resource, context=context) namespace = namespace if is_namespaced else None response = await context.session.get( url=resource.get_url(server=context.server, namespace=namespace), ) response.raise_for_status() rsp = await response.json() items: List[bodies.Body] = [] resource_version = rsp.get('metadata', {}).get('resourceVersion', None) for item in rsp['items']: if 'kind' in rsp: item.setdefault('kind', rsp['kind'][:-4] if rsp['kind'][-4:] == 'List' else rsp['kind']) if 'apiVersion' in rsp: item.setdefault('apiVersion', rsp['apiVersion']) items.append(item) return items, resource_version
def test_on_resume_with_cooldown(mocker, reason): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=reason, initial=True, deleted=False) mocker.patch('kopf.reactor.registries.match', return_value=True) with pytest.deprecated_call(match=r"use backoff="): @kopf.on.resume('group', 'version', 'plural', cooldown=78) def fn(**_): pass handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].backoff == 78 with pytest.deprecated_call(match=r"use handler.backoff"): assert handlers[0].cooldown == 78
def test_on_create_with_all_kwargs(mocker): registry = GlobalRegistry() resource = Resource('group', 'version', 'plural') cause = mocker.MagicMock(resource=resource, reason=Reason.CREATE) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.create('group', 'version', 'plural', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_state_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].id == 'id' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_on_create_minimal(cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=Reason.CREATE) @kopf.on.create('group', 'version', 'plural') def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason == Reason.CREATE assert handlers[0].field is None assert handlers[0].errors is None assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_on_field_with_all_kwargs(mocker, cause_factory): registry = GlobalRegistry() resource = Resource('group', 'version', 'plural') old = {'field': {'subfield': 'old'}} new = {'field': {'subfield': 'new'}} cause = cause_factory(resource=resource, reason=Reason.UPDATE, old=old, new=new, body=new) mocker.patch('kopf.reactor.registries.match', return_value=True) when = lambda **_: False @kopf.on.field('group', 'version', 'plural', field='field.subfield', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}, when=when) def fn(**_): pass with pytest.deprecated_call(match=r"cease using the internal registries"): handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'} assert handlers[0].when == when
def test_on_field_minimal(cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = cause_factory(resource=resource, reason=Reason.UPDATE, diff=diff) @kopf.on.field('group', 'version', 'plural', 'field.subfield') def fn(**_): pass with pytest.deprecated_call( match=r"use OperatorRegistry.get_resource_changing_handlers\(\)"): handlers = registry.get_cause_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].timeout is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
async def read_obj( *, resource: resources.Resource, namespace: Optional[str] = None, name: Optional[str] = None, default: Union[_T, _UNSET] = _UNSET.token, session: Optional[auth.APISession] = None, # injected by the decorator ) -> Union[bodies.Body, _T]: if session is None: raise RuntimeError("API instance is not injected by the decorator.") try: # TODO: also add cluster-wide resource when --namespace is set? response = await session.get(url=resource.get_url( server=session.server, namespace=namespace, name=name), ) response.raise_for_status() respdata = await response.json() return cast(bodies.Body, respdata) except aiohttp.ClientResponseError as e: if e.status in [403, 404] and not isinstance(default, _UNSET): return default raise
def test_on_field_minimal(cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = cause_factory(resource=resource, reason=Reason.UPDATE, diff=diff) @kopf.on.field('group', 'version', 'plural', 'field.subfield') def fn(**_): pass handlers = registry.resource_changing_handlers[resource].get_handlers( cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field == ('field', 'subfield') assert handlers[0].errors is None assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_on_field_with_all_kwargs(mocker): registry = GlobalRegistry() resource = Resource('group', 'version', 'plural') diff = [('op', ('field', 'subfield'), 'old', 'new')] cause = mocker.MagicMock(resource=resource, reason=Reason.UPDATE, diff=diff) mocker.patch('kopf.reactor.registries.match', return_value=True) @kopf.on.field('group', 'version', 'plural', 'field.subfield', id='id', timeout=123, registry=registry, labels={'somelabel': 'somevalue'}, annotations={'someanno': 'somevalue'}) def fn(**_): pass handlers = registry.get_state_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field ==('field', 'subfield') assert handlers[0].id == 'id/field.subfield' assert handlers[0].timeout == 123 assert handlers[0].labels == {'somelabel': 'somevalue'} assert handlers[0].annotations == {'someanno': 'somevalue'}
def test_on_resume_minimal(reason, cause_factory): registry = kopf.get_default_registry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, reason=reason, initial=True) @kopf.on.resume('group', 'version', 'plural') def fn(**_): pass with pytest.deprecated_call(match=r"cease using the internal registries"): handlers = registry.get_resource_changing_handlers(cause) assert len(handlers) == 1 assert handlers[0].fn is fn assert handlers[0].reason is None assert handlers[0].field is None assert handlers[0].errors is None assert handlers[0].timeout is None assert handlers[0].retries is None assert handlers[0].backoff is None assert handlers[0].labels is None assert handlers[0].annotations is None assert handlers[0].when is None
def test_field_with_oldnew(mocker, cause_factory, decorator, causeargs, handlers_prop): registry = OperatorRegistry() resource = Resource('group', 'version', 'plural') cause = cause_factory(resource=resource, **causeargs) mocker.patch('kopf.reactor.registries.match', return_value=True) @decorator('group', 'version', 'plural', registry=registry, field='spec.field', old='old', new='new') def fn(**_): pass handlers_registry = getattr(registry, handlers_prop) handlers = handlers_registry[resource].get_handlers(cause) assert len(handlers) == 1 assert handlers[0].field == ('spec', 'field') assert handlers[0].value is None assert handlers[0].old == 'old' assert handlers[0].new == 'new'
def resource(): """ The resource used in the tests. Usually mocked, so it does not matter. """ return Resource('zalando.org', 'v1', 'kopfexamples')
def test_url_of_builtin_resource_item_namespaced(): resource = Resource('', 'v1', 'plural') url = resource.get_url(namespace='ns-a.b', name='name-a.b') assert url == '/api/v1/namespaces/ns-a.b/plural/name-a.b'
async def patch_obj( *, resource: resources.Resource, patch: patches.Patch, namespace: Optional[str] = None, name: Optional[str] = None, body: Optional[bodies.Body] = None, context: Optional[auth.APIContext] = None, # injected by the decorator ) -> bodies.RawBody: """ Patch a resource of specific kind. Either the namespace+name should be specified, or the body, which is used only to get namespace+name identifiers. Unlike the object listing, the namespaced call is always used for the namespaced resources, even if the operator serves the whole cluster (i.e. is not namespace-restricted). Returns the patched body. The patched body can be partial (status-only, no-status, or empty) -- depending on whether there were fields in the body or in the status to patch; if neither had fields for patching, the result is an empty body. The result should only be used to check against the patch: if there was nothing to patch, it does not matter if the fields are absent. """ if context is None: raise RuntimeError("API instance is not injected by the decorator.") if body is not None and (name is not None or namespace is not None): raise TypeError("Either body, or name+namespace can be specified. Got both.") namespace = body.get('metadata', {}).get('namespace') if body is not None else namespace name = body.get('metadata', {}).get('name') if body is not None else name is_namespaced = await discovery.is_namespaced(resource=resource, context=context) namespace = namespace if is_namespaced else None if body is None: body = cast(bodies.Body, {'metadata': {'name': name}}) if namespace is not None: body['metadata']['namespace'] = namespace as_subresource = await discovery.is_status_subresource(resource=resource, context=context) body_patch = dict(patch) # shallow: for mutation of the top-level keys below. status_patch = body_patch.pop('status', None) if as_subresource else None # Patch & reconstruct the actual body as reported by the server. The reconstructed body can be # partial or empty -- if the body/status patches are empty. This is fine: it is only used # to verify that the patched fields are matching the patch. No patch? No mismatch! patched_body = bodies.RawBody() try: if body_patch: response = await context.session.patch( url=resource.get_url(server=context.server, namespace=namespace, name=name), headers={'Content-Type': 'application/merge-patch+json'}, json=body_patch, raise_for_status=True, ) patched_body = await response.json() if status_patch: response = await context.session.patch( url=resource.get_url(server=context.server, namespace=namespace, name=name, subresource='status' if as_subresource else None), headers={'Content-Type': 'application/merge-patch+json'}, json={'status': status_patch}, raise_for_status=True, ) patched_body['status'] = await response.json() except aiohttp.ClientResponseError as e: if e.status == 404: pass else: raise return patched_body
async def watch_objs( *, settings: configuration.OperatorSettings, resource: resources.Resource, namespace: Optional[str], timeout: Optional[float] = None, since: Optional[str] = None, context: Optional[auth.APIContext] = None, # injected by the decorator freeze_waiter: aiotasks.Future, ) -> AsyncIterator[bodies.RawInput]: """ Watch objects of a specific resource type. The cluster-scoped call is used in two cases: * The resource itself is cluster-scoped, and namespacing makes not sense. * The operator serves all namespaces for the namespaced custom resource. Otherwise, the namespace-scoped call is used: * The resource is namespace-scoped AND operator is namespaced-restricted. """ if context is None: raise RuntimeError("API instance is not injected by the decorator.") is_namespaced = await discovery.is_namespaced(resource=resource, context=context) namespace = namespace if is_namespaced else None params: Dict[str, str] = {} params['watch'] = 'true' if since is not None: params['resourceVersion'] = since if timeout is not None: params['timeoutSeconds'] = str(timeout) # Stream the parsed events from the response until it is closed server-side, # or until it is closed client-side by the freeze-waiting future's callbacks. try: response = await context.session.get( url=resource.get_url(server=context.server, namespace=namespace, params=params), timeout=aiohttp.ClientTimeout( total=settings.watching.client_timeout, sock_connect=settings.watching.connect_timeout, ), ) await errors.check_response(response) response_close_callback = lambda _: response.close() freeze_waiter.add_done_callback(response_close_callback) try: async with response: async for line in _iter_jsonlines(response.content): raw_input = cast(bodies.RawInput, json.loads(line.decode("utf-8"))) yield raw_input finally: freeze_waiter.remove_done_callback(response_close_callback) except (aiohttp.ClientConnectionError, aiohttp.ClientPayloadError, asyncio.TimeoutError): pass