Skip to content

Commit

Permalink
Merge pull request #160 from vkottler/dev/3.4.0
Browse files Browse the repository at this point in the history
3.4.0 - Initial scaffolding for 'task' command
  • Loading branch information
vkottler authored Jan 27, 2024
2 parents 095be33 + 27b2736 commit 10ce4fe
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
- run: |
mk python-release owner=vkottler \
repo=runtimepy version=3.3.1
repo=runtimepy version=3.4.0
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
max-args=8
max-attributes=14
max-parents=12
max-branches=13

[MESSAGES CONTROL]
disable=too-few-public-methods,duplicate-code
Expand Down
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
=====================================
generator=datazen
version=3.1.4
hash=8abaee2821e021cb2521ae3ee0a9f79d
hash=c6a4e8d162ebe8d48c6bce5ffcd9a663
=====================================
-->

# runtimepy ([3.3.1](https://pypi.org/project/runtimepy/))
# runtimepy ([3.4.0](https://pypi.org/project/runtimepy/))

[![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
Expand Down Expand Up @@ -48,7 +48,7 @@ This package is tested on the following platforms:
$ ./venv3.11/bin/runtimepy -h
usage: runtimepy [-h] [--version] [-v] [-q] [--curses] [--no-uvloop] [-C DIR]
{arbiter,server,tui,noop} ...
{arbiter,server,task,tui,noop} ...
A framework for implementing Python services.
Expand All @@ -62,10 +62,11 @@ options:
-C DIR, --dir DIR execute from a specific directory
commands:
{arbiter,server,tui,noop}
{arbiter,server,task,tui,noop}
set of available commands
arbiter run a connection-arbiter application from a config
server run a server for a specific connection factory
task run a task from a specific task factory
tui run a terminal interface for the channel environment
noop command stub (does nothing)
Expand Down Expand Up @@ -120,6 +121,27 @@ options:
```

### `task`

```
$ ./venv3.11/bin/runtimepy task -h
usage: runtimepy task [-h] [-i] [-w] factory [configs ...]
positional arguments:
factory name of task factory to create task with
configs the configuration to load
options:
-h, --help show this help message and exit
-i, --init_only, --init-only
exit after completing initialization
-w, --wait-for-stop, --wait_for_stop
ensure that a 'wait_for_stop' application method is
run last
```

### `tui`

```
Expand Down
2 changes: 2 additions & 0 deletions local/configs/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ commands:
description: "run a connection-arbiter application from a config"
- name: server
description: "run a server for a specific connection factory"
- name: task
description: "run a task from a specific task factory"
- name: tui
description: "run a terminal interface for the channel environment"

Expand Down
2 changes: 1 addition & 1 deletion local/includes/sub_commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
default_dirs: false

commands:
{% for command in ["arbiter", "server", "tui"] %}
{% for command in ["arbiter", "server", "task", "tui"] %}
- name: help-{{command}}
command: "./venv{{python_version}}/bin/{{entry}}"
force: true
Expand Down
4 changes: 2 additions & 2 deletions local/variables/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
major: 3
minor: 3
patch: 1
minor: 4
patch: 0
entry: runtimepy
1 change: 1 addition & 0 deletions manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ renders:
- commands-help
- commands-help-arbiter
- commands-help-server
- commands-help-task
- commands-help-tui

- name: app.py
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"

[project]
name = "runtimepy"
version = "3.3.1"
version = "3.4.0"
description = "A framework for implementing Python services."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
4 changes: 2 additions & 2 deletions runtimepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=3303e95749a72345c2ef69c1528de1a7
# hash=3ac128eb7b7140a6e7e096f51a3b3e43
# =====================================

"""
Expand All @@ -10,7 +10,7 @@

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "3.3.1"
VERSION = "3.4.0"

# runtimepy-specific content.
METRICS_NAME = "metrics"
6 changes: 3 additions & 3 deletions runtimepy/codec/system/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ def register(self, name: str, *namespace: str) -> Protocol:
"""Register a custom type."""

new_type = Protocol(self._enums)
self.custom[
self._name(name, *namespace, check_available=True)
] = new_type
self.custom[self._name(name, *namespace, check_available=True)] = (
new_type
)
return new_type

def get_protocol(
Expand Down
8 changes: 7 additions & 1 deletion runtimepy/commands/all.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=2fec0392db71770da35d98fdbc8ce142
# hash=89bfb0e2f56effabf3cb8f70d6349c13
# =====================================

"""
Expand All @@ -18,6 +18,7 @@
# internal
from runtimepy.commands.arbiter import add_arbiter_cmd
from runtimepy.commands.server import add_server_cmd
from runtimepy.commands.task import add_task_cmd
from runtimepy.commands.tui import add_tui_cmd


Expand All @@ -35,6 +36,11 @@ def commands() -> _List[_Tuple[str, str, _CommandRegister]]:
"run a server for a specific connection factory",
add_server_cmd,
),
(
"task",
"run a task from a specific task factory",
add_task_cmd,
),
(
"tui",
"run a terminal interface for the channel environment",
Expand Down
58 changes: 58 additions & 0 deletions runtimepy/commands/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
An entry-point for the 'task' command.
"""

# built-in
from argparse import ArgumentParser as _ArgumentParser
from argparse import Namespace as _Namespace
from typing import Any, Dict

# third-party
from vcorelib.args import CommandFunction as _CommandFunction

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


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

return {
"includes": [f"package://{PKG_NAME}/factories.yaml"],
"tasks": [
{
"name": args.factory,
"factory": args.factory,
"period_s": 1.0 / args.rate,
}
],
}


def task_cmd(args: _Namespace) -> int:
"""Execute the task command."""

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


def add_task_cmd(parser: _ArgumentParser) -> _CommandFunction:
"""Add task-command arguments to its parser."""

with arbiter_args(parser, nargs="*"):
parser.add_argument(
"-r",
"--rate",
default=10,
type=float,
help=(
"rate (in Hz) that the task should run "
"(default: %(default)s)"
),
)
parser.add_argument(
"factory", help="name of task factory to create task with"
)

return task_cmd
3 changes: 3 additions & 0 deletions runtimepy/data/factories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ factories:
# Useful protocols.
- {name: runtimepy.net.factories.Http}

# Useful tasks.
- {name: runtimepy.task.trig.Sinusoid}

ports:
# Reserve ports for JSON listeners.
- {name: udp_json, type: udp}
Expand Down
30 changes: 20 additions & 10 deletions runtimepy/net/arbiter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ def init(self, data: _JsonObject) -> None:
item["host"],
port_overrides.get(item["name"], item["port"]),
),
kind=_socket.SOCK_STREAM
if item["type"] == "tcp"
else _socket.SOCK_DGRAM,
kind=(
_socket.SOCK_STREAM
if item["type"] == "tcp"
else _socket.SOCK_DGRAM
),
).port

self.app: _Optional[str] = data.get("app") # type: ignore
Expand Down Expand Up @@ -168,16 +170,24 @@ async def process_config(
) -> None:
"""Register clients and servers from a configuration object."""

names = set()

# Registier factories.
for factory in config.factories:
name = factory["name"]
assert self.register_module_factory(
name,
*factory.get("namespaces", []),
**dict_resolve_env_vars(
factory.get("kwargs", {}), env=config.ports # type: ignore
),
), f"Couldn't register factory '{factory}'!"

# Double specifying a factory (because of include shenanigans)
# should be fine.
if name not in names:
assert self.register_module_factory(
name,
*factory.get("namespaces", []),
**dict_resolve_env_vars(
factory.get("kwargs", {}),
env=config.ports, # type: ignore
),
), f"Couldn't register factory '{factory}'!"
names.add(name)

# Register clients.
for client in config.clients:
Expand Down
14 changes: 8 additions & 6 deletions runtimepy/net/stream/json/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ async def handler(outbox: JsonMessage, request: ChannelCommand) -> None:
# Respond with an error (environment not found).
else:
outbox["success"] = False
outbox[
"reason"
] = f"No environment '{env_name}', options: {envs.keys()}."
outbox["reason"] = (
f"No environment '{env_name}', options: {envs.keys()}."
)

return handler

Expand Down Expand Up @@ -108,9 +108,11 @@ async def find_file_request_handler(
path,
includes_key=request.data["includes_key"],
expect_overwrite=request.data["expect_overwrite"],
strategy=MergeStrategy.UPDATE
if request.data["update"]
else MergeStrategy.RECURSIVE,
strategy=(
MergeStrategy.UPDATE
if request.data["update"]
else MergeStrategy.RECURSIVE
),
)
decoded["success"] = result.success
decoded["data"] = result.data
Expand Down
44 changes: 44 additions & 0 deletions runtimepy/task/trig/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
A module implementing basic trigonometric tasks.
"""

# built-in
import math

# internal
from runtimepy.net.arbiter.task import ArbiterTask as _ArbiterTask
from runtimepy.net.arbiter.task import TaskFactory as _TaskFactory
from runtimepy.primitives import Float as _Float


class SinusoidTask(_ArbiterTask):
"""A task for logging metrics."""

auto_finalize = True

def _init_state(self) -> None:
"""Add channels to this instance's channel environment."""

self.sin = _Float()
self.cos = _Float()
self.steps = _Float(10.0)

self.env.channel("sin", self.sin)
self.env.channel("cos", self.cos)
self.env.channel("steps", self.steps, commandable=True)

async def dispatch(self) -> bool:
"""Dispatch an iteration of this task."""

step = (math.tau / self.steps.value) * self.metrics.dispatches.value

self.sin.value = math.sin(step)
self.cos.value = math.cos(step)

return True


class Sinusoid(_TaskFactory[SinusoidTask]):
"""A factory for the sinusoid task."""

kind = SinusoidTask
16 changes: 16 additions & 0 deletions tests/commands/test_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Test the 'commands.task' module.
"""

# module under test
from runtimepy.entry import main as runtimepy_main

# internal
from tests.resources import base_args


def test_task_command_basic():
"""Test basic usages of the 'task' command."""

base = base_args("task")
assert runtimepy_main(base + ["sinusoid"]) == 0

0 comments on commit 10ce4fe

Please sign in to comment.