diff --git a/install-k b/install-k index b0ce73693d0..5b5d903df1e 100755 --- a/install-k +++ b/install-k @@ -1,6 +1,6 @@ #!/bin/sh -e -K_VERSION=7.1.0 +K_VERSION=7.1.300 if [ `id -u` -ne 0 ]; then echo "$0: error: This script must be run as root." diff --git a/package/debian/kframework/changelog b/package/debian/kframework/changelog index e3e847136cb..64c721e3eb6 100644 --- a/package/debian/kframework/changelog +++ b/package/debian/kframework/changelog @@ -1,4 +1,4 @@ -kframework (7.1.0) unstable; urgency=medium +kframework (7.1.300) unstable; urgency=medium * Initial Release. diff --git a/package/version b/package/version index a3fcc7121bb..65a488eaa8c 100644 --- a/package/version +++ b/package/version @@ -1 +1 @@ -7.1.0 +7.1.300 diff --git a/pyk/docs/conf.py b/pyk/docs/conf.py index 0bb235d9721..5e5dd7c61ff 100644 --- a/pyk/docs/conf.py +++ b/pyk/docs/conf.py @@ -9,8 +9,8 @@ project = 'pyk' author = 'Runtime Verification, Inc' copyright = '2024, Runtime Verification, Inc' -version = '7.1.0' -release = '7.1.0' +version = '7.1.300' +release = '7.1.300' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/pyk/pyproject.toml b/pyk/pyproject.toml index d115f39f691..1ee4a36a941 100644 --- a/pyk/pyproject.toml +++ b/pyk/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "kframework" -version = "7.1.0" +version = "7.1.300" description = "" readme = "README.md" requires-python = ">=3.10" diff --git a/pyk/src/pyk/rpc/rpc.py b/pyk/src/pyk/rpc/rpc.py index fa0f5c65e76..d9a1bb54d4a 100644 --- a/pyk/src/pyk/rpc/rpc.py +++ b/pyk/src/pyk/rpc/rpc.py @@ -3,10 +3,11 @@ import json import logging from abc import ABC, abstractmethod +from collections.abc import Iterator from dataclasses import dataclass from functools import partial from http.server import BaseHTTPRequestHandler, HTTPServer -from typing import TYPE_CHECKING, Any, Final, NamedTuple +from typing import TYPE_CHECKING, NamedTuple from typing_extensions import Protocol @@ -15,6 +16,7 @@ if TYPE_CHECKING: from collections.abc import Callable from pathlib import Path + from typing import Any, Final _LOGGER: Final = logging.getLogger(__name__) @@ -86,7 +88,7 @@ class JsonRpcBatchRequest(NamedTuple): class JsonRpcResult(ABC): @abstractmethod - def encode(self) -> bytes: ... + def encode(self) -> Iterator[bytes]: ... @dataclass(frozen=True) @@ -96,7 +98,7 @@ class JsonRpcError(JsonRpcResult): message: str id: str | int | None - def to_json(self) -> dict[str, Any]: + def wrap_response(self) -> dict[str, Any]: return { 'jsonrpc': JsonRpcServer.JSONRPC_VERSION, 'error': { @@ -106,8 +108,8 @@ def to_json(self) -> dict[str, Any]: 'id': self.id, } - def encode(self) -> bytes: - return json.dumps(self.to_json()).encode('ascii') + def encode(self) -> Iterator[bytes]: + yield json.dumps(self.wrap_response()).encode('ascii') @dataclass(frozen=True) @@ -115,23 +117,30 @@ class JsonRpcSuccess(JsonRpcResult): payload: Any id: Any - def to_json(self) -> dict[str, Any]: - return { - 'jsonrpc': JsonRpcServer.JSONRPC_VERSION, - 'result': self.payload, - 'id': self.id, - } - - def encode(self) -> bytes: - return json.dumps(self.to_json()).encode('ascii') + def encode(self) -> Iterator[bytes]: + yield f'{{"jsonrpc":"2.0", "id": {self.id}, "result": '.encode('ascii') + if isinstance(self.payload, Iterator): + for chunk in self.payload: + yield chunk.encode('ascii') + else: + yield json.dumps(self.payload).encode('ascii') + yield b'}' @dataclass(frozen=True) class JsonRpcBatchResult(JsonRpcResult): results: tuple[JsonRpcError | JsonRpcSuccess, ...] - def encode(self) -> bytes: - return json.dumps([result.to_json() for result in self.results]).encode('ascii') + def encode(self) -> Iterator[bytes]: + yield b'[' + first = True + for result in self.results: + if not first: + yield b',' + else: + first = False + yield from result.encode() + yield b']' class JsonRpcRequestHandler(BaseHTTPRequestHandler): @@ -143,8 +152,10 @@ def __init__(self, methods: dict[str, JsonRpcMethod], *args: Any, **kwargs: Any) def _send_response(self, response: JsonRpcResult) -> None: self.send_response_headers() - response_bytes = response.encode() - self.wfile.write(response_bytes) + response_body = response.encode() + for chunk in response_body: + self.wfile.write(chunk) + self.wfile.flush() def send_response_headers(self) -> None: self.send_response(200) diff --git a/pyk/src/tests/integration/test_json_rpc.py b/pyk/src/tests/integration/test_json_rpc.py index 61a5367df1b..1b6a8988d43 100644 --- a/pyk/src/tests/integration/test_json_rpc.py +++ b/pyk/src/tests/integration/test_json_rpc.py @@ -15,6 +15,7 @@ from pyk.testing import KRunTest if TYPE_CHECKING: + from collections.abc import Iterator from typing import Any @@ -154,6 +155,7 @@ def __init__(self, options: ServeRpcOptions) -> None: self.register_method('set_x', self.exec_set_x) self.register_method('set_y', self.exec_set_y) self.register_method('add', self.exec_add) + self.register_method('streaming', self.exec_streaming) def exec_get_x(self) -> int: return self.x @@ -170,6 +172,11 @@ def exec_set_y(self, n: int) -> None: def exec_add(self) -> int: return self.x + self.y + def exec_streaming(self) -> Iterator[bytes]: + yield b'{' + yield b'"foo": "bar"' + yield b'}' + class TestJsonRPCServer(KRunTest): @@ -221,6 +228,9 @@ def wait_until_ready() -> None: assert len(res) == 3 assert res[2]['result'] == 1 + 2 + res = rpc_client.request('streaming', []) + assert res == {'foo': 'bar'} + server.shutdown() thread.join()