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: redesigned protocol #106

Draft
wants to merge 102 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
43ab7b3
feat: use Socket.io instead of plain websockets
lars-reimann Apr 22, 2024
7c2bf56
build: add `asyncio-client` for socket.io
lars-reimann Apr 28, 2024
ea91022
test: fixture to start server
lars-reimann Apr 28, 2024
88cf763
test: demo test
lars-reimann Apr 28, 2024
5127572
fix: catch `TypeErrors` if unwrapping via `**` fails
lars-reimann Apr 28, 2024
35e4b5e
fix: make startup awaitable
lars-reimann Apr 28, 2024
88c53d6
fix: receive first message
lars-reimann Apr 28, 2024
1c6a04f
test: first working test
lars-reimann Apr 28, 2024
3d61489
test: move tests for message validation
lars-reimann Apr 29, 2024
0481396
refactor: make several functions `async`
lars-reimann Apr 29, 2024
265667d
docs: add some comments
lars-reimann Apr 29, 2024
0a6f886
refactor: rename attribute
lars-reimann Apr 29, 2024
f6a1fc0
refactor: rename attribute
lars-reimann Apr 29, 2024
86c7cf9
refactor: return a `Task` instead of a `Thread`
lars-reimann Apr 29, 2024
db74187
test: automatically detect `async`
lars-reimann Apr 29, 2024
a253889
refactor: move process termination handler into server
lars-reimann Apr 29, 2024
5b11f37
test: handle shutdown and teardown of server in fixture
lars-reimann Apr 29, 2024
ce43e5e
refactor: remove `atexit` handlers
lars-reimann Apr 29, 2024
83da61e
refactor: define outgoing messages with `pydantic`
lars-reimann Apr 29, 2024
b7227a8
refactor: define incoming messages with `pydantic`
lars-reimann Apr 29, 2024
f0b3737
fix: get test working again with new messages
lars-reimann Apr 29, 2024
6ad67ed
feat: send runtime warnings
lars-reimann Apr 29, 2024
8f44525
fix: deadlock
lars-reimann Apr 29, 2024
748ae0c
test: shutdown message
lars-reimann Apr 29, 2024
fbf3afd
test: remove migrated tests
lars-reimann Apr 29, 2024
fb62727
test: comment out code that causes full test run to fail completely
lars-reimann Apr 29, 2024
13def66
test: update validation tests
lars-reimann Apr 29, 2024
6af39c2
fix: run server in loop
lars-reimann Apr 29, 2024
ad216d8
fix: arbitrary args for payload of shutdown/catch all
lars-reimann Apr 29, 2024
5bdf067
test: fix stalling test
lars-reimann Apr 29, 2024
a9d9211
test: update memo tests
lars-reimann Apr 29, 2024
2b9517a
refactor: call `sys.exit` using the event loop
lars-reimann Apr 29, 2024
c8df399
feat: handle interrupt signal
lars-reimann Apr 30, 2024
e6f1584
refactor: shutdown server using interrupt signal
lars-reimann Apr 30, 2024
cd774dc
refactor: extract interface into own package
lars-reimann Apr 30, 2024
adf6aed
fix: register signal handler in main thread
lars-reimann Apr 30, 2024
a54fdc2
test: progress sent
lars-reimann Apr 30, 2024
0922dad
test: add a means to send messages with two clients
lars-reimann Apr 30, 2024
59182d0
test: add more tests
lars-reimann Apr 30, 2024
205f3d5
test: remove migrated tests
lars-reimann Apr 30, 2024
74e89ce
docs: document new messages
lars-reimann Apr 30, 2024
24cbdfb
refactor: extract logic from pipeline process into interface methods
lars-reimann Apr 30, 2024
026baca
feat: use Socket.io instead of plain websockets
lars-reimann Apr 22, 2024
a6ff86b
build: add `asyncio-client` for socket.io
lars-reimann Apr 28, 2024
e151888
test: fixture to start server
lars-reimann Apr 28, 2024
039ea31
test: demo test
lars-reimann Apr 28, 2024
d173782
fix: catch `TypeErrors` if unwrapping via `**` fails
lars-reimann Apr 28, 2024
11d70da
fix: make startup awaitable
lars-reimann Apr 28, 2024
045a36e
fix: receive first message
lars-reimann Apr 28, 2024
9a51528
test: first working test
lars-reimann Apr 28, 2024
e94476d
test: move tests for message validation
lars-reimann Apr 29, 2024
f94c789
refactor: make several functions `async`
lars-reimann Apr 29, 2024
011fabc
docs: add some comments
lars-reimann Apr 29, 2024
b0aa214
refactor: rename attribute
lars-reimann Apr 29, 2024
619c831
refactor: rename attribute
lars-reimann Apr 29, 2024
6d214da
refactor: return a `Task` instead of a `Thread`
lars-reimann Apr 29, 2024
09a0964
test: automatically detect `async`
lars-reimann Apr 29, 2024
c72fcdc
refactor: move process termination handler into server
lars-reimann Apr 29, 2024
4785da0
test: handle shutdown and teardown of server in fixture
lars-reimann Apr 29, 2024
852434b
refactor: remove `atexit` handlers
lars-reimann Apr 29, 2024
ef8a03d
refactor: define outgoing messages with `pydantic`
lars-reimann Apr 29, 2024
9846a4a
refactor: define incoming messages with `pydantic`
lars-reimann Apr 29, 2024
7624766
fix: get test working again with new messages
lars-reimann Apr 29, 2024
88f6d3c
feat: send runtime warnings
lars-reimann Apr 29, 2024
8a95ffa
fix: deadlock
lars-reimann Apr 29, 2024
8f29039
test: shutdown message
lars-reimann Apr 29, 2024
bb1484a
test: remove migrated tests
lars-reimann Apr 29, 2024
5d7f200
test: comment out code that causes full test run to fail completely
lars-reimann Apr 29, 2024
26eeec1
test: update validation tests
lars-reimann Apr 29, 2024
f5d0f82
fix: run server in loop
lars-reimann Apr 29, 2024
18b5bc3
fix: arbitrary args for payload of shutdown/catch all
lars-reimann Apr 29, 2024
7886c1b
test: fix stalling test
lars-reimann Apr 29, 2024
9ddd0af
test: update memo tests
lars-reimann Apr 29, 2024
8c3d3da
refactor: call `sys.exit` using the event loop
lars-reimann Apr 29, 2024
26f8b61
feat: handle interrupt signal
lars-reimann Apr 30, 2024
11c84bf
refactor: shutdown server using interrupt signal
lars-reimann Apr 30, 2024
1b81c77
refactor: extract interface into own package
lars-reimann Apr 30, 2024
2eb2207
fix: register signal handler in main thread
lars-reimann Apr 30, 2024
de8af73
test: progress sent
lars-reimann Apr 30, 2024
33b470e
test: add a means to send messages with two clients
lars-reimann Apr 30, 2024
48215ce
test: add more tests
lars-reimann Apr 30, 2024
5303ad3
test: remove migrated tests
lars-reimann Apr 30, 2024
621e5ee
docs: document new messages
lars-reimann Apr 30, 2024
9bd7797
refactor: extract logic from pipeline process into interface methods
lars-reimann Apr 30, 2024
5923ba6
build: regenerate lockfile
lars-reimann May 2, 2024
d9414a8
test: always stop server used to test `shutdown` message
lars-reimann May 2, 2024
c2e8ea3
feat: method to check whether server is started
lars-reimann May 2, 2024
88f6f56
test: wait until the server to test shutdown is ready to accept conne…
lars-reimann May 2, 2024
abd5628
Merge branch 'refs/heads/main' into socket-io
lars-reimann May 4, 2024
145edfb
WIP
lars-reimann May 4, 2024
9d0b025
Merge branch 'refs/heads/main' into socket-io
lars-reimann May 4, 2024
ba59134
build: merge dependencies
lars-reimann May 4, 2024
6da43a4
ci: remove mypy plugin
lars-reimann May 4, 2024
bd1ae3e
refactor: move method to get runtime type
lars-reimann May 4, 2024
d9259af
refactor: utils to create stacktraces
lars-reimann May 4, 2024
b586aef
test: automatically detect `async`
lars-reimann May 4, 2024
f9e5319
fix: allow line of stacktrace entry to be `None`
lars-reimann May 4, 2024
7fd0bd7
Merge remote-tracking branch 'origin/socket-io' into socket-io
lars-reimann May 4, 2024
3862377
test: don't compare exact line numbers
lars-reimann May 4, 2024
706dba2
refactor: extract `tree_kill` function
lars-reimann May 4, 2024
4999aed
test: double the timeout
lars-reimann May 4, 2024
e0d0296
feat: convert values
lars-reimann May 4, 2024
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
1 change: 1 addition & 0 deletions .github/linters/.mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
disallow_incomplete_defs = true
disallow_untyped_defs = true
ignore_missing_imports = true
plugins = pydantic.mypy
632 changes: 474 additions & 158 deletions poetry.lock

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ safe-ds-runner = "safeds_runner.main:main"

[tool.poetry.dependencies]
python = "^3.11,<3.13"
safe-ds = ">=0.22.1,<0.23"
hypercorn = "^0.16.0"
psutil = "^5.9.8"
pydantic = "^2.7.0"
quart = "^0.19.4"
python-socketio = "^5.11.2"
safe-ds = ">=0.22.1,<0.23"
uvicorn = "^0.29.0"

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^8.2.0"
pytest-asyncio = "^0.23.6"
pytest-cov = "^5.0.0"
pytest-timeout = "^2.3.1"
pytest-asyncio = "^0.23.6"
simple-websocket = "^1.0.0"
python-socketio = {extras = ["asyncio-client"], version = "^5.11.2"}
torch = [
# Install the CUDA version on Windows. Projects that depend on us always get their dependencies from PyPI, so
# there's no point moving this to the main dependencies section.
Expand Down Expand Up @@ -58,3 +58,6 @@ build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 120

[tool.pytest.ini_options]
asyncio_mode = "auto"
12 changes: 9 additions & 3 deletions src/safeds_runner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
"""A runner for the Python code generated from Safe-DS programs."""

from .server._pipeline_manager import (
from .interface._files import (
absolute_path,
file_mtime,
)
from .interface._memoization import (
memoized_dynamic_call,
memoized_static_call,
save_placeholder,
)
from .interface._reporters import (
report_placeholder_computed,
report_placeholder_value,
)

__all__ = [
"absolute_path",
"file_mtime",
"memoized_static_call",
"memoized_dynamic_call",
"save_placeholder",
"report_placeholder_computed",
"report_placeholder_value",
]
1 change: 1 addition & 0 deletions src/safeds_runner/interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Functions that can be called in generated code."""
63 changes: 63 additions & 0 deletions src/safeds_runner/interface/_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

import typing
from pathlib import Path


@typing.overload
def file_mtime(filenames: str) -> int | None: ...


@typing.overload
def file_mtime(filenames: list[str]) -> list[int | None]: ...


def file_mtime(filenames: str | list[str]) -> int | None | list[int | None]:
"""
Get the last modification timestamp of the provided file.

Parameters
----------
filenames:
Names of the files.

Returns
-------
timestamps:
Last modification timestamp or None for each provided file, depending on whether the file exists or not.
"""
if isinstance(filenames, list):
return [file_mtime(f) for f in filenames]

try:
return Path(filenames).stat().st_mtime_ns
except FileNotFoundError:
return None


@typing.overload
def absolute_path(filenames: str) -> str: ...


@typing.overload
def absolute_path(filenames: list[str]) -> list[str]: ...


def absolute_path(filenames: str | list[str]) -> str | list[str]:
"""
Get the absolute path of the provided file.

Parameters
----------
filenames:
Names of the files.

Returns
-------
absolute_paths:
Absolute paths of the provided files.
"""
if isinstance(filenames, list):
return [absolute_path(f) for f in filenames]

return str(Path(filenames).resolve())
102 changes: 102 additions & 0 deletions src/safeds_runner/interface/_memoization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from __future__ import annotations

import typing
from typing import Any

from safeds_runner.server._pipeline_manager import get_current_pipeline_process


def memoized_static_call(
fully_qualified_function_name: str,
callable_: typing.Callable,
positional_arguments: list[Any],
keyword_arguments: dict[str, Any],
hidden_arguments: list[Any],
) -> Any:
"""
Call a function that can be memoized and save the result.

If a function has been previously memoized, the previous result may be reused.

Parameters
----------
fully_qualified_function_name:
Fully qualified function name.
callable_:
Function that is called and memoized if the result was not found in the memoization map.
positional_arguments:
List of positions arguments for the function.
keyword_arguments:
Dictionary of keyword arguments for the function.
hidden_arguments:
List of hidden arguments for the function. This is used for memoizing some impure functions.

Returns
-------
result:
The result of the specified function, if any exists.
"""
current_pipeline = get_current_pipeline_process()
if current_pipeline is None:
return None # pragma: no cover

memoization_map = current_pipeline.get_memoization_map()
return memoization_map.memoized_function_call(
fully_qualified_function_name,
callable_,
positional_arguments,
keyword_arguments,
hidden_arguments,
)


def memoized_dynamic_call(
receiver: Any,
function_name: str,
positional_arguments: list[Any],
keyword_arguments: dict[str, Any],
hidden_arguments: list[Any],
) -> Any:
"""
Dynamically call a function that can be memoized and save the result.

If a function has been previously memoized, the previous result may be reused. Dynamic calling in this context
means, the function name will be used to look up the function on the instance passed as receiver.

Parameters
----------
receiver : Any
Instance the function should be called on.
function_name:
Simple function name.
positional_arguments:
List of positions arguments for the function.
keyword_arguments:
Dictionary of keyword arguments for the function.
hidden_arguments:
List of hidden parameters for the function. This is used for memoizing some impure functions.

Returns
-------
result:
The result of the specified function, if any exists.
"""
current_pipeline = get_current_pipeline_process()
if current_pipeline is None:
return None # pragma: no cover

fully_qualified_function_name = (
receiver.__class__.__module__ + "." + receiver.__class__.__qualname__ + "." + function_name
)

member = getattr(receiver, function_name)
callable_ = member.__func__

memoization_map = get_current_pipeline_process().get_memoization_map()
return memoization_map.memoized_function_call(
fully_qualified_function_name,
callable_,
[receiver, *positional_arguments],
keyword_arguments,
hidden_arguments,
)
67 changes: 67 additions & 0 deletions src/safeds_runner/interface/_reporters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Any

from safeds_runner.server._pipeline_manager import get_current_pipeline_process
from safeds_runner.server.messages._from_server import create_placeholder_value_message, create_progress_message
from safeds_runner.utils._get_type_name import get_type_name
from safeds_runner.utils._make_value_json_serializable import make_value_json_serializable


def report_placeholder_computed(placeholder_name: str) -> None:
"""
Report that a placeholder has been computed.

Parameters
----------
placeholder_name:
Name of the placeholder.
"""
current_pipeline = get_current_pipeline_process()
if current_pipeline is None:
return # pragma: no cover

current_pipeline.send_message(
create_progress_message(
run_id=current_pipeline._payload.run_id,
placeholder_name=placeholder_name,
percentage=100,
),
)


def report_placeholder_value(placeholder_name: str, value: Any) -> None:
"""
Report the value of a placeholder.

Parameters
----------
placeholder_name:
Name of the placeholder.
value:
Value of the placeholder.
"""
current_pipeline = get_current_pipeline_process()
if current_pipeline is None:
return # pragma: no cover

# Also send a progress message
current_pipeline.send_message(
create_progress_message(
run_id=current_pipeline._payload.run_id,
placeholder_name=placeholder_name,
percentage=100,
),
)

# Send the actual value
requested_table_window = current_pipeline._payload.table_window
serialized_value, chosen_window = make_value_json_serializable(value, requested_table_window)

current_pipeline.send_message(
create_placeholder_value_message(
run_id=current_pipeline._payload.run_id,
placeholder_name=placeholder_name,
value=serialized_value,
type_=get_type_name(value),
window=chosen_window,
),
)
54 changes: 0 additions & 54 deletions src/safeds_runner/server/_json_encoder.py

This file was deleted.

Loading
Loading