From da1b2f002713e27f99b55aab5ef41977a1e0dcaa Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 23 Oct 2024 04:48:28 +0000 Subject: [PATCH 1/3] 5.7.7 - Minor improvements --- .github/workflows/python-package.yml | 2 +- README.md | 4 ++-- local/variables/package.yaml | 2 +- pyproject.toml | 2 +- runtimepy/__init__.py | 4 ++-- runtimepy/metrics/channel.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5d5c579d..f5cb821e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -77,7 +77,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=runtimepy version=5.7.6 + repo=runtimepy version=5.7.7 if: | matrix.python-version == '3.12' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index ed16da0b..9aa7c961 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.4 - hash=41ff8742602f69f2831092a4b666cddc + hash=0bf47d2ff3fb0ed78ed013f4fcbc59e2 ===================================== --> -# runtimepy ([5.7.6](https://pypi.org/project/runtimepy/)) +# runtimepy ([5.7.7](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) diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 26f18677..250c64bb 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 5 minor: 7 -patch: 6 +patch: 7 entry: runtimepy diff --git a/pyproject.toml b/pyproject.toml index 548affa6..ac0902bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "runtimepy" -version = "5.7.6" +version = "5.7.7" description = "A framework for implementing Python services." readme = "README.md" requires-python = ">=3.11" diff --git a/runtimepy/__init__.py b/runtimepy/__init__.py index 3f1490ce..a2533f18 100644 --- a/runtimepy/__init__.py +++ b/runtimepy/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=cab7cff2cb194a61178fa386b80eae85 +# hash=ed12f307211d1317e3ce6a7c4409b97f # ===================================== """ @@ -10,7 +10,7 @@ DESCRIPTION = "A framework for implementing Python services." PKG_NAME = "runtimepy" -VERSION = "5.7.6" +VERSION = "5.7.7" # runtimepy-specific content. METRICS_NAME = "metrics" diff --git a/runtimepy/metrics/channel.py b/runtimepy/metrics/channel.py index ae6996d6..1fd517d4 100644 --- a/runtimepy/metrics/channel.py +++ b/runtimepy/metrics/channel.py @@ -43,7 +43,7 @@ def update(self, other: "ChannelMetrics") -> None: def __str__(self) -> str: """Get metrics as a string.""" - return "\t".join( + return " | ".join( [ f"messages={self.messages.value}", f"message_rate={self.message_rate.value:.2f}", From b1bab59e6b8b036796b86d99c7d27bf5d0e5c79e Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 23 Oct 2024 06:13:02 +0000 Subject: [PATCH 2/3] Add 'final poll' feature to structs --- runtimepy/net/arbiter/info.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/runtimepy/net/arbiter/info.py b/runtimepy/net/arbiter/info.py index ee03e0b1..7db2d495 100644 --- a/runtimepy/net/arbiter/info.py +++ b/runtimepy/net/arbiter/info.py @@ -6,6 +6,7 @@ from abc import ABC as _ABC import asyncio as _asyncio from contextlib import AsyncExitStack as _AsyncExitStack +from contextlib import contextmanager from dataclasses import dataclass from logging import getLogger as _getLogger from re import compile as _compile @@ -57,13 +58,29 @@ class RuntimeStruct(RuntimeStructBase, _ABC): byte_order: ByteOrder = DEFAULT_BYTE_ORDER + # Set this for structs to automatically be polled when the application is + # going down. + final_poll = False + def init_env(self) -> None: """Initialize this sample environment.""" + @contextmanager + def _final_poll(self) -> _Iterator[None]: + """Poll when the context ends.""" + + try: + yield + finally: + self.poll() + async def build(self, app: "AppInfo", **kwargs) -> None: """Build a struct instance's channel environment.""" self.app = app + if self.final_poll: + self.app.stack.enter_context(self._final_poll()) + self.init_env() self.update_byte_order(self.byte_order, **kwargs) @@ -102,6 +119,9 @@ def poll(self) -> None: class SampleStruct(TrigStruct): """A sample runtime structure.""" + # For fun. + final_poll = True + def init_env(self) -> None: """Initialize this sample environment.""" super().init_env() From 5ebbc7da27f43c44acc0f77c00bcf84eaa9a47e4 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 23 Oct 2024 06:34:14 +0000 Subject: [PATCH 3/3] Add more features to struct poll command --- .../net/arbiter/housekeeping/__init__.py | 6 ++++- runtimepy/struct/__init__.py | 27 ++++++++++++++++++- tests/net/server/__init__.py | 11 ++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/runtimepy/net/arbiter/housekeeping/__init__.py b/runtimepy/net/arbiter/housekeeping/__init__.py index 9c9dbb51..438f1d7d 100644 --- a/runtimepy/net/arbiter/housekeeping/__init__.py +++ b/runtimepy/net/arbiter/housekeeping/__init__.py @@ -52,7 +52,11 @@ async def dispatch(self) -> bool: # Handle any incoming commands. processors: list[Awaitable[None]] = [] - for mapping in self.app.connections.values(), self.app.tasks.values(): + for mapping in ( + self.app.connections.values(), + self.app.tasks.values(), + self.app.structs.values(), + ): for item in mapping: if isinstance(item, AsyncCommandProcessingMixin): processors.append(item.process_command_queue()) diff --git a/runtimepy/struct/__init__.py b/runtimepy/struct/__init__.py index 2403f13b..c4258ad8 100644 --- a/runtimepy/struct/__init__.py +++ b/runtimepy/struct/__init__.py @@ -3,7 +3,10 @@ """ # built-in +from argparse import Namespace +import asyncio from logging import getLogger as _getLogger +from typing import Optional # third-party from vcorelib.io import MarkdownMixin @@ -11,15 +14,20 @@ # internal from runtimepy import PKG_NAME +from runtimepy.channel.environment.command import FieldOrChannel from runtimepy.channel.environment.command.processor import ( ChannelCommandProcessor, ) +from runtimepy.mixins.async_command import AsyncCommandProcessingMixin from runtimepy.mixins.environment import ChannelEnvironmentMixin from runtimepy.mixins.logging import LoggerMixinLevelControl class RuntimeStructBase( - LoggerMixinLevelControl, ChannelEnvironmentMixin, MarkdownMixin + LoggerMixinLevelControl, + ChannelEnvironmentMixin, + AsyncCommandProcessingMixin, + MarkdownMixin, ): """A base runtime structure.""" @@ -42,6 +50,23 @@ def __init__( self.command = ChannelCommandProcessor(self.env, self.logger) self.config = config + async def poll(args: Namespace, __: Optional[FieldOrChannel]) -> None: + """Handle a test command.""" + + count = 1 + delay = 0.0 + + if args.extra: + count = int(args.extra[0]) + if len(args.extra) > 1: + delay = float(args.extra[1]) + + for _ in range(count): + self.poll() + await asyncio.sleep(delay) + + self._setup_async_commands(poll) + def poll(self) -> None: """ A method that other runtime entities can call to perform canonical diff --git a/tests/net/server/__init__.py b/tests/net/server/__init__.py index bd6b349e..1320a503 100644 --- a/tests/net/server/__init__.py +++ b/tests/net/server/__init__.py @@ -139,6 +139,17 @@ async def runtimepy_http_client_server( client.request_json( RequestHeader(method="POST", target="/sample1/custom/asdf") ), + client.request_json( + RequestHeader(method="POST", target="/struct2/custom/poll") + ), + client.request_json( + RequestHeader(method="POST", target="/struct2/custom/poll/5") + ), + client.request_json( + RequestHeader( + method="POST", target="/struct2/custom/poll/5/0.01" + ) + ), client.request_json( RequestHeader( method="POST", target="/sample1/custom/test_command"