Skip to content

Commit

Permalink
Some more command-line options
Browse files Browse the repository at this point in the history
  • Loading branch information
vkottler committed Jan 20, 2024
1 parent 36393d5 commit 4fc58db
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 32 deletions.
15 changes: 14 additions & 1 deletion runtimepy/commands/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from argparse import ArgumentParser as _ArgumentParser
from argparse import Namespace as _Namespace
from contextlib import contextmanager
from typing import Iterator
from typing import Any, Dict, Iterator

# third-party
from vcorelib.args import CommandFunction as _CommandFunction
from vcorelib.io import ARBITER
from vcorelib.paths.context import tempfile

try:
import curses as _curses
Expand Down Expand Up @@ -69,3 +71,14 @@ def arbiter_args(parser: _ArgumentParser, nargs: str = "+") -> Iterator[None]:
parser.add_argument(
"configs", nargs=nargs, help="the configuration to load"
)


def cmd_with_jit(
command: _CommandFunction, args: _Namespace, data: Dict[str, Any]
) -> int:
"""Run an 'arbiter' command with custom data inserted."""

with tempfile(suffix=".yaml") as temp_config:
ARBITER.encode(temp_config, data)
args.configs.append(str(temp_config))
return command(args)
109 changes: 91 additions & 18 deletions runtimepy/commands/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,104 @@
# built-in
from argparse import ArgumentParser as _ArgumentParser
from argparse import Namespace as _Namespace
from typing import Any, Dict
from typing import Any, Dict, List

# third-party
from vcorelib.args import CommandFunction as _CommandFunction
from vcorelib.io import ARBITER
from vcorelib.paths.context import tempfile

# internal
from runtimepy import PKG_NAME
from runtimepy.commands.arbiter import arbiter_cmd
from runtimepy.commands.common import arbiter_args
from runtimepy.commands.common import arbiter_args, cmd_with_jit


def port_name(args: _Namespace, port: str = "port") -> str:
"""Get the name for a connection factory's port."""
return f"{args.factory}_{'udp' if args.udp else 'tcp'}_{port}"


def server_data(args: _Namespace) -> Dict[str, Any]:
"""Get server data based on command-line arguments."""

return {
"factory": args.factory,
"kwargs": {"port": args.port, "host": args.host},
"kwargs": {"port": f"${port_name(args)}", "host": args.host},
}


def server_cmd(args: _Namespace) -> int:
"""Execute the server command."""
def is_websocket(args: _Namespace) -> bool:
"""Determine if the specified factory uses WebSocket or not."""
return "websocket" in args.factory.lower()


def client_data(args: _Namespace) -> Dict[str, Any]:
"""Get client data based on command-line arguments."""

port = f"${port_name(args)}"

arg_list: List[Any] = []
kwargs: Dict[str, Any] = {}

if is_websocket(args):
arg_list.append(f"ws://localhost:{port}")
elif not args.udp:
kwargs["host"] = "localhost"
kwargs["port"] = port
else:
kwargs["remote_addr"] = ["localhost", port]

result = {
"name": port_name(args, port="client"),
"defer": True,
"factory": args.factory,
}
if arg_list:
result["args"] = arg_list
if kwargs:
result["kwargs"] = kwargs

return result


def config_data(args: _Namespace) -> Dict[str, Any]:
"""Get configuration data for the 'server' command."""

with tempfile(suffix=".yaml") as temp_config:
ARBITER.encode(
temp_config,
servers = []
clients = []

if not args.udp:
servers.append(server_data(args))
else:
clients.append(
{
"includes": [f"package://{PKG_NAME}/factories.yaml"],
"servers": [server_data(args)], # type: ignore
"app": ["runtimepy.net.apps.wait_for_stop"],
},
"name": port_name(args, port="server"),
"factory": args.factory,
"kwargs": {"local_addr": ["0.0.0.0", f"${port_name(args)}"]},
}
)

# Ensure injected data is loaded.
args.configs.append(str(temp_config))
return arbiter_cmd(args)
# Add a loopback connection if specified.
if args.loopback:
clients.append(client_data(args))

return {
"includes": [f"package://{PKG_NAME}/factories.yaml"],
"clients": clients,
"servers": servers,
"ports": [
{
"name": port_name(args),
"port": args.port,
"type": "udp" if args.udp else "tcp",
}
],
}


def server_cmd(args: _Namespace) -> int:
"""Execute the server command."""

return cmd_with_jit(arbiter_cmd, args, config_data(args))


def add_server_cmd(parser: _ArgumentParser) -> _CommandFunction:
Expand All @@ -52,7 +112,7 @@ def add_server_cmd(parser: _ArgumentParser) -> _CommandFunction:
parser.add_argument(
"--host",
default="0.0.0.0",
help="host address to listen on (default: %(default)s)",
help="host address to listen on (default: '%(default)s')",
)
parser.add_argument(
"-p",
Expand All @@ -61,6 +121,19 @@ def add_server_cmd(parser: _ArgumentParser) -> _CommandFunction:
type=int,
help="port to listen on (default: %(default)s)",
)
parser.add_argument(
"-u",
"--udp",
action="store_true",
help="whether or not this is a UDP-based server "
"(otherwise it must be a TCP-based server)",
)
parser.add_argument(
"-l",
"--loopback",
action="store_true",
help="if true a client of the same connection type is added",
)
parser.add_argument(
"factory", help="name of connection factory to create server for"
)
Expand Down
5 changes: 2 additions & 3 deletions runtimepy/net/apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
async def wait_for_stop(app: AppInfo) -> int:
"""Waits for the stop signal to be set."""

await app.all_finalized()
result = await init_only(app)
await app.stop.wait()

return 0
return result


noop = init_only
Expand Down
2 changes: 1 addition & 1 deletion runtimepy/net/arbiter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
async def init_only(app: AppInfo) -> int:
"""A network application that doesn't do anything."""

del app
await app.all_finalized()
return 0


Expand Down
2 changes: 1 addition & 1 deletion runtimepy/net/tcp/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def try_tcp_transport_protocol(

result = await try_log_connection_error(
tcp_transport_protocol(**kwargs),
"Error creating TCP connection:",
f"Error creating TCP connection ({kwargs}):",
logger=LOG,
)

Expand Down
12 changes: 6 additions & 6 deletions runtimepy/net/tcp/http/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
class HttpConnection(_TcpConnection):
"""A class implementing a basic HTTP interface."""

async def process_binary(self, data: bytes) -> bool:
"""Process a binary frame."""

print(data)

return True
# async def process_binary(self, data: bytes) -> bool:
# """Process a binary frame."""
#
# print(data)
#
# return True
5 changes: 3 additions & 2 deletions tests/commands/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ def test_server_command_basic():
"""Test basic usages of the 'server' command."""

base = base_args("server")

assert runtimepy_main(base + ["--init_only", "tcp_null"]) == 0
assert runtimepy_main(base + ["-l", "tcp_null"]) == 0
assert runtimepy_main(base + ["-l", "websocket_null"]) == 0
assert runtimepy_main(base + ["-u", "-l", "udp_null"]) == 0

0 comments on commit 4fc58db

Please sign in to comment.