Skip to content

Commit

Permalink
Merge pull request #30 from vkottler/dev/protocol-dev
Browse files Browse the repository at this point in the history
Dev/protocol dev
  • Loading branch information
vkottler authored Feb 10, 2023
2 parents ffc1973 + f5f3534 commit 83b4914
Show file tree
Hide file tree
Showing 14 changed files with 1,625 additions and 1,135 deletions.
6 changes: 3 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.0
hash=edfe0b431e5e5c87a6a1e84b9e3a12f6
hash=a8255694100a058fa4413abe0341a123
=====================================
-->

# runtimepy ([0.12.3](https://pypi.org/project/runtimepy/))
# runtimepy ([0.13.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 @@ -41,7 +41,7 @@ This package is tested on the following platforms:
# Command-line Options

```
$ ./venv3.7/bin/runtimepy -h
$ ./venv3.8/bin/runtimepy -h
usage: runtimepy [-h] [--version] [-v] [-C DIR] {tui,noop} ...
Expand Down
2,608 changes: 1,482 additions & 1,126 deletions im/pydeps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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: 0
minor: 12
patch: 3
minor: 13
patch: 0
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 = "0.12.3"
version = "0.13.0"
description = "A framework for implementing Python services."
readme = "README.md"
requires-python = ">=3.7"
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.0
# hash=f5cf7cc360be1ec5780bef4fa93e0a53
# hash=1a71c8001105fcdb5a240f10db7ba967
# =====================================

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

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "0.12.3"
VERSION = "0.13.0"
3 changes: 3 additions & 0 deletions runtimepy/codec/protocol/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ def __init__(
names: _NameRegistry = None,
fields: BitFieldsManager = None,
build: _List[_Union[int, FieldSpec]] = None,
identifier: int = 1,
) -> None:
"""Initialize this protocol."""

self.id = identifier

# Each instance gets its own array.
self.array = PrimitiveArray()

Expand Down
24 changes: 23 additions & 1 deletion runtimepy/codec/protocol/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""

# built-in
from json import dumps as _dumps
from typing import BinaryIO as _BinaryIO
from typing import Dict as _Dict
from typing import List as _List
from typing import Set as _Set
Expand All @@ -25,18 +27,32 @@
)

BUILD_KEY = "build"
META_KEY = "meta"
T = _TypeVar("T", bound="JsonProtocol")


class JsonProtocol(ProtocolBase):
"""A class for defining runtime communication protocols."""

def meta_str(self, resolve_enum: bool = True) -> str:
"""Get protocol metadata as a string.."""
return _dumps(self.export_json(resolve_enum=resolve_enum))

def meta_bytes(self, resolve_enum: bool = True) -> bytes:
"""Get protocol metadata as a bytes object."""
return self.meta_str(resolve_enum=resolve_enum).encode()

def write_meta(self, stream: _BinaryIO, resolve_enum: bool = True) -> int:
"""Write protocol metadata to a stream."""
return stream.write(self.meta_bytes(resolve_enum=resolve_enum))

def export_json(
self, resolve_enum: bool = True
) -> _Dict[str, _JsonObject]:
"""Export this protocol's data to JSON."""

data = self._fields.export_json(resolve_enum=resolve_enum)
data[META_KEY] = {"id": self.id}

# Export regular-field names.
json_obj = data[NAMES_KEY]
Expand Down Expand Up @@ -101,7 +117,13 @@ def import_json(cls: _Type[T], data: _Dict[str, _JsonObject]) -> T:
)
)

result = cls(fields.enums, fields.registry, fields=fields, build=build)
result = cls(
fields.enums,
fields.registry,
fields=fields,
build=build,
identifier=_cast(int, data[META_KEY]["id"]),
)

# Set values.
for name, value in values.items():
Expand Down
19 changes: 19 additions & 0 deletions runtimepy/enum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"""

# built-in
from enum import IntEnum as _IntEnum
from typing import Optional as _Optional
from typing import Type as _Type
from typing import Union as _Union
from typing import cast as _cast

Expand Down Expand Up @@ -138,3 +140,20 @@ def register_int(self, name: str) -> _Optional[int]:
def register_bool(self, name: str, value: bool) -> bool:
"""Register a boolean enumeration."""
return self.bools.register(name, value)

@staticmethod
def data_from_enum(enum: _Type[_IntEnum]) -> _JsonObject:
"""Get JSON data from an enumeration class."""

return {
"type": "int",
"items": {x.name.lower(): x.value for x in enum},
}

@staticmethod
def from_enum(enum: _Type[_IntEnum], identifier: int) -> "RuntimeEnum":
"""Create a runtime enumeration from an enum class."""

data = RuntimeEnum.data_from_enum(enum)
data["id"] = identifier
return RuntimeEnum.create(data)
23 changes: 23 additions & 0 deletions runtimepy/enum/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

# built-in
from enum import IntEnum as _IntEnum
from typing import Optional as _Optional
from typing import Type as _Type
from typing import cast as _cast
Expand Down Expand Up @@ -37,3 +38,25 @@ def enum(
if items is not None:
data["items"] = items # type: ignore
return self.register_dict(name, data)


class RuntimeIntEnum(_IntEnum):
"""An integer enumeration extension."""

@classmethod
def runtime_enum(cls, identifier: int) -> _RuntimeEnum:
"""Obtain a runtime enumeration from this class."""
return _RuntimeEnum.from_enum(cls, identifier)

@classmethod
def register_enum(
cls, registry: EnumRegistry, name: str = None
) -> _RuntimeEnum:
"""Register an enumeration to a registry."""

if name is None:
name = cls.__name__

result = registry.register_dict(name, _RuntimeEnum.data_from_enum(cls))
assert result is not None
return result
13 changes: 13 additions & 0 deletions runtimepy/telemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
A module implementing a telemetry interface.
"""

# internal
from runtimepy.enum.registry import RuntimeIntEnum as _RuntimeIntEnum


class MessageType(_RuntimeIntEnum):
"""An enumeration of viable message types."""

PROTOCOL_META = 1
PROTOCOL_DATA = 2
7 changes: 7 additions & 0 deletions tests/codec/test_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

# built-in
from copy import copy
from io import BytesIO
from json import load

# module under test
from runtimepy.codec.protocol import Protocol
Expand Down Expand Up @@ -53,3 +55,8 @@ def test_protocol_basic():
new_proto = Protocol.import_json(proto.export_json())
assert new_proto["test1"] == 40
assert proto["flag1"] == "valve_open"

with BytesIO() as stream:
assert proto.write_meta(stream)
stream.seek(0)
assert Protocol.import_json(load(stream))
26 changes: 26 additions & 0 deletions tests/enum/test_enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Test the 'enum' module.
"""

# built-in
from enum import IntEnum, auto

# module under test
from runtimepy.enum import RuntimeEnum


class SampleEnum(IntEnum):
"""A sample enumeration."""

A = auto()
B = auto()
C = auto()


def test_runtime_enum_from_enum():
"""Test that we can create a runtime enumeration from a class."""

enum = RuntimeEnum.from_enum(SampleEnum, 1)
assert enum.get_int("a")
assert enum.get_int("b")
assert enum.get_int("c")
Empty file added tests/telemetry/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions tests/telemetry/test_telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Test the 'telemetry' module.
"""

# module under test
from runtimepy.enum.registry import EnumRegistry
from runtimepy.telemetry import MessageType


def test_message_type_basic():
"""Test basic interactions with the message-type enumeration."""

enum = MessageType.runtime_enum(1)
assert enum.get_int("protocol_meta") == 1
assert enum.get_int("protocol_data") == 2

enum_reg = EnumRegistry({})
enum = MessageType.register_enum(enum_reg)
assert enum_reg["MessageType"] is enum
assert enum.get_int("protocol_meta") == 1
assert enum.get_int("protocol_data") == 2

0 comments on commit 83b4914

Please sign in to comment.