From 92ad3e4e1a70f961eead62f059e00bcdf5754da8 Mon Sep 17 00:00:00 2001 From: Mike Hukiewitz <70762838+MHHukiewitz@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:05:32 +0100 Subject: [PATCH] Extend entrypoint parameter (#77) * Add Entrypoint class and update CodeContent model to reference Entrypoint. Allows distinction between different types of plain code executions and more freedom in defining how it should be run. * Update `entrypoint` field to also accept strings for backwards compatibility. * Use Union for Python 3.9 compatibility * Remove Entrypoint model and integrate some fields into CodeContent * Extend parsing of `ProgramMessage` to account for default values Default values in `ProgramContent` could make `item_content` verification fail, as they get added upon parsing. Creating `item_content` now triggers a parsing of `ProgramContent` and dumps its resulting struct. * Fix formatting * Fix typing * Format with isort & black * Fix rebase * Fix typing for mypy * Change all new fields to have None default values to not break backward compatibility --- aleph_message/models/__init__.py | 20 +++++++------------- aleph_message/models/execution/base.py | 8 ++++++++ aleph_message/models/execution/instance.py | 8 +++----- aleph_message/models/execution/program.py | 20 ++++++++++++++++---- aleph_message/tests/test_models.py | 13 +++++++------ 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/aleph_message/models/__init__.py b/aleph_message/models/__init__.py index 721d267..eee25ce 100644 --- a/aleph_message/models/__init__.py +++ b/aleph_message/models/__init__.py @@ -4,15 +4,7 @@ from hashlib import sha256 from json import JSONDecodeError from pathlib import Path -from typing import ( - Any, - Dict, - List, - Literal, - Optional, - Type, - Union, TypeVar, cast, -) +from typing import Any, Dict, List, Literal, Optional, Type, TypeVar, Union, cast from pydantic import BaseModel, Extra, Field, validator from typing_extensions import TypeAlias @@ -174,7 +166,9 @@ class BaseMessage(BaseModel): size: Optional[int] = Field( default=None, description="Size of the content" ) # Almost always present - time: datetime.datetime = Field(description="Unix timestamp or datetime when the message was published") + time: datetime.datetime = Field( + description="Unix timestamp or datetime when the message was published" + ) item_type: ItemType = Field(description="Storage method used for the content") item_content: Optional[str] = Field( default=None, @@ -189,7 +183,7 @@ class BaseMessage(BaseModel): forgotten_by: Optional[List[str]] @validator("item_content") - def check_item_content(cls, v: Optional[str], values): + def check_item_content(cls, v: Optional[str], values) -> Optional[str]: item_type = values["item_type"] if v is None: return None @@ -207,7 +201,7 @@ def check_item_content(cls, v: Optional[str], values): return v @validator("item_hash") - def check_item_hash(cls, v, values): + def check_item_hash(cls, v: ItemHash, values) -> ItemHash: item_type = values["item_type"] if item_type == ItemType.inline: item_content: str = values["item_content"] @@ -313,7 +307,7 @@ class InstanceMessage(BaseMessage): ] -T = TypeVar('T', bound=AlephMessage) +T = TypeVar("T", bound=AlephMessage) AlephMessageType: TypeAlias = Type[T] diff --git a/aleph_message/models/execution/base.py b/aleph_message/models/execution/base.py index 64e02e9..5a6d43f 100644 --- a/aleph_message/models/execution/base.py +++ b/aleph_message/models/execution/base.py @@ -43,3 +43,11 @@ class Payment(HashableModel): @property def is_stream(self): return self.type == PaymentType.superfluid + + +class Interface(str, Enum): + """Two types of program interfaces supported: + Running plain binary and ASGI apps.""" + + asgi = "asgi" + binary = "binary" diff --git a/aleph_message/models/execution/instance.py b/aleph_message/models/execution/instance.py index 7530ac6..d4ce223 100644 --- a/aleph_message/models/execution/instance.py +++ b/aleph_message/models/execution/instance.py @@ -1,13 +1,11 @@ from __future__ import annotations -from typing import Any, Dict - from pydantic import Field from aleph_message.models.abstract import HashableModel -from aleph_message.models.item_hash import ItemHash + from .abstract import BaseExecutableContent -from .volume import VolumePersistence, PersistentVolumeSizeMib, ParentVolume +from .volume import ParentVolume, PersistentVolumeSizeMib, VolumePersistence class RootfsVolume(HashableModel): @@ -17,6 +15,7 @@ class RootfsVolume(HashableModel): The root file system of an instance is built as a copy of a reference image, named parent image. The user determines a custom size and persistence model. """ + parent: ParentVolume persistence: VolumePersistence # Use the same size constraint as persistent volumes for now @@ -29,4 +28,3 @@ class InstanceContent(BaseExecutableContent): rootfs: RootfsVolume = Field( description="Root filesystem of the system, will be booted by the kernel" ) - diff --git a/aleph_message/models/execution/program.py b/aleph_message/models/execution/program.py index 36cf300..d413c76 100644 --- a/aleph_message/models/execution/program.py +++ b/aleph_message/models/execution/program.py @@ -1,14 +1,14 @@ from __future__ import annotations -from typing import Literal, Optional +from typing import Literal, Optional, Union -from pydantic import Field +from pydantic import Field, validator -from .environment import FunctionTriggers from ..abstract import HashableModel from ..item_hash import ItemHash from .abstract import BaseExecutableContent -from .base import Encoding, MachineType +from .base import Encoding, Interface, MachineType +from .environment import FunctionTriggers class FunctionRuntime(HashableModel): @@ -23,8 +23,20 @@ class CodeContent(HashableModel): encoding: Encoding entrypoint: str ref: ItemHash # Must reference a StoreMessage + interface: Optional[Interface] = None + args: Optional[list[str]] = None use_latest: bool = False + @property + def inferred_interface(self) -> Interface: + """The initial behaviour is to use asgi, if there is a semicolon in the entrypoint. Else, assume its a binary on port 8000.""" + if self.interface: + return self.interface + elif ":" in self.entrypoint: + return Interface.asgi + else: + return Interface.binary + class DataContent(HashableModel): """Reference to the StoreMessage that contains the input data of a program.""" diff --git a/aleph_message/tests/test_models.py b/aleph_message/tests/test_models.py index 48d587a..1668dab 100644 --- a/aleph_message/tests/test_models.py +++ b/aleph_message/tests/test_models.py @@ -20,12 +20,13 @@ MessageType, PostContent, PostMessage, + ProgramContent, ProgramMessage, add_item_content_and_hash, create_message_from_file, create_message_from_json, create_new_message, - parse_message, AlephMessage, + parse_message, ) from aleph_message.tests.download_messages import MESSAGES_STORAGE_PATH @@ -87,7 +88,7 @@ def test_messages_last_page(): if message_dict["item_hash"] in HASHES_TO_IGNORE: continue - message: AlephMessage = parse_message(message_dict) + message = parse_message(message_dict) assert message @@ -285,14 +286,14 @@ def test_create_new_message(): "signature": "0x123456789", # Signature validation requires using aleph-client } - new_message_1: PostMessage = create_new_message(message_dict, factory=PostMessage) + new_message_1 = create_new_message(message_dict, factory=PostMessage) assert new_message_1 assert new_message_1.type == MessageType.post # Check that the time was converted to a datetime - assert new_message_1.time.isoformat() == '2021-07-07T10:04:47.017000+00:00' + assert new_message_1.time.isoformat() == "2021-07-07T10:04:47.017000+00:00" # The time field can be either a float or a datetime as string - message_dict["time"] = '2021-07-07T10:04:47.017000+00:00' + message_dict["time"] = "2021-07-07T10:04:47.017000+00:00" new_message_2 = create_message_from_json( json.dumps(message_dict), factory=PostMessage ) @@ -308,7 +309,7 @@ def test_messages_from_disk(): data_dict = json.load(page_fd) for message_dict in data_dict["messages"]: try: - message: AlephMessage = parse_message(message_dict) + message = parse_message(message_dict) assert message except ValidationError as e: console.print("-" * 79)