diff --git a/examples/test_program.zip b/examples/test_program.zip new file mode 100644 index 00000000..074ae44f Binary files /dev/null and b/examples/test_program.zip differ diff --git a/src/aleph_client/asynchronous.py b/src/aleph_client/asynchronous.py index 6e86147f..4b1ec51c 100644 --- a/src/aleph_client/asynchronous.py +++ b/src/aleph_client/asynchronous.py @@ -64,9 +64,9 @@ def get_fallback_session() -> ClientSession: async def ipfs_push( - content, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + content, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> str: session = session or get_fallback_session() @@ -79,9 +79,9 @@ async def ipfs_push( async def storage_push( - content, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + content, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> str: session = session or get_fallback_session() @@ -94,9 +94,9 @@ async def storage_push( async def ipfs_push_file( - file_content, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + file_content, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> str: session = session or get_fallback_session() @@ -112,9 +112,9 @@ async def ipfs_push_file( async def storage_push_file( - file_content, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + file_content, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> str: session = session or get_fallback_session() @@ -130,9 +130,9 @@ async def storage_push_file( async def broadcast( - message, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + message, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> None: """Broadcast a message on the Aleph network via pubsub for nodes to pick it up.""" session = session or get_fallback_session() @@ -141,8 +141,8 @@ async def broadcast( logger.debug(f"Posting message on {url}") async with session.post( - url, - json={"topic": "ALEPH-TEST", "data": json.dumps(message)}, + url, + json={"topic": "ALEPH-TEST", "data": json.dumps(message)}, ) as response: response.raise_for_status() result = await response.json() @@ -169,16 +169,16 @@ async def broadcast( async def create_post( - account: Account, - post_content, - post_type: str, - ref: Optional[str] = None, - address: Optional[str] = settings.ADDRESS_TO_USE, - channel: str = settings.DEFAULT_CHANNEL, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, - inline: bool = True, - storage_engine: StorageEnum = StorageEnum.storage, + account: Account, + post_content, + post_type: str, + ref: Optional[str] = None, + address: Optional[str] = settings.ADDRESS_TO_USE, + channel: str = settings.DEFAULT_CHANNEL, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, + inline: bool = True, + storage_engine: StorageEnum = StorageEnum.storage, ) -> PostMessage: address = address or account.get_address() @@ -203,14 +203,14 @@ async def create_post( async def create_aggregate( - account: Account, - key, - content, - address: Optional[str] = settings.ADDRESS_TO_USE, - channel: str = settings.DEFAULT_CHANNEL, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, - inline: bool = True, + account: Account, + key, + content, + address: Optional[str] = settings.ADDRESS_TO_USE, + channel: str = settings.DEFAULT_CHANNEL, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, + inline: bool = True, ) -> AggregateMessage: address = address or account.get_address() @@ -233,17 +233,17 @@ async def create_aggregate( async def create_store( - account: Account, - address=settings.ADDRESS_TO_USE, - file_content: Optional[bytes] = None, - file_hash: Optional[str] = None, - guess_mime_type: bool = False, - ref: Optional[str] = None, - storage_engine=StorageEnum.storage, - extra_fields: Optional[dict] = None, - channel: str = settings.DEFAULT_CHANNEL, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + account: Account, + address=settings.ADDRESS_TO_USE, + file_content: Optional[bytes] = None, + file_hash: Optional[str] = None, + guess_mime_type: bool = False, + ref: Optional[str] = None, + storage_engine=StorageEnum.storage, + extra_fields: Optional[dict] = None, + channel: str = settings.DEFAULT_CHANNEL, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> StoreMessage: address = address or account.get_address() extra_fields = extra_fields or {} @@ -296,26 +296,31 @@ async def create_store( async def create_program( - account: Account, - program_ref: str, - entrypoint: str, - runtime: str, - environment_variables: Optional[Dict[str, str]] = None, - storage_engine: StorageEnum = StorageEnum.storage, - channel: str = settings.DEFAULT_CHANNEL, - address: Optional[str] = settings.ADDRESS_TO_USE, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, - memory: int = settings.DEFAULT_VM_MEMORY, - vcpus: int = settings.DEFAULT_VM_VCPUS, - timeout_seconds: float = settings.DEFAULT_VM_TIMEOUT, - persistent: bool = False, - encoding: Encoding = Encoding.zip, - volumes: Optional[List[Dict]] = None, - subscriptions: Optional[List[Dict]] = None, + account: Account, + program_ref: str, + entrypoint: str, + runtime: str, + allow_amend: bool, + use_latest: bool, + reproducible: bool, + internet: bool, + aleph_api: bool, + environment_variables: Optional[Dict[str, str]] = None, + storage_engine: StorageEnum = StorageEnum.storage, + channel: str = settings.DEFAULT_CHANNEL, + address: Optional[str] = settings.ADDRESS_TO_USE, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, + memory: int = settings.DEFAULT_VM_MEMORY, + vcpus: int = settings.DEFAULT_VM_VCPUS, + timeout_seconds: float = settings.DEFAULT_VM_TIMEOUT, + persistent: bool = False, + encoding: Encoding = Encoding.zip, + volumes: Optional[List[Dict]] = None, + subscriptions: Optional[List[Dict]] = None, + ) -> ProgramMessage: volumes = volumes if volumes is not None else [] - address = address or account.get_address() # TODO: Check that program_ref, runtime and data_ref exist @@ -332,18 +337,18 @@ async def create_program( **{ "type": "vm-function", "address": address, - "allow_amend": False, + "allow_amend": allow_amend, "code": { "encoding": encoding, "entrypoint": entrypoint, "ref": program_ref, - "use_latest": True, + "use_latest": use_latest, }, "on": triggers, "environment": { - "reproducible": False, - "internet": True, - "aleph_api": True, + "reproducible": reproducible, + "internet": internet, + "aleph_api": aleph_api, }, "variables": environment_variables, "resources": { @@ -353,7 +358,7 @@ async def create_program( }, "runtime": { "ref": runtime, - "use_latest": True, + "use_latest": use_latest, "comment": "Official Aleph runtime" if runtime == settings.DEFAULT_RUNTIME_ID else "", @@ -390,14 +395,14 @@ async def create_program( async def forget( - account: Account, - hashes: List[str], - reason: Optional[str], - storage_engine: StorageEnum = StorageEnum.storage, - channel: str = settings.DEFAULT_CHANNEL, - address: Optional[str] = settings.ADDRESS_TO_USE, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + account: Account, + hashes: List[str], + reason: Optional[str], + storage_engine: StorageEnum = StorageEnum.storage, + channel: str = settings.DEFAULT_CHANNEL, + address: Optional[str] = settings.ADDRESS_TO_USE, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> ForgetMessage: address = address or account.get_address() @@ -421,17 +426,17 @@ async def forget( async def submit( - account: Account, - content: Dict, - message_type: MessageType, - channel: str = settings.DEFAULT_CHANNEL, - api_server: str = settings.API_HOST, - storage_engine: StorageEnum = StorageEnum.storage, - session: Optional[ClientSession] = None, - inline: bool = True, + account: Account, + content: Dict, + message_type: MessageType, + channel: str = settings.DEFAULT_CHANNEL, + api_server: str = settings.API_HOST, + storage_engine: StorageEnum = StorageEnum.storage, + session: Optional[ClientSession] = None, + inline: bool = True, ) -> AlephMessage: message: Dict[str, Any] = { - #'item_hash': ipfs_hash, + # 'item_hash': ipfs_hash, "chain": account.CHAIN, "channel": channel, "sender": account.get_address(), @@ -468,11 +473,11 @@ async def submit( async def fetch_aggregate( - address: str, - key: str, - limit: Optional[int] = 100, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + address: str, + key: str, + limit: Optional[int] = 100, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> Dict[str, Dict]: session = session or get_fallback_session() @@ -481,7 +486,7 @@ async def fetch_aggregate( params["limit"] = limit async with session.get( - f"{api_server}/api/v0/aggregates/{address}.json", params=params + f"{api_server}/api/v0/aggregates/{address}.json", params=params ) as resp: result = await resp.json() data = result.get("data", dict()) @@ -489,11 +494,11 @@ async def fetch_aggregate( async def fetch_aggregates( - address: str, - keys: Optional[Iterable[str]] = None, - limit: Optional[int] = 100, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + address: str, + keys: Optional[Iterable[str]] = None, + limit: Optional[int] = 100, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> Dict[str, Dict]: session = session or get_fallback_session() @@ -505,8 +510,8 @@ async def fetch_aggregates( params["limit"] = limit async with session.get( - f"{api_server}/api/v0/aggregates/{address}.json", - params=params, + f"{api_server}/api/v0/aggregates/{address}.json", + params=params, ) as resp: result = await resp.json() data = result.get("data", dict()) @@ -514,19 +519,19 @@ async def fetch_aggregates( async def get_posts( - pagination: int = 200, - page: int = 1, - types: Optional[Iterable[str]] = None, - refs: Optional[Iterable[str]] = None, - addresses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - hashes: Optional[Iterable[str]] = None, - channels: Optional[Iterable[str]] = None, - chains: Optional[Iterable[str]] = None, - start_date: Optional[Union[datetime, float]] = None, - end_date: Optional[Union[datetime, float]] = None, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + pagination: int = 200, + page: int = 1, + types: Optional[Iterable[str]] = None, + refs: Optional[Iterable[str]] = None, + addresses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + hashes: Optional[Iterable[str]] = None, + channels: Optional[Iterable[str]] = None, + chains: Optional[Iterable[str]] = None, + start_date: Optional[Union[datetime, float]] = None, + end_date: Optional[Union[datetime, float]] = None, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ): session = session or get_fallback_session() @@ -562,23 +567,23 @@ async def get_posts( async def get_messages( - pagination: int = 200, - page: int = 1, - message_type: Optional[MessageType] = None, - content_types: Optional[Iterable[str]] = None, - content_keys: Optional[Iterable[str]] = None, - refs: Optional[Iterable[str]] = None, - addresses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - hashes: Optional[Iterable[str]] = None, - channels: Optional[Iterable[str]] = None, - chains: Optional[Iterable[str]] = None, - start_date: Optional[Union[datetime, float]] = None, - end_date: Optional[Union[datetime, float]] = None, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, - ignore_invalid_messages: bool = True, - invalid_messages_log_level: int = logging.NOTSET, + pagination: int = 200, + page: int = 1, + message_type: Optional[MessageType] = None, + content_types: Optional[Iterable[str]] = None, + content_keys: Optional[Iterable[str]] = None, + refs: Optional[Iterable[str]] = None, + addresses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + hashes: Optional[Iterable[str]] = None, + channels: Optional[Iterable[str]] = None, + chains: Optional[Iterable[str]] = None, + start_date: Optional[Union[datetime, float]] = None, + end_date: Optional[Union[datetime, float]] = None, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, + ignore_invalid_messages: bool = True, + invalid_messages_log_level: int = logging.NOTSET, ) -> MessagesResponse: session = session or get_fallback_session() @@ -647,11 +652,11 @@ async def get_messages( async def get_message( - item_hash: str, - message_type: Optional[Type[GenericMessage]] = None, - channel: Optional[str] = None, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + item_hash: str, + message_type: Optional[Type[GenericMessage]] = None, + channel: Optional[str] = None, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> GenericMessage: """Get a single message from its `item_hash`.""" messages_response = await get_messages( @@ -678,18 +683,18 @@ async def get_message( async def watch_messages( - message_type: Optional[MessageType] = None, - content_types: Optional[Iterable[str]] = None, - refs: Optional[Iterable[str]] = None, - addresses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - hashes: Optional[Iterable[str]] = None, - channels: Optional[Iterable[str]] = None, - chains: Optional[Iterable[str]] = None, - start_date: Optional[Union[datetime, float]] = None, - end_date: Optional[Union[datetime, float]] = None, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + message_type: Optional[MessageType] = None, + content_types: Optional[Iterable[str]] = None, + refs: Optional[Iterable[str]] = None, + addresses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + hashes: Optional[Iterable[str]] = None, + channels: Optional[Iterable[str]] = None, + chains: Optional[Iterable[str]] = None, + start_date: Optional[Union[datetime, float]] = None, + end_date: Optional[Union[datetime, float]] = None, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> AsyncIterable[AlephMessage]: """ Iterate over current and future matching messages asynchronously. @@ -726,7 +731,7 @@ async def watch_messages( params["endDate"] = end_date async with session.ws_connect( - f"{api_server}/api/ws0/messages", params=params + f"{api_server}/api/ws0/messages", params=params ) as ws: logger.debug("Websocket connected") async for msg in ws: diff --git a/src/aleph_client/commands/__init__.py b/src/aleph_client/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/aleph_client/commands/message.py b/src/aleph_client/commands/message.py index 2a1903df..241b5300 100644 --- a/src/aleph_client/commands/message.py +++ b/src/aleph_client/commands/message.py @@ -1,56 +1,51 @@ +import asyncio import json import os.path import subprocess -from typing import Optional, Dict, List -from pathlib import Path import tempfile -import asyncio +from pathlib import Path +from typing import Optional, Dict, List import typer - from aleph_message.models import ( PostMessage, ForgetMessage, AlephMessage, ) - from aleph_client import synchronous -from aleph_client.commands import help_strings -from aleph_client.types import AccountFromPrivateKey from aleph_client.account import _load_account -from aleph_client.conf import settings - from aleph_client.asynchronous import ( get_fallback_session, StorageEnum, ) - +from aleph_client.commands import help_strings from aleph_client.commands.utils import ( setup_logging, input_multiline, ) - +from aleph_client.conf import settings +from aleph_client.types import AccountFromPrivateKey app = typer.Typer() @app.command() def post( - path: Optional[Path] = typer.Option( - None, - help="Path to the content you want to post. If omitted, you can input your content directly", - ), - type: str = typer.Option("test", help="Text representing the message object type"), - ref: Optional[str] = typer.Option(None, help=help_strings.REF), - channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), - private_key: Optional[str] = typer.Option( - settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY - ), - private_key_file: Optional[Path] = typer.Option( - settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE - ), - debug: bool = False, + path: Optional[Path] = typer.Option( + None, + help="Path to the content you want to post. If omitted, you can input your content directly", + ), + type: str = typer.Option("test", help="Text representing the message object type"), + ref: Optional[str] = typer.Option(None, help=help_strings.REF), + channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), + private_key: Optional[str] = typer.Option( + settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + debug: bool = False, ): """Post a message on Aleph.im.""" @@ -104,14 +99,14 @@ def post( @app.command() def amend( - hash: str = typer.Argument(..., help="Hash reference of the message to amend"), - private_key: Optional[str] = typer.Option( - settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY - ), - private_key_file: Optional[Path] = typer.Option( - settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE - ), - debug: bool = False, + hash: str = typer.Argument(..., help="Hash reference of the message to amend"), + private_key: Optional[str] = typer.Option( + settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + debug: bool = False, ): """Amend an existing Aleph message.""" @@ -149,10 +144,10 @@ def amend( def forget_messages( - account: AccountFromPrivateKey, - hashes: List[str], - reason: Optional[str], - channel: str, + account: AccountFromPrivateKey, + hashes: List[str], + reason: Optional[str], + channel: str, ): try: result: ForgetMessage = synchronous.forget( @@ -169,20 +164,20 @@ def forget_messages( @app.command() def forget( - hashes: str = typer.Argument( - ..., help="Comma separated list of hash references of messages to forget" - ), - reason: Optional[str] = typer.Option( - None, help="A description of why the messages are being forgotten." - ), - channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), - private_key: Optional[str] = typer.Option( - settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY - ), - private_key_file: Optional[Path] = typer.Option( - settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE - ), - debug: bool = False, + hashes: str = typer.Argument( + ..., help="Comma separated list of hash references of messages to forget" + ), + reason: Optional[str] = typer.Option( + None, help="A description of why the messages are being forgotten." + ), + channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), + private_key: Optional[str] = typer.Option( + settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + debug: bool = False, ): """Forget an existing Aleph message.""" @@ -196,9 +191,9 @@ def forget( @app.command() def watch( - ref: str = typer.Argument(..., help="Hash reference of the message to watch"), - indent: Optional[int] = typer.Option(None, help="Number of indents to use"), - debug: bool = False, + ref: str = typer.Argument(..., help="Hash reference of the message to watch"), + indent: Optional[int] = typer.Option(None, help="Number of indents to use"), + debug: bool = False, ): """Watch a hash for amends and print amend hashes""" @@ -207,6 +202,6 @@ def watch( original: AlephMessage = synchronous.get_message(item_hash=ref) for message in synchronous.watch_messages( - refs=[ref], addresses=[original.content.address] + refs=[ref], addresses=[original.content.address] ): typer.echo(f"{message.json(indent=indent)}") diff --git a/src/aleph_client/commands/program.py b/src/aleph_client/commands/program.py index fe904ce9..4d7b23f5 100644 --- a/src/aleph_client/commands/program.py +++ b/src/aleph_client/commands/program.py @@ -1,15 +1,3 @@ -import typer -from typing import Optional, Dict, List -from aleph_client.types import AccountFromPrivateKey -from aleph_client.account import _load_account -from aleph_client.conf import settings -from pathlib import Path -import asyncio -from aleph_client import synchronous -import json -from zipfile import BadZipFile -from aleph_client.commands import help_strings - import asyncio import json import logging @@ -19,19 +7,18 @@ from zipfile import BadZipFile import typer -from aleph_message.models import ( - ProgramMessage, - StoreMessage, -) -from aleph_client.types import AccountFromPrivateKey +from aleph_client import synchronous from aleph_client.account import _load_account +from aleph_client.commands import help_strings +from aleph_client.commands.utils import volume_to_dict +from aleph_client.conf import settings +from aleph_client.types import AccountFromPrivateKey from aleph_client.utils import create_archive logger = logging.getLogger(__name__) app = typer.Typer() - from aleph_client.asynchronous import ( get_fallback_session, StorageEnum, @@ -56,35 +43,67 @@ @app.command() def upload( - path: Path = typer.Argument(..., help="Path to your source code"), - entrypoint: str = typer.Argument(..., help="Your program entrypoint"), - channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), - memory: int = typer.Option( - settings.DEFAULT_VM_MEMORY, help="Maximum memory allocation on vm in MiB" - ), - vcpus: int = typer.Option( - settings.DEFAULT_VM_VCPUS, help="Number of virtual cpus to allocate." - ), - timeout_seconds: float = typer.Option( - settings.DEFAULT_VM_TIMEOUT, - help="If vm is not called after [timeout_seconds] it will shutdown", - ), - private_key: Optional[str] = typer.Option( - settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY - ), - private_key_file: Optional[Path] = typer.Option( - settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE - ), - print_messages: bool = typer.Option(False), - print_code_message: bool = typer.Option(False), - print_program_message: bool = typer.Option(False), - runtime: str = typer.Option( - None, - help="Hash of the runtime to use for your program. Defaults to aleph debian with Python3.8 and node. You can also create your own runtime and pin it", - ), - beta: bool = typer.Option(False), - debug: bool = False, - persistent: bool = False, + path: Path = typer.Argument(..., help="Path to your source code"), + entrypoint: str = typer.Argument(..., help="Your program entrypoint"), + channel: str = typer.Option(settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL), + memory: int = typer.Option( + settings.DEFAULT_VM_MEMORY, help="Maximum memory allocation on vm in MiB" + ), + allow_amend: bool = typer.Option(False, help='Deployed VM image may be changed and deployed on the same URL'), + use_latest: bool = typer.Option(True, help='Use latest version of the base docker image'), + reproducible: bool = typer.Option(False, help=' (Not Implemented)--> Check output is same for other nodes'), + internet: bool = typer.Option(True, help='VM should have internet connectivity'), + aleph_api: bool = typer.Option(True, help='VM needs access to Aleph messages AP'), + vcpus: int = typer.Option( + settings.DEFAULT_VM_VCPUS, help="Number of virtual CPUS to allocate." + ), + timeout_seconds: float = typer.Option( + settings.DEFAULT_VM_TIMEOUT, + help="If vm is not called after [timeout_seconds] it will shutdown", + ), + private_key: Optional[str] = typer.Option( + settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + print_messages: bool = typer.Option(False), + print_code_message: bool = typer.Option(False), + print_program_message: bool = typer.Option(False), + runtime: str = typer.Option( + None, + help="Hash of the runtime to use for your program. Defaults to aleph debian with Python3.8 and node. You can also create your own runtime and pin it", + ), + beta: bool = typer.Option(False), + + debug: bool = False, + persistent: bool = False, + persistent_volume: Optional[List[str]] = typer.Option( + None, + help='''Takes 3 parameters + A persistent volume is allocated on the host machine at any time + eg: Use , to seperate the parameters and no spaces + --persistent_volume persistence=host,name=my-volume,size=100 ./my-program main:app + '''), + + ephemeral_volume: Optional[List[str]] = typer.Option( + None, + help= + '''Takes 1 parameter Only + Ephemeral volumes can move and be removed by the host,Garbage collected basically, when the VM isn't running + eg: Use , to seperate the parameters and no spaces + --ephemeral-volume size_mib=100 ./my-program main:app '''), + + immutable_volume: Optional[List[str]] = typer.Option( + None, + help= + '''Takes 3 parameters + Immutable volume is one whose contents do not change + eg: Use , to seperate the parameters and no spaces + --immutable-volume ref=25a393222692c2f73489dc6710ae87605a96742ceef7b91de4d7ec34bb688d94,use_latest=true,mount=/mnt/volume ./my-program main:app + ''' + ) + ): """Register a program to run on Aleph.im virtual machines from a zip archive.""" @@ -104,15 +123,30 @@ def upload( account: AccountFromPrivateKey = _load_account(private_key, private_key_file) runtime = ( - runtime - or input(f"Ref of runtime ? [{settings.DEFAULT_RUNTIME_ID}] ") - or settings.DEFAULT_RUNTIME_ID + runtime + or input(f"Ref of runtime ? [{settings.DEFAULT_RUNTIME_ID}] ") + or settings.DEFAULT_RUNTIME_ID ) volumes = [] - for volume in prompt_for_volumes(): - volumes.append(volume) - typer.echo("\n") + + # Check if the volumes are empty + if persistent_volume is None or ephemeral_volume is None or immutable_volume is None: + for volume in prompt_for_volumes(): + volumes.append(volume) + typer.echo("\n") + + # else Parse all the volumes that have passed as the cli parameters and put it into volume list + else: + if len(persistent_volume) > 0: + persistent_volume_dict = volume_to_dict(volume=persistent_volume) + volumes.append(persistent_volume_dict) + if len(ephemeral_volume) > 0: + ephemeral_volume_dict = volume_to_dict(volume=ephemeral_volume) + volumes.append(ephemeral_volume_dict) + if len(immutable_volume) > 0: + immutable_volume_dict = volume_to_dict(volume=immutable_volume) + volumes.append(immutable_volume_dict) subscriptions: Optional[List[Dict]] if beta and yes_no_input("Subscribe to messages ?", default=False): @@ -156,6 +190,11 @@ def upload( program_ref=program_ref, entrypoint=entrypoint, runtime=runtime, + allow_amend=allow_amend, + use_latest=use_latest, + reproducible=reproducible, + internet=internet, + aleph_api=aleph_api, storage_engine=StorageEnum.storage, channel=channel, memory=memory, @@ -189,12 +228,12 @@ def upload( @app.command() def update( - hash: str, - path: Path, - private_key: Optional[str] = settings.PRIVATE_KEY_STRING, - private_key_file: Optional[Path] = settings.PRIVATE_KEY_FILE, - print_message: bool = True, - debug: bool = False, + hash: str, + path: Path, + private_key: Optional[str] = settings.PRIVATE_KEY_STRING, + private_key_file: Optional[Path] = settings.PRIVATE_KEY_FILE, + print_message: bool = True, + debug: bool = False, ): """Update the code of an existing program""" @@ -252,10 +291,10 @@ def update( @app.command() def unpersist( - hash: str, - private_key: Optional[str] = settings.PRIVATE_KEY_STRING, - private_key_file: Optional[Path] = settings.PRIVATE_KEY_FILE, - debug: bool = False, + hash: str, + private_key: Optional[str] = settings.PRIVATE_KEY_STRING, + private_key_file: Optional[Path] = settings.PRIVATE_KEY_FILE, + debug: bool = False, ): """Stop a persistent virtual machine by making it non-persistent""" diff --git a/src/aleph_client/commands/utils.py b/src/aleph_client/commands/utils.py index f46af556..44d6b2d4 100644 --- a/src/aleph_client/commands/utils.py +++ b/src/aleph_client/commands/utils.py @@ -1,6 +1,6 @@ import logging from typer import echo -from typing import Optional +from typing import Optional, List, Dict def input_multiline() -> str: @@ -68,3 +68,22 @@ def prompt_for_volumes(): "ref": ref, "use_latest": use_latest, } + + +def volume_to_dict(volume: List[str]) -> Optional[Dict[str, str]]: + if not volume: + return None + dict_store = {} + for word in volume: + split_values = word.split(",") + for param in split_values: + p = param.split("=") + if p[1].isdigit(): + dict_store[p[0]] = int(p[1]) + elif p[1] in ['True', 'true', 'False', 'false']: + dict_store[p[0]] = bool(p[1].capitalize()) + else: + dict_store[p[0]] = p[1] + + return dict_store + diff --git a/src/aleph_client/synchronous.py b/src/aleph_client/synchronous.py index 70d03886..341fe305 100644 --- a/src/aleph_client/synchronous.py +++ b/src/aleph_client/synchronous.py @@ -46,11 +46,11 @@ def func_caller(*args, **kwargs): def get_message( - item_hash: str, - message_type: Optional[Type[GenericMessage]] = None, - channel: Optional[str] = None, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, + item_hash: str, + message_type: Optional[Type[GenericMessage]] = None, + channel: Optional[str] = None, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, ) -> GenericMessage: return wrap_async(asynchronous.get_message)( item_hash=item_hash, @@ -62,28 +62,39 @@ def get_message( def create_program( - account: Account, - program_ref: str, - entrypoint: str, - runtime: str, - environment_variables: Optional[Dict[str, str]] = None, - storage_engine: StorageEnum = StorageEnum.storage, - channel: str = settings.DEFAULT_CHANNEL, - address: Optional[str] = settings.ADDRESS_TO_USE, - session: Optional[ClientSession] = None, - api_server: str = settings.API_HOST, - memory: int = settings.DEFAULT_VM_MEMORY, - vcpus: int = settings.DEFAULT_VM_VCPUS, - timeout_seconds: float = settings.DEFAULT_VM_TIMEOUT, - persistent: bool = False, - encoding: Encoding = Encoding.zip, - volumes: Optional[List[Dict]] = None, - subscriptions: Optional[List[Dict]] = None, + account: Account, + program_ref: str, + entrypoint: str, + runtime: str, + allow_amend:bool, + use_latest: bool, + reproducible: bool, + internet: bool, + aleph_api: bool, + environment_variables: Optional[Dict[str, str]] = None, + storage_engine: StorageEnum = StorageEnum.storage, + channel: str = settings.DEFAULT_CHANNEL, + address: Optional[str] = settings.ADDRESS_TO_USE, + session: Optional[ClientSession] = None, + api_server: str = settings.API_HOST, + memory: int = settings.DEFAULT_VM_MEMORY, + vcpus: int = settings.DEFAULT_VM_VCPUS, + timeout_seconds: float = settings.DEFAULT_VM_TIMEOUT, + persistent: bool = False, + encoding: Encoding = Encoding.zip, + volumes: Optional[List[Dict]] = None, + + subscriptions: Optional[List[Dict]] = None, ): return wrap_async(asynchronous.create_program)( account=account, program_ref=program_ref, entrypoint=entrypoint, + allow_amend=allow_amend, + use_latest=use_latest, + reproducible=reproducible, + internet=internet, + aleph_api=aleph_api, environment_variables=environment_variables, runtime=runtime, storage_engine=storage_engine, diff --git a/tests/unit/test_asynchronous.py b/tests/unit/test_asynchronous.py index 252bad19..08fc859a 100644 --- a/tests/unit/test_asynchronous.py +++ b/tests/unit/test_asynchronous.py @@ -10,9 +10,6 @@ ForgetMessage, ) -from aleph_client.conf import settings -from aleph_client.types import StorageEnum - from aleph_client.asynchronous import ( create_post, _get_fallback_session, @@ -23,6 +20,8 @@ ) from aleph_client.chains.common import get_fallback_private_key from aleph_client.chains.ethereum import ETHAccount +from aleph_client.conf import settings +from aleph_client.types import StorageEnum def new_mock_session_with_post_success(): @@ -115,7 +114,6 @@ async def test_create_store(): mock_ipfs_push_file.return_value = "QmRTV3h1jLcACW4FRfdisokkQAk4E4qDhUzGpgdrd4JAFy" with patch("aleph_client.asynchronous.ipfs_push_file", mock_ipfs_push_file): - await create_store( account=account, file_content=b"HELLO", @@ -140,7 +138,6 @@ async def test_create_store(): mock_storage_push_file.return_value = "QmRTV3h1jLcACW4FRfdisokkQAk4E4qDhUzGpgdrd4JAFy" with patch("aleph_client.asynchronous.storage_push_file", mock_storage_push_file): - new_post = await create_store( account=account, file_content=b"HELLO", @@ -203,3 +200,4 @@ async def test_forget(): assert mock_session.post.called assert isinstance(new_post, ForgetMessage) + diff --git a/tests/unit/test_asynchronous_get.py b/tests/unit/test_asynchronous_get.py index 24b9198f..2f03a954 100644 --- a/tests/unit/test_asynchronous_get.py +++ b/tests/unit/test_asynchronous_get.py @@ -1,5 +1,6 @@ import pytest from aleph_message.models import MessageType, MessagesResponse +import unittest from aleph_client.asynchronous import ( get_messages, @@ -57,3 +58,7 @@ async def test_get_messages(): assert len(messages) > 1 assert messages[0].type assert messages[0].sender + + +if __name__ == '__main __': + unittest.main()