Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preparing for version 0.5.9 #90

Merged
merged 23 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
56d1dcc
feat: support iframe with jupyter
Yazawazi May 21, 2024
7b1275f
update chatGPT examples
May 22, 2024
f92f06f
feat: do not use `Autocomplete` for `TextField`
Yazawazi May 22, 2024
bbcd2ee
feat: support `List[Literal[...]]` for dropdown menu
Yazawazi May 25, 2024
86424ac
feat: support docstring for widgets
Yazawazi Jun 6, 2024
717cfa7
fix: disable close on select and check value for docstring
Yazawazi Jun 6, 2024
35e4de0
fix: only legal widgets in the docstring will be processed
Yazawazi Jun 7, 2024
15e8690
feat: parse label in docstring
Yazawazi Jun 7, 2024
0d8b711
feat: direct printing is supported for tuples
Yazawazi Jun 7, 2024
483cc8d
feat: support ipywidgets Image, Video and Audio
Yazawazi Jun 7, 2024
faa1cb5
fix: close pyplot
Yazawazi Jun 7, 2024
8299b99
fix: class function do not use new app
Yazawazi Jun 7, 2024
00574df
feat: add `encoding` and replace `stderr` for ws
Yazawazi Jun 21, 2024
e56e175
fix: use inline code for stderr
Yazawazi Jun 21, 2024
9fc6c32
fix: multiple apps in `funix_class`
Yazawazi Jun 26, 2024
787c3d4
refactor: short function name
Yazawazi Jun 26, 2024
8c45de7
fix: keep dict order
Yazawazi Jun 30, 2024
7dc344c
feat: remove pandera
Yazawazi Jul 8, 2024
8c14995
feat: remove pandera in `bioinformatics`
Yazawazi Jul 8, 2024
815281e
fix: resize in options
Yazawazi Jul 8, 2024
276e229
fix: class menu and `push_counter`
Yazawazi Jul 8, 2024
f2e4d56
fix: isolate data with Flask instances
Yazawazi Jul 11, 2024
1920872
fix: handle `list`
Yazawazi Jul 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions backend/funix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@
import funix.decorator.theme as theme
import funix.hint as hint
from funix.app import app, enable_funix_host_checker
from funix.config.switch import GlobalSwitchOption
from funix.frontend import run_open_frontend, start
from funix.jupyter import jupyter
from funix.prep.global_to_session import get_new_python_file
from funix.util.file import (
create_safe_tempdir,
get_path_difference,
get_python_files_in_dir,
)
from funix.util.module import handle_module, import_module_from_file
from funix.util.network import get_compressed_ip_address_as_str, get_unused_port_from
from funix.util.network import (
get_compressed_ip_address_as_str,
get_unused_port_from,
is_port_used,
)

# ---- Exports ----
# ---- Decorators ----
Expand Down Expand Up @@ -68,6 +74,10 @@
pass


def enable_jupyter(value: bool):
GlobalSwitchOption.NOTEBOOK_AUTO_EXECUTION = value


def __prep(
module_or_file: Optional[str],
need_path: bool,
Expand Down Expand Up @@ -99,7 +109,7 @@ def __prep(
if module_or_file:
if is_module:
if default:
lists.set_default_function_name(default)
lists.set_default_function_name(app.name, default)
module = import_module(module_or_file)
handle_module(module, need_path, base_dir, path_difference)
else:
Expand Down Expand Up @@ -188,7 +198,7 @@ def import_from_config(
raise Exception("GitPython is not installed, please install it first!")

if app_secret and isinstance(app_secret, str):
set_app_secret(app_secret)
set_app_secret(app.name, app_secret)

if dir_mode:
base_dir = file_or_module_name
Expand Down Expand Up @@ -334,7 +344,7 @@ def get_flask_application(
)

if not no_frontend:
start()
start(app)
return app


Expand Down Expand Up @@ -392,7 +402,7 @@ def run(
default=default,
)

if lists.is_empty_function_list():
if lists.is_empty_function_list(app.name):
print(
"No functions nor classes decorated by Funix. Please check your code: "
"functions and classes that need to be handled by Funix should be public "
Expand All @@ -403,7 +413,7 @@ def run(
parsed_ip = ip_address(host)
parsed_port = get_unused_port_from(port, parsed_ip)

funix_secrets = secret.export_secrets()
funix_secrets = secret.export_secrets(app.name)
if funix_secrets:
local = get_compressed_ip_address_as_str(parsed_ip)
print("Secrets:")
Expand All @@ -416,7 +426,7 @@ def run(
print("-" * 15)

if not no_frontend:
start()
start(app)
print(f"Starting Funix at http://{host}:{parsed_port}")
else:
print(f"Starting Funix backend only at http://{host}:{parsed_port}")
Expand Down
55 changes: 42 additions & 13 deletions backend/funix/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import re
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4

from flask import Flask, Response, abort, request
from flask_sock import Sock
from sqlalchemy import create_engine, text
from sqlalchemy.pool import SingletonThreadPool

from funix.config.switch import GlobalSwitchOption
from funix.frontend import start
from funix.hint import LogLevel

app = Flask(__name__)
Expand All @@ -23,8 +25,47 @@
SESSION_COOKIE_SAMESITE="None",
SESSION_TYPE="filesystem",
)
app.json.sort_keys = False
sock = Sock(app)


def funix_auto_cors(response: Response) -> Response:
if "HTTP_ORIGIN" not in request.environ:
response.headers["Access-Control-Allow-Origin"] = "*"
else:
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Origin"] = request.environ["HTTP_ORIGIN"]
response.headers[
"Access-Control-Allow-Methods"
] = "GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, *"

return response


def get_new_app_and_sock_for_jupyter() -> tuple[Flask, Sock]:
"""
Get a new Flask app and a new Sock instance.
No telemetry, logging and visitor checks (fixed to 127.0.0.1)

Returns:
tuple[Flask, Sock]: The new Flask app and the new Sock instance.
"""
new_app = Flask(f"Junix_{uuid4().hex}")
new_app.secret_key = "Jupiter"
new_app.config.update(
SESSION_COOKIE_PATH="/",
SESSION_COOKIE_SAMESITE="None",
SESSION_TYPE="filesystem",
)
new_sock = Sock(new_app)

new_app.after_request(funix_auto_cors)
start(new_app)

return new_app, new_sock


funix_log_level = LogLevel.get_level()

privacy = """We honor your choices. For your data and freedom.
Expand Down Expand Up @@ -68,19 +109,7 @@ def privacy_policy(message: str) -> None:
privacy = message


@app.after_request
def funix_auto_cors(response: Response) -> Response:
if "HTTP_ORIGIN" not in request.environ:
response.headers["Access-Control-Allow-Origin"] = "*"
else:
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Origin"] = request.environ["HTTP_ORIGIN"]
response.headers[
"Access-Control-Allow-Methods"
] = "GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, *"

return response
app.after_request(funix_auto_cors)


def __api_call_data(response: Response, dict_to_json: bool = False) -> dict | None:
Expand Down
21 changes: 16 additions & 5 deletions backend/funix/app/websocket.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Websocket class to redirect
"""

from io import StringIO
from json import dumps

Expand All @@ -11,34 +10,46 @@ class StdoutToWebsocket:
Stdout to websocket.
"""

def __init__(self, ws):
encoding = "utf-8"

def __init__(self, ws, is_err=False):
"""
Initialize the StdoutToWebsocket.

Parameters:
ws (WebSocket): The websocket.
"""
self.ws = ws
self.is_err = is_err
self.value = StringIO()

def _get_html(self):
"""
Get the html data.
"""
value = self.value.getvalue()
if self.is_err and value.strip():
return f"`{value}`"
return value

def write(self, data):
"""
Write the data to the websocket.
"""
self.value.write(data)
self.ws.send(dumps([self.value.getvalue()]))
self.ws.send(dumps([self._get_html()]))

def writelines(self, data):
"""
Write the lines to the websocket.
"""
self.value.writelines(data)
self.ws.send(dumps([self.value.getvalue()]))
self.ws.send(dumps([self._get_html()]))

def flush(self):
"""
Flush the data to the websocket.
"""
self.value.flush()
self.ws.send(dumps([self.value.getvalue()]))
self.ws.send(dumps([self._get_html()]))
self.value = StringIO()
14 changes: 13 additions & 1 deletion backend/funix/config/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from secrets import token_hex
from sys import modules
from typing import Union


Expand All @@ -19,17 +20,28 @@ class SwitchOption:
AUTO_READ_DOCSTRING_TO_FUNCTION_DESCRIPTION: bool = True
"""Auto read docstring to function description"""

AUTO_READ_DOCSTRING_TO_PARSE: bool = True
"""Auto parse docstring to function widgets and description"""

USE_FIXED_SESSION_KEY: Union[bool, str] = False
"""Use fixed session key"""

FILE_LINK_EXPIRE_TIME: int = 60 * 60
"""The file link expire time (secounds), -1 for never expire"""
"""The file link expire time (seconds), -1 for never expire"""

BIGGER_DATA_SAVE_TO_TEMP: int = 1024 * 1024 * 10
"""The bigger data size to save to temp (bytes), -1 for always in memory"""

NOTEBOOK_AUTO_EXECUTION: bool = False
"""In notebook, auto run the flask app"""

__session_key = None

@property
def in_notebook(self) -> bool:
"""Whether in notebook."""
return "ipykernel" in modules and self.NOTEBOOK_AUTO_EXECUTION

def get_session_key(self) -> str:
"""Get the session key."""
if self.__session_key is not None:
Expand Down
Loading
Loading