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

3.4.1 - Improve channel command suggestions #166

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.4.0
repo=runtimepy version=3.4.1
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
7 changes: 4 additions & 3 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=c6a4e8d162ebe8d48c6bce5ffcd9a663
hash=e249f3854eb9586ba137f0d4cb7089b9
=====================================
-->

# runtimepy ([3.4.0](https://pypi.org/project/runtimepy/))
# runtimepy ([3.4.1](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 @@ -126,7 +126,7 @@ options:
```
$ ./venv3.11/bin/runtimepy task -h

usage: runtimepy task [-h] [-i] [-w] factory [configs ...]
usage: runtimepy task [-h] [-i] [-w] [-r RATE] factory [configs ...]

positional arguments:
factory name of task factory to create task with
Expand All @@ -139,6 +139,7 @@ options:
-w, --wait-for-stop, --wait_for_stop
ensure that a 'wait_for_stop' application method is
run last
-r RATE, --rate RATE rate (in Hz) that the task should run (default: 10)

```

Expand Down
2 changes: 1 addition & 1 deletion local/configs/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: A framework for implementing Python services.
entry: {{entry}}

requirements:
- vcorelib>=2.8.3
- vcorelib>=3.2.0
- websockets
- "windows-curses; sys_platform == 'win32' and python_version < '3.12'"

Expand Down
2 changes: 1 addition & 1 deletion local/variables/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
major: 3
minor: 4
patch: 0
patch: 1
entry: runtimepy
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.4.0"
version = "3.4.1"
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=3ac128eb7b7140a6e7e096f51a3b3e43
# hash=4df0a252c47915951eb613d1f294ee92
# =====================================

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

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

# runtimepy-specific content.
METRICS_NAME = "metrics"
18 changes: 18 additions & 0 deletions runtimepy/channel/environment/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
BoolChannelResult = _Tuple[_BoolChannel, _Optional[_RuntimeEnum]]
IntChannelResult = _Tuple[_IntChannel, _Optional[_RuntimeEnum]]

FieldOrChannel = _Union[_BitField, _AnyChannel]


class BaseChannelEnvironment(_NamespaceMixin, FinalizeMixin):
"""A class integrating channel and enumeration registries."""
Expand Down Expand Up @@ -184,6 +186,22 @@ def exists(self, val: _RegistryKey) -> bool:
"""Determine if a channel exists."""
return self.fields.has_field(val) or self.get(val) is not None

def field_or_channel(self, val: _RegistryKey) -> _Optional[FieldOrChannel]:
"""Attempt to look up a field or channel for a given registry key."""

channel: _Optional[FieldOrChannel] = None

chan = self.get(val)
if chan is None:
# Check if the name is a field.
field = self.fields.get_field(val)
if field is not None:
channel = field
else:
channel, _ = chan

return channel

def get(self, val: _RegistryKey) -> _Optional[ChannelResult]:
"""Attempt to get a channel and its enumeration (if it has one)."""

Expand Down
45 changes: 29 additions & 16 deletions runtimepy/channel/environment/command/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

# built-in
from argparse import Namespace
from typing import Any, Callable, Optional, Union, cast
from typing import Any, Callable, Optional, cast

# third-party
from vcorelib.logging import LoggerType

# internal
from runtimepy.channel import AnyChannel
from runtimepy.channel.environment import ChannelEnvironment
from runtimepy.channel.environment.base import FieldOrChannel
from runtimepy.channel.environment.command.parser import (
ChannelCommand,
CommandParser,
Expand All @@ -21,9 +21,19 @@
from runtimepy.primitives.bool import Bool
from runtimepy.primitives.field import BitField

FieldOrChannel = Union[BitField, AnyChannel]
CommandHook = Callable[[Namespace, Optional[FieldOrChannel]], None]

# Declared so we re-export FieldOrChannel after moving where it's declared.
__all__ = [
"CommandHook",
"FieldOrChannel",
"ChannelCommandProcessor",
"EnvironmentMap",
"ENVIRONMENTS",
"clear_env",
"register_env",
]


class ChannelCommandProcessor(ChannelEnvironmentMixin):
"""A command processing interface for channel environments."""
Expand All @@ -50,7 +60,19 @@ def get_suggestion(self, value: str) -> Optional[str]:

args = self.parse(value)
if args is not None:
result = self.env.namespace_suggest(args.channel, delta=False)
candidates = self.env.ns.length_sorted_suggestions(
args.channel, delta=False
)
if candidates:
result = candidates[0]

# Try to find a commandable suggestion.
for candidate in candidates:
chan = self.env.field_or_channel(candidate)
if chan is not None and chan.commandable:
result = candidate
break

if result is not None:
result = args.command + " " + result

Expand Down Expand Up @@ -113,18 +135,9 @@ def handle_command(self, args: Namespace) -> CommandResult:
if self.env.exists(args.channel):
return CommandResult(True, str(self.env.value(args.channel)))

chan = self.env.get(args.channel)

channel: FieldOrChannel

if chan is None:
# Check if the name is a field.
field = self.env.fields.get_field(args.channel)
if field is None:
return CommandResult(False, f"No channel '{args.channel}'.")
channel = field
else:
channel, _ = chan
channel = self.env.field_or_channel(args.channel)
if channel is None:
return CommandResult(False, f"No channel '{args.channel}'.")

# Check if channel is commandable (or if a -f/--force flag is
# set?).
Expand Down
2 changes: 2 additions & 0 deletions runtimepy/commands/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ def start(args: _Namespace) -> int:
_ChannelEnvironment(),
max_iterations=args.iterations,
)

stop_sig = _asyncio.Event()
_run_handle_stop(
stop_sig,
task.run(
args.window,
stop_sig=stop_sig,
),
eloop=_asyncio.new_event_loop(),
enable_uvloop=not getattr(args, "no_uvloop", False),
)

Expand Down
5 changes: 4 additions & 1 deletion runtimepy/net/tcp/telnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from io import BytesIO as _BytesIO
from typing import BinaryIO as _BinaryIO

# third-party
from vcorelib import DEFAULT_ENCODING

# internal
from runtimepy.net.tcp.connection import TcpConnection as _TcpConnection
from runtimepy.net.tcp.telnet.codes import (
Expand All @@ -32,7 +35,7 @@ class Telnet(_TcpConnection):

async def process_telnet_message(self, data: bytes) -> bool:
"""By default, treat all incoming data bytes as text."""
return await self.process_text(data.decode())
return await self.process_text(data.decode(encoding=DEFAULT_ENCODING))

@_abstractmethod
async def process_command(self, code: TelnetCode) -> None:
Expand Down
2 changes: 1 addition & 1 deletion runtimepy/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
vcorelib>=2.8.3
vcorelib>=3.2.0
websockets
windows-curses; sys_platform == 'win32' and python_version < '3.12'
14 changes: 7 additions & 7 deletions tasks/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=9f62028523c3b5a953733ca89dcc3018
# hash=7d378a1752611508007a77d4ca39a5af
# =====================================
"""
A module for project-specific task registration.
Expand All @@ -20,14 +20,9 @@ def audit_local_tasks() -> None:
"""Ensure that shared task infrastructure is present."""

local = Path(__file__).parent.joinpath("mklocal")

# Also link a top-level file.
top_level = local.parent.parent.joinpath("mklocal")
if not top_level.is_symlink():
assert not top_level.exists()
top_level.symlink_to(local)

if local.is_symlink():
if local.is_symlink() and top_level.is_symlink():
return

# If it's not a symlink, it shouldn't be any other kind of file.
Expand All @@ -48,6 +43,11 @@ def audit_local_tasks() -> None:
# Create the link.
local.symlink_to(vmklib)

# Also link a top-level file.
if not top_level.is_symlink():
assert not top_level.exists()
top_level.symlink_to(local)


def register(
manager: TaskManager,
Expand Down
Loading