diff --git a/.gitignore b/.gitignore index 758d63e..0763936 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,4 @@ cover/ .idea venv -node_modules \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..c47e8b5 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.6.10 diff --git a/generate_ts_api/BaseConverter.py b/generate_ts_api/BaseConverter.py new file mode 100644 index 0000000..2c23aa1 --- /dev/null +++ b/generate_ts_api/BaseConverter.py @@ -0,0 +1,90 @@ +import abc +from typing import Optional, List, NewType, Dict, Callable + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType + +task_id_type = NewType('task_id_type', str) + +impl_str = NewType('impl_str', str) + +impl_middleware = Callable[ + [NodeType, 'BasePythonToTypescriptConverter'], + Optional[impl_str] +] + +identifier_str = NewType('identifier_str', str) + +identifier_middleware = Callable[ + [NodeType, 'BasePythonToTypescriptConverter'], + Optional[identifier_str] +] + + +class BasePythonToTypescriptConverter(metaclass=abc.ABCMeta): + impl_middlewares: List[impl_middleware] + identifier_middlewares: List[identifier_middleware] + + def __init__( + self, + impl_middlewares: List[impl_middleware], + identifier_middlewares: List[identifier_middleware], + ): + self.impl_middlewares = impl_middlewares + self.identifier_middlewares = identifier_middlewares + + def get_identifier(self, node: NodeType) -> Optional[identifier_str]: + return run_identifier_middlewares( + middlewares=self.identifier_middlewares, + node=node, + converter=self, + ) + + def get_impl(self, node: NodeType): + result = run_impl_middlewares( + middlewares=self.impl_middlewares, + node=node, + converter=self, + ) + return result + + def convert_args(self, args: Dict[str, NodeType]) -> str: + converted_args = [ + f'{key}: {self.get_identifier(value)},\n' + for key, value in args.items() + ] + return '\n'.join(converted_args) + + + + + +def define_identifier_middleware(fn: identifier_middleware): + return fn + + +def run_identifier_middlewares( + middlewares: List[identifier_middleware], + node: NodeType, + converter: BasePythonToTypescriptConverter, +) -> Optional[identifier_str]: + for i in range(len(middlewares)): + result = middlewares[i](node, converter) + if result is not None: + return result + return None + + +def define_impl_middleware(fn: impl_middleware): + return fn + + +def run_impl_middlewares( + middlewares: List[impl_middleware], + node: NodeType, + converter: BasePythonToTypescriptConverter, +) -> Optional[impl_str]: + for i in range(len(middlewares)): + result = middlewares[i](node, converter) + if result is not None: + return result + return None diff --git a/generate_ts_api/__init__.py b/generate_ts_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generate_ts_api/converter.py b/generate_ts_api/converter.py new file mode 100644 index 0000000..cff74ec --- /dev/null +++ b/generate_ts_api/converter.py @@ -0,0 +1,113 @@ +from textwrap import indent +from typing import Dict, List + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.FunctionFound import FunctionFound +from py_codegen.type_extractor.type_extractor import TypeExtractor + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + identifier_middleware, + impl_middleware, + run_impl_middlewares, +) +from generate_ts_api.identifier_middlewares.builtins import builtin_identifier +from generate_ts_api.identifier_middlewares.class_identifier import class_identifier +from generate_ts_api.identifier_middlewares.list import list_identifier +from generate_ts_api.identifier_middlewares.unknown_to_any import unknown_to_any_identifier +from generate_ts_api.impl_middlewares.class_impl import class_impl_as_ts_type_middleware +from generate_ts_api.options import MarkTsTaskFunction +from task_definitions.TaskDefinitions import TaskDefinitionsGroup + + +class TypescriptTaskConverter(BasePythonToTypescriptConverter): + def __init__( + self, + task_definitions: TaskDefinitionsGroup, + identifier_middlewares: List[identifier_middleware] = None, + impl_middlewares: List[impl_middleware] = None, + ): + super().__init__( + impl_middlewares=impl_middlewares or [ + class_impl_as_ts_type_middleware, + ], + identifier_middlewares=identifier_middlewares or [ + builtin_identifier, + class_identifier, + list_identifier, + unknown_to_any_identifier, + ], + ) + self.task_definitions = task_definitions + self.extractor = TypeExtractor() + + for key, func in self.task_definitions.tasks.items(): + self.extractor.add({ + MarkTsTaskFunction(has_stream=False), + })(func) + + def get_task_definitions(self): + task_funcs = { + key: value + for key, value in + self.extractor.collected_types.items() + if isinstance(value, FunctionFound) + and ( + value.options.__contains__(MarkTsTaskFunction(True)) + or value.options.__contains__(MarkTsTaskFunction(False)) + ) + } + + non_streamed_task_funcs = [ + self.__get_non_streamed_task_func_definition(value) + for value in + task_funcs.values() + if value.options.__contains__(MarkTsTaskFunction(False)) + ] + return '\n\n'.join(non_streamed_task_funcs) + + def __get_non_streamed_task_func_definition(self, task_fn: FunctionFound) -> str: + # TODO: use get_identifier( + """ + example: + + class SomeClientAPI { + some_task(args: { + a: number, + }): Promise { + this.apiInfra.sendTask( + } + """ + return ( + f"{task_fn.name}(args: {{\n" + f"{indent(self.convert_args(task_fn.params), ' ')}\n" + f"}}): Promise<\n" + f"" + f"> {{\n" + f" return this.apiInfra.sendTask({{\n" + f" apiName: {task_fn.name},\n" + f" args,\n" + f" }})\n" + f"}}" + ) + + def get_others_definitions(self): + converted = [ + self.convert_non_task(value) + for value in self.extractor.collected_types.values() + if not ( + isinstance(value, FunctionFound) + and value.options.__contains__(MarkTsTaskFunction(False)) + ) + ] + # converted_without_none = + # return converted + return '\n\n'.join(list(filter(None, converted))) + + def convert_non_task(self, node: NodeType): + result = run_impl_middlewares( + middlewares=self.impl_middlewares, + node=node, + converter=self, + ) + return result diff --git a/generate_ts_api/identifier_middlewares/__init__.py b/generate_ts_api/identifier_middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generate_ts_api/identifier_middlewares/builtins.py b/generate_ts_api/identifier_middlewares/builtins.py new file mode 100644 index 0000000..38aee9b --- /dev/null +++ b/generate_ts_api/identifier_middlewares/builtins.py @@ -0,0 +1,26 @@ +from typing import Optional + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.ClassFound import ClassFound +from py_codegen.type_extractor.utils import is_builtin + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + define_identifier_middleware, + identifier_str, +) + +@define_identifier_middleware +def builtin_identifier( + node: NodeType, + converter: BasePythonToTypescriptConverter, +) -> Optional[identifier_str]: + if not is_builtin(node): + return None + typ = node + if typ == str: + return 'string' + if typ == int or typ == float: + return 'number' + if typ == bool: + return 'boolean' \ No newline at end of file diff --git a/generate_ts_api/identifier_middlewares/class_identifier.py b/generate_ts_api/identifier_middlewares/class_identifier.py new file mode 100644 index 0000000..89cd13a --- /dev/null +++ b/generate_ts_api/identifier_middlewares/class_identifier.py @@ -0,0 +1,20 @@ +from typing import Optional + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.ClassFound import ClassFound + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + define_identifier_middleware, + identifier_str, +) + +@define_identifier_middleware +def class_identifier( + node: NodeType, + converter: BasePythonToTypescriptConverter, +) -> Optional[identifier_str]: + if not isinstance(node, ClassFound): + return None + + return identifier_str(node.name) \ No newline at end of file diff --git a/generate_ts_api/identifier_middlewares/function.py b/generate_ts_api/identifier_middlewares/function.py new file mode 100644 index 0000000..976da40 --- /dev/null +++ b/generate_ts_api/identifier_middlewares/function.py @@ -0,0 +1,21 @@ +from typing import Optional + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.FunctionFound import FunctionFound + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + define_identifier_middleware, + identifier_str, +) + + +@define_identifier_middleware +def function_identifier( + node: NodeType, + converter: BasePythonToTypescriptConverter, +) -> Optional[identifier_str]: + if not isinstance(node, FunctionFound): + return None + + return identifier_str(node.name) \ No newline at end of file diff --git a/generate_ts_api/identifier_middlewares/list.py b/generate_ts_api/identifier_middlewares/list.py new file mode 100644 index 0000000..2c55ee9 --- /dev/null +++ b/generate_ts_api/identifier_middlewares/list.py @@ -0,0 +1,19 @@ +from typing import Optional + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.ListFound import ListFound + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + identifier_str, + define_identifier_middleware, +) + + +@define_identifier_middleware +def list_identifier(node: NodeType, converter: BasePythonToTypescriptConverter) -> Optional[identifier_str]: + if not isinstance(node, ListFound): + return None + + child_identifier = converter.get_identifier(node.typ) + return identifier_str(f"{child_identifier}[]") \ No newline at end of file diff --git a/generate_ts_api/identifier_middlewares/unknown_to_any.py b/generate_ts_api/identifier_middlewares/unknown_to_any.py new file mode 100644 index 0000000..9c88521 --- /dev/null +++ b/generate_ts_api/identifier_middlewares/unknown_to_any.py @@ -0,0 +1,15 @@ +from typing import Optional + +from py_codegen.type_extractor.nodes.BaseNodeType import NodeType +from py_codegen.type_extractor.nodes.ListFound import ListFound + +from generate_ts_api.BaseConverter import ( + BasePythonToTypescriptConverter, + identifier_str, + define_identifier_middleware, +) + + +@define_identifier_middleware +def unknown_to_any_identifier(node: NodeType, converter: BasePythonToTypescriptConverter) -> Optional[identifier_str]: + return identifier_str("any") \ No newline at end of file diff --git a/generate_ts_api/impl_middlewares/__init__.py b/generate_ts_api/impl_middlewares/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generate_ts_api/impl_middlewares/class_impl.py b/generate_ts_api/impl_middlewares/class_impl.py new file mode 100644 index 0000000..602734f --- /dev/null +++ b/generate_ts_api/impl_middlewares/class_impl.py @@ -0,0 +1,25 @@ +from textwrap import indent + +from py_codegen.type_extractor.nodes.BaseNodeType import BaseNodeType +from py_codegen.type_extractor.nodes.ClassFound import ClassFound + +from generate_ts_api.BaseConverter import BasePythonToTypescriptConverter, define_impl_middleware + + +# converts Python-class to typescript-'type' +# this makes sense because typescript can't access class-methods/etc anyway +# (or that can be possible with a lot more effort) +@define_impl_middleware +def class_impl_as_ts_type_middleware( + _class: BaseNodeType, + converter: BasePythonToTypescriptConverter, +): + if not isinstance(_class, ClassFound): + return None + + impl_str = ( + f"type {converter.get_identifier(_class)} = {{\n" + f"{indent(converter.convert_args(_class.fields), ' ')}\n" + f"}}\n" + ) + return impl_str \ No newline at end of file diff --git a/generate_ts_api/options.py b/generate_ts_api/options.py new file mode 100644 index 0000000..00434bf --- /dev/null +++ b/generate_ts_api/options.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass + +from py_codegen.type_extractor.nodes.BaseNodeType import BaseOption + + +class MarkTsTaskFunction(BaseOption): + has_stream: bool = False + + def __key(self): + return (self.has_stream,) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, other): + if isinstance(other, MarkTsTaskFunction): + return self.__key() == other.__key() + return NotImplemented + + def __init__(self, has_stream: bool): + self.has_stream = has_stream \ No newline at end of file diff --git a/generate_ts_api/testrun.py b/generate_ts_api/testrun.py new file mode 100644 index 0000000..8795002 --- /dev/null +++ b/generate_ts_api/testrun.py @@ -0,0 +1,37 @@ +from py_codegen.type_extractor.type_extractor import TypeExtractor + +from generate_ts_api.converter import TypescriptTaskConverter +from sample_tasks import ( + sleep_task, + some_complicated_task, +) +from task_definitions.TaskDefinitions import TaskDefinitionsGroup + + +def testrun(): + task_definitions = TaskDefinitionsGroup() + task_definitions.add_task(sleep_task) + task_definitions.add_task(some_complicated_task) + + converter = TypescriptTaskConverter( + task_definitions=task_definitions, + ) + + print('task_definition: \n', converter.get_task_definitions()) + + print('other_defs: \n', converter.get_others_definitions()) + + # type_extractor = TypeExtractor() + # type_extractor.add({ + # MarkTaskFunction(False), + # })(sleep_task) + # + # converter = PythonTaskConverter( + # extractor=type_extractor, + # ) + # + # result = converter.get_generated_source() + # + # print(result) + +testrun() \ No newline at end of file diff --git a/generate_ts_api/tests/__init__.py b/generate_ts_api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/generate_ts_api/tests/test_simple.py b/generate_ts_api/tests/test_simple.py new file mode 100644 index 0000000..78f09fb --- /dev/null +++ b/generate_ts_api/tests/test_simple.py @@ -0,0 +1,2 @@ +def test_simple(): + print('!') diff --git a/on_pipelines.md b/on_pipelines.md new file mode 100644 index 0000000..c45a5c0 --- /dev/null +++ b/on_pipelines.md @@ -0,0 +1,15 @@ +# pipeline stuff: + +- args-as-promise? + +[...args] -> [...Promise] + +eg. +``` +run_sequentially( +``` + +--> multi-args ? compose ? + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..a113058 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "@types/socket.io-client": "^1.4.32", + "io-ts": "^2.2.0", + "rxjs": "^6.5.5", + "socket.io-client": "^2.3.0" + }, + "devDependencies": { + "ts-node": "^8.8.2", + "typescript": "^3.8.3" + } +} diff --git a/py_client/socketio_cli_test.py b/py_client/socketio_cli_sample.py similarity index 98% rename from py_client/socketio_cli_test.py rename to py_client/socketio_cli_sample.py index a817410..afab699 100644 --- a/py_client/socketio_cli_test.py +++ b/py_client/socketio_cli_sample.py @@ -1,5 +1,4 @@ import asyncio -import json from dataclasses import asdict import socketio @@ -18,10 +17,12 @@ async def on_done(data): def connect(): print("I'm connected!") + @sio.event def connect_error(): print("The connection failed!") + @sio.event def disconnect(): print("I'm disconnected!") diff --git a/sample_tasks/__init__.py b/sample_tasks/__init__.py index eb6cb0b..28d6382 100644 --- a/sample_tasks/__init__.py +++ b/sample_tasks/__init__.py @@ -1,6 +1,34 @@ +from typing import List + +from dataclasses import dataclass from time import sleep +from task_definitions.TaskContext import TaskContext, RemoteFile + -def sleep_task(a: int) -> int: +def sleep_task( + task_context: TaskContext[None], + a: int, +) -> int: sleep(5) - return a \ No newline at end of file + return a + + +@dataclass +class SomeClass: + some_float: List[float] + + +def some_complicated_task( + task_context: TaskContext, + some_cls: SomeClass, +) -> SomeClass: + print(some_cls.some_float) + return some_cls + + +async def some_task_with_file( + task_context: TaskContext, + file1: RemoteFile, +): + f = await task_context.recv_file(file1) diff --git a/sample_tasks/mypy_io_test.py b/sample_tasks/mypy_io_test.py new file mode 100644 index 0000000..bcb6b1b --- /dev/null +++ b/sample_tasks/mypy_io_test.py @@ -0,0 +1,13 @@ +import aiofiles +from typing import IO + + +def parser(file_obj: IO[bytes]) -> None: + pass + + +async def run(): + f = aiofiles.open('hello.txt', 'r') + + # f = open('hello.txt', 'r') + parser(f) \ No newline at end of file diff --git a/server/SocketIOTaskRunner.py b/server/SocketIOTaskRunner.py index 873ca3e..9f0f693 100644 --- a/server/SocketIOTaskRunner.py +++ b/server/SocketIOTaskRunner.py @@ -9,12 +9,12 @@ import socketio from dataclasses import dataclass, field -from task_definitions.TaskDefinitions import TaskDefinition, long_running_task, TaskCall +from task_definitions.TaskDefinitions import TaskDefinitionsGroup, long_running_task, TaskCall @dataclass class SocketIOTaskRunner: - task_definitions: TaskDefinition + task_definitions: TaskDefinitionsGroup # asgi_app: engineio.ASGIApp = engineio.ASGIApp() sio: socketio.AsyncServer = field(default_factory=lambda: socketio.AsyncServer(async_mode='asgi')) pool: ThreadPoolExecutor = field(default_factory=lambda: ThreadPoolExecutor(max_workers=5)) diff --git a/task_definitions/TaskContext.py b/task_definitions/TaskContext.py new file mode 100644 index 0000000..10c046a --- /dev/null +++ b/task_definitions/TaskContext.py @@ -0,0 +1,42 @@ +import abc +from typing import ( + TypeVar, Generic, +) + +ProgressType = TypeVar('ProgressType') + + +# example + +class RemoteFile: + param_name: str + + +class TaskContext( + Generic[ + ProgressType + ] +): + def send_progress(self, progress_val: ProgressType): + pass + + # @abc.abstractmethod + async def recv_file( + self, + remote_file: RemoteFile, + ): + remote_file.param_name + pass + + +async def some_task( + task_context: TaskContext[int], + remote_file_a: RemoteFile, # remote_file_a.name = 'remote_file_a' given automatically +): + f = await task_context.recv_file(remote_file_a) + # f = await remote_file_a.recv(task_context) + + +# executor + +# some_task(socketio_task_context) diff --git a/task_definitions/TaskDefinitions.py b/task_definitions/TaskDefinitions.py index 6a6992d..66d7207 100644 --- a/task_definitions/TaskDefinitions.py +++ b/task_definitions/TaskDefinitions.py @@ -1,7 +1,13 @@ -from datetime import datetime -from time import sleep -from typing import Callable, Dict, List -from dataclasses import dataclass, field, asdict +import abc +import inspect +from typing import Callable, Dict, List, Awaitable, Any, cast +from dataclasses import dataclass, field +import pathlib + +from py_codegen.type_extractor.nodes.ClassFound import ClassFound +from py_codegen.type_extractor.nodes.FixedGenericFound import FixedGenericFound +from py_codegen.type_extractor.nodes.FunctionFound import FunctionFound +from py_codegen.type_extractor.type_extractor import TypeExtractor @dataclass @@ -11,40 +17,55 @@ class TaskCall: kwargs: Dict = field(default_factory=dict) -# might add options? -@dataclass -class Task: - func: Callable +class MissingTaskContextException(Exception): + def __init__(self, task_func): + super(MissingTaskContextException, self).__init__( + f"Missing `task_context: TaskContext` param: \n" + f"see function {task_func.__qualname__}\n" + f"{inspect.getsourcefile(task_func)}:{inspect.getsourcelines(task_func)[1]}\n" + ) @dataclass -class TaskDefinition: - tasks: Dict[str, Task] = field(default_factory=dict) +class TaskDefinitionsGroup: + tasks: Dict[str, Callable] = field(default_factory=dict) + + def __post_init__(self): + self.type_extractor = TypeExtractor() def add_task(self, task_func: Callable): - task = Task( - func=task_func, - ) - self.tasks.__setitem__(task_func.__qualname__, task) + self.validate_task_func(task_func) + self.tasks.__setitem__(task_func.__qualname__, task_func) def __wrapper_func__(*args, **kwargs): result = task_func(*args, **kwargs) return result return __wrapper_func__ + def validate_task_func(self, task_func: Callable): + self.type_extractor.add()(task_func) + task_func_type = cast(FunctionFound, self.type_extractor.collected_types.get(task_func.__qualname__)) + task_context_classnode = cast(ClassFound, self.type_extractor.collected_types.get('TaskContext')) + task_context_param = cast(FixedGenericFound, task_func_type.params.get('task_context')) + if getattr(task_context_param, 'origin', None) is not task_context_classnode \ + and task_context_param is not task_context_classnode: + # FIXME: check inside task_context_param.base_classes + print(1) + raise MissingTaskContextException(task_func) + def run_task(self, task_call: TaskCall): - task = self.tasks.get(task_call.func_name) - if not task: + func = self.tasks.get(task_call.func_name) + if not func: raise KeyError(f'task not found: {task_call.func_name}') - task.func(*task_call.args, **task_call.kwargs) - - -task_definition = TaskDefinition() - + return func(*task_call.args, **task_call.kwargs) -@task_definition.add_task -def long_running_task(time: int, val: int): - print(f"long_running_task( time={time}, val={val} started at: {datetime.now()}") - sleep(time) - print(f"long_running_task( time={time}, val={val} ended at: {datetime.now()}") - return val +# +# task_definition = TaskDefinitionsGroup() +# +# +# @task_definition.add_task +# def long_running_task(time: int, val: int): +# print(f"long_running_task( time={time}, val={val} started at: {datetime.now()}") +# sleep(time) +# print(f"long_running_task( time={time}, val={val} ended at: {datetime.now()}") +# return val diff --git a/task_definitions/protocol_aaa.py b/task_definitions/protocol_aaa.py new file mode 100644 index 0000000..9b75b65 --- /dev/null +++ b/task_definitions/protocol_aaa.py @@ -0,0 +1,28 @@ +from typing import TypeVar, Generic, Callable, Any, List + +from typing_extensions import Protocol, Type + +ReturnType = TypeVar('ReturnType') + + +class SomeCallable(Protocol): + def __call__(self, a: int) -> Any: ... + + +def add_some_class(some_class: SomeCallable): + print(some_class) + return some_class + + +def some_aa(a: int, b: str, c: float) -> float: + print(a) + return 3.3 + + +add_some_class(some_aa) + + +def add_some_class2(f: Callable[[..., int], float]): + pass + + diff --git a/task_definitions/protocol_mypy.py b/task_definitions/protocol_mypy.py new file mode 100644 index 0000000..4890ea7 --- /dev/null +++ b/task_definitions/protocol_mypy.py @@ -0,0 +1,20 @@ +from typing import Optional, Iterable, List +from typing_extensions import Protocol, Type + + +class Combiner(Protocol): + def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> List[bytes]: ... + +def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes: + for item in data: + ... + + +def good_cb(*vals: bytes, a: int, maxlen: Optional[int] = None) -> List[bytes]: + ... +def bad_cb(*vals: bytes, maxitems: Optional[int]) -> List[bytes]: + ... + +batch_proc([], good_cb) # OK +# batch_proc([], bad_cb) # Error! Argument 2 has incompatible type because of +# # different name and kind in the callback \ No newline at end of file diff --git a/task_definitions/to_send.md b/task_definitions/to_send.md new file mode 100644 index 0000000..7fa05f3 --- /dev/null +++ b/task_definitions/to_send.md @@ -0,0 +1,53 @@ +일단 대략적으로만 설명드릴께요~ +(** 자세한건 제가 직접 설명드릴께요ㅠ 아래 설명이 좀 많이 + +현재 작업중인 레포는 여기 뒀어요~ +https://github.com/pycodegen/api-tasks/tree/temp-wip-ts +(아직 ㄹㅇ 프로토타입 단계긴 해요ㅠ) + +하려는게 이렇게 되요~ + +``` +@task_definitions.add( options ) +async def sleep_blocks_task( + api_context: APIContext[ + NumberProgress, + Union[ + RemoteFile[Literal['upload_file_a']], + RemoteFile[Literal['upload_file_b']], + ], + count: int, + length: int, +): + ... + api_context.progress.send(1) # send progress + + remote_file_a = await api_context # receive upload file + .remote_files + .upload_file_a + .recv() + # do other things + +``` + + + + + +['task' 함수를 정의하고 decorator 달아주면 ] + -> Typescript api-client code 튀어나오게 (generate_ts_api 폴더) +server-side 에서 등록된 task 부르면 (client 입장에선 some_task_func( args ) + task 실행하게 ( <-- 현재는 socketio 로 주어진걸 threadpool로 실행하지만 오늘 추가된 요구사항엔 task-queue 써야 됩니다) +3. progress, file-upload 가능하게 +이건 task_func 형태에 'context' 를 줘서 할 생각입니다 + +```python +def some_task_func( +api_context: APIContext[ + NumberProgress, # progress type + RemoteFile, # multiple remote file 은 생각좀 해야됩니다... + ], + arg1: int, + arg2: float, ... +): +``` \ No newline at end of file diff --git a/ts_client/package.json b/ts_client/package.json deleted file mode 100644 index 9835dc2..0000000 --- a/ts_client/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "flask_socketio_task_runner_client", - "version": "0.0.1", - "main": "index.js", - "license": "MIT", - "private": true -} diff --git a/ts_client/src/BaseAPIClient.ts b/ts_client/src/BaseAPIClient.ts new file mode 100644 index 0000000..1d5f7cf --- /dev/null +++ b/ts_client/src/BaseAPIClient.ts @@ -0,0 +1,13 @@ +export type TaskArgs< + ArgsDict, + FuncName extends string = string, +> = { + funcName: string; + argsDict: ArgsDict +} + +export abstract class BaseAPIClient { + send(args: TaskArgs) { + + } +} \ No newline at end of file diff --git a/ts_client/src/Client.ts b/ts_client/src/Client.ts new file mode 100644 index 0000000..c52e1ae --- /dev/null +++ b/ts_client/src/Client.ts @@ -0,0 +1 @@ +import io from 'socket.io-client'; \ No newline at end of file diff --git a/ts_client/src/sample_generated_api_client.ts b/ts_client/src/sample_generated_api_client.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9a1f434 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "rootDir": "ts_client/src", + "outDir": "ts_client/lib", + "module": "ES2015" + }, + "exclude": [ + "ts_client/lib" + ] +} \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..5f582c6 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,280 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/socket.io-client@^1.4.32": + version "1.4.32" + resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14" + integrity sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg== + +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +arraybuffer.slice@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" + integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= + dependencies: + callsite "1.0.0" + +blob@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" + integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= + +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= + +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= + +debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +engine.io-client@~3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.1.tgz#922ddb47eecdcb541136a93aeead24718fd05461" + integrity sha512-RJNmA+A9Js+8Aoq815xpGAsgWH1VoSYM//2VgIiu9lNOaHFfLpTjH4tOzktBpjIs5lvOfiNY1dwf+NuU6D38Mw== + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "~4.1.0" + engine.io-parser "~2.2.0" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~6.1.0" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed" + integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w== + dependencies: + after "0.8.2" + arraybuffer.slice "~0.0.7" + base64-arraybuffer "0.1.5" + blob "0.0.5" + has-binary2 "~1.0.2" + +has-binary2@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" + integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== + dependencies: + isarray "2.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= + +io-ts@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.0.tgz#e01ee2031098f05ab2ef17d137b8bebc928644e2" + integrity sha512-BIZkC9IJ1ix8v3ttSWb5CWVzbEbMkGwjl0dMBGCXVT921/4f/9wAngsqA8VLXFe5JjLJ3INxBufz3BaQVf2zKw== + +isarray@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= + +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= + dependencies: + better-assert "~1.0.0" + +rxjs@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + dependencies: + tslib "^1.9.0" + +socket.io-client@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" + integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~4.1.0" + engine.io-client "~3.4.0" + has-binary2 "~1.0.2" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.3.0" + to-array "0.1.4" + +socket.io-parser@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" + integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng== + dependencies: + component-emitter "1.2.1" + debug "~3.1.0" + isarray "2.0.1" + +source-map-support@^0.5.6: + version "0.5.17" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.17.tgz#29fe1b3c98b9dbd5064ada89052ee8ff070cb46c" + integrity sha512-bwdKOBZ5L0gFRh4KOxNap/J/MpvX9Yxsq9lFDx65s3o7F/NiHy7JRaGIS8MwW6tZPAq9UXE207Il0cfcb5yu/Q== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= + +ts-node@^8.8.2: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f" + integrity sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.6" + yn "3.1.1" + +tslib@^1.9.0: + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +ws@~6.1.0: + version "6.1.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" + integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== + dependencies: + async-limiter "~1.0.0" + +xmlhttprequest-ssl@~1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" + integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" + integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==