flamepy 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flamepy/__init__.py +82 -0
- flamepy/client.py +433 -0
- flamepy/frontend_pb2.py +68 -0
- flamepy/frontend_pb2_grpc.py +709 -0
- flamepy/service.py +225 -0
- flamepy/shim_pb2.py +46 -0
- flamepy/shim_pb2_grpc.py +184 -0
- flamepy/test_flame.py +195 -0
- flamepy/types.py +159 -0
- flamepy/types_pb2.py +95 -0
- flamepy/types_pb2_grpc.py +24 -0
- flamepy-0.3.0.dist-info/METADATA +177 -0
- flamepy-0.3.0.dist-info/RECORD +16 -0
- flamepy-0.3.0.dist-info/WHEEL +5 -0
- flamepy-0.3.0.dist-info/licenses/LICENSE +201 -0
- flamepy-0.3.0.dist-info/top_level.txt +1 -0
flamepy/service.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2025 The Flame Authors.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
See the License for the specific language governing permissions and
|
|
11
|
+
limitations under the License.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import os
|
|
16
|
+
import grpc
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from typing import Optional, Dict, Any, Union
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
from .types import Shim, FlameError, FlameErrorCode
|
|
23
|
+
from .shim_pb2_grpc import GrpcShimServicer, add_GrpcShimServicer_to_server
|
|
24
|
+
from .types_pb2 import Result, EmptyRequest
|
|
25
|
+
from .shim_pb2 import TaskOutput as TaskOutputProto
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ApplicationContext:
|
|
31
|
+
"""Context for an application."""
|
|
32
|
+
name: str
|
|
33
|
+
shim: Shim
|
|
34
|
+
url: Optional[str] = None
|
|
35
|
+
command: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class SessionContext:
|
|
40
|
+
"""Context for a session."""
|
|
41
|
+
session_id: str
|
|
42
|
+
application: ApplicationContext
|
|
43
|
+
common_data: Optional[bytes] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class TaskContext:
|
|
48
|
+
"""Context for a task."""
|
|
49
|
+
task_id: str
|
|
50
|
+
session_id: str
|
|
51
|
+
input: Optional[bytes] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class TaskOutput:
|
|
56
|
+
"""Output from a task."""
|
|
57
|
+
data: Optional[bytes] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FlameService:
|
|
61
|
+
"""Base class for implementing Flame services."""
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
async def on_session_enter(self, context: SessionContext):
|
|
65
|
+
"""
|
|
66
|
+
Called when entering a session.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
context: Session context information
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if successful, False otherwise
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
async def on_task_invoke(self, context: TaskContext) -> TaskOutput:
|
|
78
|
+
"""
|
|
79
|
+
Called when a task is invoked.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
context: Task context information
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Task output
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
async def on_session_leave(self):
|
|
91
|
+
"""
|
|
92
|
+
Called when leaving a session.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if successful, False otherwise
|
|
96
|
+
"""
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class GrpcShimServicer(GrpcShimServicer):
|
|
101
|
+
"""gRPC servicer implementation for GrpcShim service."""
|
|
102
|
+
|
|
103
|
+
def __init__(self, service: FlameService):
|
|
104
|
+
self._service = service
|
|
105
|
+
|
|
106
|
+
async def OnSessionEnter(self, request, context):
|
|
107
|
+
"""Handle OnSessionEnter RPC call."""
|
|
108
|
+
try:
|
|
109
|
+
# Convert protobuf request to SessionContext
|
|
110
|
+
app_context = ApplicationContext(
|
|
111
|
+
name=request.application.name,
|
|
112
|
+
shim=Shim(request.application.shim),
|
|
113
|
+
url=request.application.url,
|
|
114
|
+
command=request.application.command
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
session_context = SessionContext(
|
|
118
|
+
session_id=request.session_id,
|
|
119
|
+
application=app_context,
|
|
120
|
+
common_data=request.common_data
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Call the service implementation
|
|
124
|
+
await self._service.on_session_enter(session_context)
|
|
125
|
+
|
|
126
|
+
# Return result
|
|
127
|
+
return Result(
|
|
128
|
+
return_code=0,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return Result(
|
|
133
|
+
return_code=1,
|
|
134
|
+
message=f"Session enter error: {str(e)}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def OnTaskInvoke(self, request, context):
|
|
138
|
+
"""Handle OnTaskInvoke RPC call."""
|
|
139
|
+
try:
|
|
140
|
+
# Convert protobuf request to TaskContext
|
|
141
|
+
task_context = TaskContext(
|
|
142
|
+
task_id=request.task_id,
|
|
143
|
+
session_id=request.session_id,
|
|
144
|
+
input=request.input
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Call the service implementation
|
|
148
|
+
output = await self._service.on_task_invoke(task_context)
|
|
149
|
+
|
|
150
|
+
# Return task output
|
|
151
|
+
return TaskOutputProto(data=output.data)
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
return TaskOutputProto(data=None)
|
|
155
|
+
|
|
156
|
+
async def OnSessionLeave(self, request, context):
|
|
157
|
+
"""Handle OnSessionLeave RPC call."""
|
|
158
|
+
try:
|
|
159
|
+
# Call the service implementation
|
|
160
|
+
await self._service.on_session_leave()
|
|
161
|
+
|
|
162
|
+
# Return result
|
|
163
|
+
return Result(
|
|
164
|
+
return_code=0,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return Result(
|
|
169
|
+
return_code=1,
|
|
170
|
+
message=f"Session leave error: {str(e)}"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
class GrpcShimServer:
|
|
174
|
+
"""Server for gRPC shim services."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, service: FlameService):
|
|
177
|
+
self._service = service
|
|
178
|
+
self._server = None
|
|
179
|
+
|
|
180
|
+
async def start(self):
|
|
181
|
+
"""Start the gRPC server."""
|
|
182
|
+
try:
|
|
183
|
+
# Create gRPC server
|
|
184
|
+
self._server = grpc.aio.server()
|
|
185
|
+
|
|
186
|
+
# Add servicer to server
|
|
187
|
+
shim_servicer = GrpcShimServicer(self._service)
|
|
188
|
+
add_GrpcShimServicer_to_server(shim_servicer, self._server)
|
|
189
|
+
|
|
190
|
+
# Listen on Unix socket
|
|
191
|
+
socket_path = f"/tmp/flame/shim/fsi.sock"
|
|
192
|
+
self._server.add_insecure_port(f"unix://{socket_path}")
|
|
193
|
+
|
|
194
|
+
# Start server
|
|
195
|
+
await self._server.start()
|
|
196
|
+
|
|
197
|
+
print(f"Flame Python service started on Unix socket: {socket_path}")
|
|
198
|
+
|
|
199
|
+
# Keep server running
|
|
200
|
+
await self._server.wait_for_termination()
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
raise FlameError(
|
|
204
|
+
FlameErrorCode.INTERNAL,
|
|
205
|
+
f"Failed to start gRPC server: {str(e)}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
async def stop(self):
|
|
209
|
+
"""Stop the gRPC server."""
|
|
210
|
+
if self._server:
|
|
211
|
+
await self._server.stop(grace=5)
|
|
212
|
+
print("gRPC shim server stopped")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def run(service: FlameService):
|
|
216
|
+
"""
|
|
217
|
+
Run a gRPC shim server.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
service: The shim service implementation
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
server = GrpcShimServer(service)
|
|
224
|
+
await server.start()
|
|
225
|
+
|
flamepy/shim_pb2.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: shim.proto
|
|
5
|
+
# Protobuf Python Version: 6.31.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
31,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'shim.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import flamepy.types_pb2 as types__pb2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nshim.proto\x12\x05\x66lame\x1a\x0btypes.proto\"}\n\x12\x41pplicationContext\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x04shim\x18\x02 \x01(\x0e\x32\x0b.flame.Shim\x12\x12\n\x05image\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x63ommand\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\x08\n\x06_imageB\n\n\x08_command\"~\n\x0eSessionContext\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12.\n\x0b\x61pplication\x18\x02 \x01(\x0b\x32\x19.flame.ApplicationContext\x12\x18\n\x0b\x63ommon_data\x18\x03 \x01(\x0cH\x00\x88\x01\x01\x42\x0e\n\x0c_common_data\"P\n\x0bTaskContext\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x12\n\x05input\x18\x04 \x01(\x0cH\x00\x88\x01\x01\x42\x08\n\x06_input\"(\n\nTaskOutput\x12\x11\n\x04\x64\x61ta\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x42\x07\n\x05_data2\xb5\x01\n\x08GrpcShim\x12\x38\n\x0eOnSessionEnter\x12\x15.flame.SessionContext\x1a\r.flame.Result\"\x00\x12\x37\n\x0cOnTaskInvoke\x12\x12.flame.TaskContext\x1a\x11.flame.TaskOutput\"\x00\x12\x36\n\x0eOnSessionLeave\x12\x13.flame.EmptyRequest\x1a\r.flame.Result\"\x00\x42&Z$github.com/flame-sh/flame/sdk/go/rpcb\x06proto3')
|
|
29
|
+
|
|
30
|
+
_globals = globals()
|
|
31
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
32
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'shim_pb2', _globals)
|
|
33
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
34
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
35
|
+
_globals['DESCRIPTOR']._serialized_options = b'Z$github.com/flame-sh/flame/sdk/go/rpc'
|
|
36
|
+
_globals['_APPLICATIONCONTEXT']._serialized_start=34
|
|
37
|
+
_globals['_APPLICATIONCONTEXT']._serialized_end=159
|
|
38
|
+
_globals['_SESSIONCONTEXT']._serialized_start=161
|
|
39
|
+
_globals['_SESSIONCONTEXT']._serialized_end=287
|
|
40
|
+
_globals['_TASKCONTEXT']._serialized_start=289
|
|
41
|
+
_globals['_TASKCONTEXT']._serialized_end=369
|
|
42
|
+
_globals['_TASKOUTPUT']._serialized_start=371
|
|
43
|
+
_globals['_TASKOUTPUT']._serialized_end=411
|
|
44
|
+
_globals['_GRPCSHIM']._serialized_start=414
|
|
45
|
+
_globals['_GRPCSHIM']._serialized_end=595
|
|
46
|
+
# @@protoc_insertion_point(module_scope)
|
flamepy/shim_pb2_grpc.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
import flamepy.shim_pb2 as shim__pb2
|
|
7
|
+
import flamepy.types_pb2 as types__pb2
|
|
8
|
+
|
|
9
|
+
GRPC_GENERATED_VERSION = '1.74.0'
|
|
10
|
+
GRPC_VERSION = grpc.__version__
|
|
11
|
+
_version_not_supported = False
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from grpc._utilities import first_version_is_lower
|
|
15
|
+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
16
|
+
except ImportError:
|
|
17
|
+
_version_not_supported = True
|
|
18
|
+
|
|
19
|
+
if _version_not_supported:
|
|
20
|
+
raise RuntimeError(
|
|
21
|
+
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
22
|
+
+ f' but the generated code in shim_pb2_grpc.py depends on'
|
|
23
|
+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
24
|
+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
25
|
+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GrpcShimStub(object):
|
|
30
|
+
"""Missing associated documentation comment in .proto file."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, channel):
|
|
33
|
+
"""Constructor.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
channel: A grpc.Channel.
|
|
37
|
+
"""
|
|
38
|
+
self.OnSessionEnter = channel.unary_unary(
|
|
39
|
+
'/flame.GrpcShim/OnSessionEnter',
|
|
40
|
+
request_serializer=shim__pb2.SessionContext.SerializeToString,
|
|
41
|
+
response_deserializer=types__pb2.Result.FromString,
|
|
42
|
+
_registered_method=True)
|
|
43
|
+
self.OnTaskInvoke = channel.unary_unary(
|
|
44
|
+
'/flame.GrpcShim/OnTaskInvoke',
|
|
45
|
+
request_serializer=shim__pb2.TaskContext.SerializeToString,
|
|
46
|
+
response_deserializer=shim__pb2.TaskOutput.FromString,
|
|
47
|
+
_registered_method=True)
|
|
48
|
+
self.OnSessionLeave = channel.unary_unary(
|
|
49
|
+
'/flame.GrpcShim/OnSessionLeave',
|
|
50
|
+
request_serializer=types__pb2.EmptyRequest.SerializeToString,
|
|
51
|
+
response_deserializer=types__pb2.Result.FromString,
|
|
52
|
+
_registered_method=True)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class GrpcShimServicer(object):
|
|
56
|
+
"""Missing associated documentation comment in .proto file."""
|
|
57
|
+
|
|
58
|
+
def OnSessionEnter(self, request, context):
|
|
59
|
+
"""Missing associated documentation comment in .proto file."""
|
|
60
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
61
|
+
context.set_details('Method not implemented!')
|
|
62
|
+
raise NotImplementedError('Method not implemented!')
|
|
63
|
+
|
|
64
|
+
def OnTaskInvoke(self, request, context):
|
|
65
|
+
"""Missing associated documentation comment in .proto file."""
|
|
66
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
67
|
+
context.set_details('Method not implemented!')
|
|
68
|
+
raise NotImplementedError('Method not implemented!')
|
|
69
|
+
|
|
70
|
+
def OnSessionLeave(self, request, context):
|
|
71
|
+
"""Missing associated documentation comment in .proto file."""
|
|
72
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
73
|
+
context.set_details('Method not implemented!')
|
|
74
|
+
raise NotImplementedError('Method not implemented!')
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def add_GrpcShimServicer_to_server(servicer, server):
|
|
78
|
+
rpc_method_handlers = {
|
|
79
|
+
'OnSessionEnter': grpc.unary_unary_rpc_method_handler(
|
|
80
|
+
servicer.OnSessionEnter,
|
|
81
|
+
request_deserializer=shim__pb2.SessionContext.FromString,
|
|
82
|
+
response_serializer=types__pb2.Result.SerializeToString,
|
|
83
|
+
),
|
|
84
|
+
'OnTaskInvoke': grpc.unary_unary_rpc_method_handler(
|
|
85
|
+
servicer.OnTaskInvoke,
|
|
86
|
+
request_deserializer=shim__pb2.TaskContext.FromString,
|
|
87
|
+
response_serializer=shim__pb2.TaskOutput.SerializeToString,
|
|
88
|
+
),
|
|
89
|
+
'OnSessionLeave': grpc.unary_unary_rpc_method_handler(
|
|
90
|
+
servicer.OnSessionLeave,
|
|
91
|
+
request_deserializer=types__pb2.EmptyRequest.FromString,
|
|
92
|
+
response_serializer=types__pb2.Result.SerializeToString,
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
96
|
+
'flame.GrpcShim', rpc_method_handlers)
|
|
97
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
98
|
+
server.add_registered_method_handlers('flame.GrpcShim', rpc_method_handlers)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# This class is part of an EXPERIMENTAL API.
|
|
102
|
+
class GrpcShim(object):
|
|
103
|
+
"""Missing associated documentation comment in .proto file."""
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def OnSessionEnter(request,
|
|
107
|
+
target,
|
|
108
|
+
options=(),
|
|
109
|
+
channel_credentials=None,
|
|
110
|
+
call_credentials=None,
|
|
111
|
+
insecure=False,
|
|
112
|
+
compression=None,
|
|
113
|
+
wait_for_ready=None,
|
|
114
|
+
timeout=None,
|
|
115
|
+
metadata=None):
|
|
116
|
+
return grpc.experimental.unary_unary(
|
|
117
|
+
request,
|
|
118
|
+
target,
|
|
119
|
+
'/flame.GrpcShim/OnSessionEnter',
|
|
120
|
+
shim__pb2.SessionContext.SerializeToString,
|
|
121
|
+
types__pb2.Result.FromString,
|
|
122
|
+
options,
|
|
123
|
+
channel_credentials,
|
|
124
|
+
insecure,
|
|
125
|
+
call_credentials,
|
|
126
|
+
compression,
|
|
127
|
+
wait_for_ready,
|
|
128
|
+
timeout,
|
|
129
|
+
metadata,
|
|
130
|
+
_registered_method=True)
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def OnTaskInvoke(request,
|
|
134
|
+
target,
|
|
135
|
+
options=(),
|
|
136
|
+
channel_credentials=None,
|
|
137
|
+
call_credentials=None,
|
|
138
|
+
insecure=False,
|
|
139
|
+
compression=None,
|
|
140
|
+
wait_for_ready=None,
|
|
141
|
+
timeout=None,
|
|
142
|
+
metadata=None):
|
|
143
|
+
return grpc.experimental.unary_unary(
|
|
144
|
+
request,
|
|
145
|
+
target,
|
|
146
|
+
'/flame.GrpcShim/OnTaskInvoke',
|
|
147
|
+
shim__pb2.TaskContext.SerializeToString,
|
|
148
|
+
shim__pb2.TaskOutput.FromString,
|
|
149
|
+
options,
|
|
150
|
+
channel_credentials,
|
|
151
|
+
insecure,
|
|
152
|
+
call_credentials,
|
|
153
|
+
compression,
|
|
154
|
+
wait_for_ready,
|
|
155
|
+
timeout,
|
|
156
|
+
metadata,
|
|
157
|
+
_registered_method=True)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def OnSessionLeave(request,
|
|
161
|
+
target,
|
|
162
|
+
options=(),
|
|
163
|
+
channel_credentials=None,
|
|
164
|
+
call_credentials=None,
|
|
165
|
+
insecure=False,
|
|
166
|
+
compression=None,
|
|
167
|
+
wait_for_ready=None,
|
|
168
|
+
timeout=None,
|
|
169
|
+
metadata=None):
|
|
170
|
+
return grpc.experimental.unary_unary(
|
|
171
|
+
request,
|
|
172
|
+
target,
|
|
173
|
+
'/flame.GrpcShim/OnSessionLeave',
|
|
174
|
+
types__pb2.EmptyRequest.SerializeToString,
|
|
175
|
+
types__pb2.Result.FromString,
|
|
176
|
+
options,
|
|
177
|
+
channel_credentials,
|
|
178
|
+
insecure,
|
|
179
|
+
call_credentials,
|
|
180
|
+
compression,
|
|
181
|
+
wait_for_ready,
|
|
182
|
+
timeout,
|
|
183
|
+
metadata,
|
|
184
|
+
_registered_method=True)
|
flamepy/test_flame.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tests for the Flame Python SDK.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import asyncio
|
|
8
|
+
from unittest.mock import Mock, AsyncMock
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
from flamepy import (
|
|
12
|
+
Connection, SessionAttributes, ApplicationAttributes,
|
|
13
|
+
Session, Task, Application, Shim, SessionState, TaskState,
|
|
14
|
+
ApplicationState, FlameError, FlameErrorCode, TaskInformer
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestTypes:
|
|
19
|
+
"""Test type definitions."""
|
|
20
|
+
|
|
21
|
+
def test_session_attributes(self):
|
|
22
|
+
"""Test SessionAttributes."""
|
|
23
|
+
attrs = SessionAttributes(
|
|
24
|
+
application="test-app",
|
|
25
|
+
slots=2,
|
|
26
|
+
common_data=b"test data"
|
|
27
|
+
)
|
|
28
|
+
assert attrs.application == "test-app"
|
|
29
|
+
assert attrs.slots == 2
|
|
30
|
+
assert attrs.common_data == b"test data"
|
|
31
|
+
|
|
32
|
+
def test_application_attributes(self):
|
|
33
|
+
"""Test ApplicationAttributes."""
|
|
34
|
+
attrs = ApplicationAttributes(
|
|
35
|
+
name="test-app",
|
|
36
|
+
shim=Shim.SHELL,
|
|
37
|
+
command="python",
|
|
38
|
+
arguments=["script.py"],
|
|
39
|
+
environments=["PATH=/usr/bin"],
|
|
40
|
+
working_directory="/tmp"
|
|
41
|
+
)
|
|
42
|
+
assert attrs.name == "test-app"
|
|
43
|
+
assert attrs.shim == Shim.SHELL
|
|
44
|
+
assert attrs.command == "python"
|
|
45
|
+
assert attrs.arguments == ["script.py"]
|
|
46
|
+
assert attrs.environments == ["PATH=/usr/bin"]
|
|
47
|
+
assert attrs.working_directory == "/tmp"
|
|
48
|
+
|
|
49
|
+
def test_session(self):
|
|
50
|
+
"""Test Session."""
|
|
51
|
+
session = Session(
|
|
52
|
+
id="session-1",
|
|
53
|
+
application="test-app",
|
|
54
|
+
slots=2,
|
|
55
|
+
state=SessionState.OPEN,
|
|
56
|
+
creation_time=datetime.now(),
|
|
57
|
+
pending=1,
|
|
58
|
+
running=1,
|
|
59
|
+
succeed=0,
|
|
60
|
+
failed=0
|
|
61
|
+
)
|
|
62
|
+
assert session.id == "session-1"
|
|
63
|
+
assert session.application == "test-app"
|
|
64
|
+
assert session.slots == 2
|
|
65
|
+
assert session.state == SessionState.OPEN
|
|
66
|
+
|
|
67
|
+
def test_task(self):
|
|
68
|
+
"""Test Task."""
|
|
69
|
+
task = Task(
|
|
70
|
+
id="task-1",
|
|
71
|
+
session_id="session-1",
|
|
72
|
+
state=TaskState.PENDING,
|
|
73
|
+
creation_time=datetime.now(),
|
|
74
|
+
input=b"test input"
|
|
75
|
+
)
|
|
76
|
+
assert task.id == "task-1"
|
|
77
|
+
assert task.session_id == "session-1"
|
|
78
|
+
assert task.state == TaskState.PENDING
|
|
79
|
+
assert task.input == b"test input"
|
|
80
|
+
assert not task.is_completed()
|
|
81
|
+
|
|
82
|
+
# Test completed task
|
|
83
|
+
completed_task = Task(
|
|
84
|
+
id="task-2",
|
|
85
|
+
session_id="session-1",
|
|
86
|
+
state=TaskState.SUCCEED,
|
|
87
|
+
creation_time=datetime.now()
|
|
88
|
+
)
|
|
89
|
+
assert completed_task.is_completed()
|
|
90
|
+
|
|
91
|
+
def test_application(self):
|
|
92
|
+
"""Test Application."""
|
|
93
|
+
app = Application(
|
|
94
|
+
id="app-1",
|
|
95
|
+
name="test-app",
|
|
96
|
+
shim=Shim.SHELL,
|
|
97
|
+
state=ApplicationState.ENABLED,
|
|
98
|
+
creation_time=datetime.now(),
|
|
99
|
+
command="python",
|
|
100
|
+
arguments=["script.py"]
|
|
101
|
+
)
|
|
102
|
+
assert app.id == "app-1"
|
|
103
|
+
assert app.name == "test-app"
|
|
104
|
+
assert app.shim == Shim.SHELL
|
|
105
|
+
assert app.state == ApplicationState.ENABLED
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestFlameError:
|
|
109
|
+
"""Test FlameError."""
|
|
110
|
+
|
|
111
|
+
def test_flame_error(self):
|
|
112
|
+
"""Test FlameError creation and string representation."""
|
|
113
|
+
error = FlameError(FlameErrorCode.INVALID_CONFIG, "Test error")
|
|
114
|
+
assert error.code == FlameErrorCode.INVALID_CONFIG
|
|
115
|
+
assert error.message == "Test error"
|
|
116
|
+
assert "Test error" in str(error)
|
|
117
|
+
assert "INVALID_CONFIG" in str(error)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestTaskInformer:
|
|
121
|
+
"""Test TaskInformer."""
|
|
122
|
+
|
|
123
|
+
def test_task_informer_default(self):
|
|
124
|
+
"""Test default TaskInformer implementation."""
|
|
125
|
+
informer = TaskInformer()
|
|
126
|
+
task = Task(
|
|
127
|
+
id="task-1",
|
|
128
|
+
session_id="session-1",
|
|
129
|
+
state=TaskState.PENDING,
|
|
130
|
+
creation_time=datetime.now()
|
|
131
|
+
)
|
|
132
|
+
error = FlameError(FlameErrorCode.INTERNAL, "Test error")
|
|
133
|
+
|
|
134
|
+
# Should not raise any exceptions
|
|
135
|
+
informer.on_update(task)
|
|
136
|
+
informer.on_error(error)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestConnection:
|
|
140
|
+
"""Test Connection class."""
|
|
141
|
+
|
|
142
|
+
@pytest.mark.asyncio
|
|
143
|
+
async def test_connection_connect_empty_addr(self):
|
|
144
|
+
"""Test connection with empty address."""
|
|
145
|
+
with pytest.raises(FlameError) as exc_info:
|
|
146
|
+
await Connection.connect("")
|
|
147
|
+
assert exc_info.value.code == FlameErrorCode.INVALID_CONFIG
|
|
148
|
+
assert "address cannot be empty" in exc_info.value.message
|
|
149
|
+
|
|
150
|
+
@pytest.mark.asyncio
|
|
151
|
+
async def test_connection_connect_invalid_addr(self):
|
|
152
|
+
"""Test connection with invalid address."""
|
|
153
|
+
with pytest.raises(FlameError) as exc_info:
|
|
154
|
+
await Connection.connect("invalid://address")
|
|
155
|
+
assert exc_info.value.code == FlameErrorCode.INVALID_CONFIG
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestEnums:
|
|
159
|
+
"""Test enum values."""
|
|
160
|
+
|
|
161
|
+
def test_session_state_values(self):
|
|
162
|
+
"""Test SessionState enum values."""
|
|
163
|
+
assert SessionState.OPEN == 0
|
|
164
|
+
assert SessionState.CLOSED == 1
|
|
165
|
+
|
|
166
|
+
def test_task_state_values(self):
|
|
167
|
+
"""Test TaskState enum values."""
|
|
168
|
+
assert TaskState.PENDING == 0
|
|
169
|
+
assert TaskState.RUNNING == 1
|
|
170
|
+
assert TaskState.SUCCEED == 2
|
|
171
|
+
assert TaskState.FAILED == 3
|
|
172
|
+
|
|
173
|
+
def test_application_state_values(self):
|
|
174
|
+
"""Test ApplicationState enum values."""
|
|
175
|
+
assert ApplicationState.ENABLED == 0
|
|
176
|
+
assert ApplicationState.DISABLED == 1
|
|
177
|
+
|
|
178
|
+
def test_shim_values(self):
|
|
179
|
+
"""Test Shim enum values."""
|
|
180
|
+
assert Shim.LOG == 0
|
|
181
|
+
assert Shim.STDIO == 1
|
|
182
|
+
assert Shim.WASM == 2
|
|
183
|
+
assert Shim.SHELL == 3
|
|
184
|
+
assert Shim.GRPC == 4
|
|
185
|
+
|
|
186
|
+
def test_flame_error_code_values(self):
|
|
187
|
+
"""Test FlameErrorCode enum values."""
|
|
188
|
+
assert FlameErrorCode.INVALID_CONFIG == 0
|
|
189
|
+
assert FlameErrorCode.INVALID_STATE == 1
|
|
190
|
+
assert FlameErrorCode.INVALID_ARGUMENT == 2
|
|
191
|
+
assert FlameErrorCode.INTERNAL == 3
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
pytest.main([__file__])
|