Skip to content

Commit

Permalink
[AIC-py][editor] server: persistent local cfg + telemetry flag, endpo…
Browse files Browse the repository at this point in the history
…ints

Test:

- Start the server with and without flag and with no file, see that it gets created with correct contents
- start the server with and without flag with existing file, with both true and false
  - hit both endpoints, check responses and that the file contents are changed


https://github.com/lastmile-ai/aiconfig/assets/148090348/b36c3c00-c14b-4cde-8449-60386f2be1eb
  • Loading branch information
jonathanlastmileai committed Jan 11, 2024
1 parent 9f5cd3d commit e622b59
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 7 deletions.
50 changes: 48 additions & 2 deletions python/src/aiconfig/editor/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import lastmile_utils.lib.core.api as core_utils
import result
import yaml
from aiconfig.Config import AIConfigRuntime
from aiconfig.editor.server.queue_iterator import STOP_STREAMING_SIGNAL, QueueIterator
from aiconfig.editor.server.server_utils import (
Expand Down Expand Up @@ -57,7 +58,7 @@
CORS(app, resources={r"/api/*": {"origins": "*"}})


def run_backend_server(edit_config: EditServerConfig) -> Result[str, str]:
def run_backend_server(edit_config: EditServerConfig, editor_config_path: str) -> Result[str, str]:
LOGGER.setLevel(edit_config.log_level)
LOGGER.info("Edit config: %s", edit_config.model_dump_json())
LOGGER.info(f"Starting server on http://localhost:{edit_config.server_port}")
Expand All @@ -68,7 +69,7 @@ def run_backend_server(edit_config: EditServerConfig) -> Result[str, str]:
LOGGER.warning(f"Failed to open browser: {e}. Please open http://localhost:{port} manually.")

app.server_state = ServerState() # type: ignore
res_server_state_init = init_server_state(app, edit_config)
res_server_state_init = init_server_state(app, edit_config, editor_config_path)
match res_server_state_init:
case Ok(_):
LOGGER.info("Initialized server state")
Expand Down Expand Up @@ -566,3 +567,48 @@ def _op(aiconfig_runtime: AIConfigRuntime, _op_args: OpArgs) -> Result[None, str

signature: dict[str, Type[Any]] = {}
return run_aiconfig_operation_with_request_json(aiconfig, request_json, f"method_", _op, signature)


@app.route("/api/get_settings", methods=["GET"])
def get_settings() -> FlaskResponse:
state = get_server_state(app)

def _yaml_deserialize(yaml_str: str) -> Result[dict[str, Any], str]:
try:
out = yaml.safe_load(yaml_str)
if out is None:
return Err(f"Cannot parse string from YAML file '{state.settings_path}' into YAML format: dict[str, Any]")
else:
return Ok(out)
except Exception as e:
return core_utils.ErrWithTraceback(e)

settings_object = core_utils.read_text_file(state.settings_path).and_then(_yaml_deserialize)
match settings_object:
case Ok(settings_object_):
return FlaskResponse(({"message": "got settings", "data": settings_object_}, 200))
case Err(e):
return FlaskResponse(({"message": f"failed to get settings: {e}", "data": None}, 400))


@app.route("/api/set_settings", methods=["POST"])
def set_settings() -> FlaskResponse:
state = get_server_state(app)
request_json = request.get_json()
try:
contents = request_json.get("contents")
yaml_contents = yaml.dump(contents)
with open(state.settings_path, "w") as f:
f.write(yaml_contents)

return HttpResponseWithAIConfig(
message=f"Successfully set settings to {yaml_contents}",
code=200,
aiconfig=None,
).to_flask_format()
except Exception as e:
return HttpResponseWithAIConfig(
message=f"Failed to set settings: {e}",
code=400,
aiconfig=None,
).to_flask_format()
4 changes: 3 additions & 1 deletion python/src/aiconfig/editor/server/server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def convert_to_mode(cls, value: Any) -> ServerMode: # pylint: disable=no-self-a

@dataclass
class ServerState:
settings_path: str = "aiconfig_cli.config.yaml"
aiconfig: AIConfigRuntime | None = None
events: dict[str, Event] = field(default_factory=dict)

Expand Down Expand Up @@ -200,10 +201,11 @@ def safe_load_from_disk(aiconfig_path: ValidatedPath) -> Result[AIConfigRuntime,
return core_utils.ErrWithTraceback(e)


def init_server_state(app: Flask, edit_config: EditServerConfig) -> Result[None, str]:
def init_server_state(app: Flask, edit_config: EditServerConfig, settings_path: str) -> Result[None, str]:
LOGGER.info("Initializing server state")
_load_user_parser_module_if_exists(edit_config.parsers_module_path)
state = get_server_state(app)
state.settings_path = settings_path

assert state.aiconfig is None
if os.path.exists(edit_config.aiconfig_path):
Expand Down
30 changes: 26 additions & 4 deletions python/src/aiconfig/scripts/aiconfig_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

import lastmile_utils.lib.core.api as core_utils
import result
import yaml
from aiconfig.editor.server.server import run_backend_server
from aiconfig.editor.server.server_utils import EditServerConfig, ServerMode
from result import Err, Ok, Result


class AIConfigCLIConfig(core_utils.Record):
log_level: str | int = "WARNING"
settings_path: str = "aiconfig_cli.config.yaml"
allow_usage_data_sharing: bool = False


logging.basicConfig(format=core_utils.LOGGER_FMT)
Expand Down Expand Up @@ -46,12 +49,13 @@ def run_subcommand(argv: list[str]) -> Result[str, str]:
LOGGER.debug("Running edit subcommand")
res_edit_config = core_utils.parse_args(main_parser, argv[1:], EditServerConfig)
LOGGER.debug(f"{res_edit_config.is_ok()=}")
res_servers = res_edit_config.and_then(_run_editor_servers)
out: Result[str, str] = result.do(
#
Ok(",".join(res_servers_ok))
#
for res_servers_ok in res_servers
for edit_config in res_edit_config
for cli_config in res_cli_config
for res_servers_ok in _run_editor_servers(edit_config, cli_config.settings_path)
)
return out
else:
Expand All @@ -76,7 +80,7 @@ def is_port_in_use(port: int) -> bool:
return s.connect_ex(("localhost", port)) == 0


def _run_editor_servers(edit_config: EditServerConfig) -> Result[list[str], str]:
def _run_editor_servers(edit_config: EditServerConfig, settings_path: str) -> Result[list[str], str]:
port = edit_config.server_port

while is_port_in_use(port):
Expand All @@ -100,7 +104,7 @@ def _run_editor_servers(edit_config: EditServerConfig) -> Result[list[str], str]
return Err(e)

results: list[Result[str, str]] = []
backend_res = run_backend_server(edit_config)
backend_res = run_backend_server(edit_config, settings_path)
match backend_res:
case Ok(_):
pass
Expand All @@ -116,6 +120,24 @@ def _run_editor_servers(edit_config: EditServerConfig) -> Result[list[str], str]

def _process_cli_config(cli_config: AIConfigCLIConfig) -> Result[bool, str]:
LOGGER.setLevel(cli_config.log_level)

config_path = cli_config.settings_path
file_contents = yaml.dump({"allow_usage_data_sharing": cli_config.allow_usage_data_sharing})
try:
with open(config_path, "x") as f:
f.write(file_contents)
except FileExistsError:
with open(config_path, "r") as f:
existing_file_contents = f.read()

existing_config = yaml.safe_load(existing_file_contents)
existing_config["allow_usage_data_sharing"] = cli_config.allow_usage_data_sharing
file_contents = yaml.dump(existing_config)
with open(config_path, "w") as f:
f.write(file_contents)
except Exception as e:
return core_utils.ErrWithTraceback(e)

return Ok(True)


Expand Down

0 comments on commit e622b59

Please sign in to comment.