Skip to content

Commit

Permalink
fix: isolate data with Flask instances
Browse files Browse the repository at this point in the history
All but session have been quarantined (`pre_fill` has been implemented in a special way) and `session` will be isolated in the future.
  • Loading branch information
Yazawazi committed Jul 15, 2024
1 parent 9363bf5 commit c6b5d00
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 50 deletions.
9 changes: 2 additions & 7 deletions backend/funix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@
# ---- Util ----
# ---- Exports ----

if GlobalSwitchOption.in_notebook:
import logging

logging.getLogger("werkzeug").setLevel(logging.ERROR)

__use_git = False
"""
Funix uses git to clone repo, now this feature is optional.
Expand Down Expand Up @@ -203,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 @@ -418,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 Down
1 change: 0 additions & 1 deletion backend/funix/app/websocket.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""
Websocket class to redirect
"""
import sys
from io import StringIO
from json import dumps

Expand Down
55 changes: 36 additions & 19 deletions backend/funix/decorator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
except:
pass


__pandas_use = False
"""
Whether Funix can handle pandas or pandera-related logic
Expand All @@ -101,9 +100,9 @@
except:
pass

__decorated_functions_names_list: list[str] = []
__decorated_functions_names_list: dict[str, list[str]] = {}
"""
A list, each element is the name of the decorated function.
A dict, each element is the names of the decorated function.
"""

__wrapper_enabled: bool = False
Expand All @@ -121,7 +120,7 @@
Default dir mode info.
"""

handled_object: list[int] = []
handled_object: dict[str, list[int]] = {app.name: []}
"""
Handled object ids.
"""
Expand Down Expand Up @@ -182,17 +181,18 @@ def enable_wrapper() -> None:
enable_file_service(app)


def object_is_handled(object_id: int) -> bool:
def object_is_handled(app_: Flask, object_id: int) -> bool:
"""
Check if the object is handled.
Parameters:
app_ (Flask): The app.
object_id (int): The object id.
Returns:
bool: True if handled, False otherwise.
"""
return object_id in handled_object
return app_.name in handled_object and object_id in handled_object[app_.name]


def funix(
Expand Down Expand Up @@ -291,6 +291,12 @@ def funix(
app_ = app
sock_ = sock

if app_.name not in __decorated_functions_names_list:
__decorated_functions_names_list[app_.name] = []

if app_.name not in handled_object:
handled_object[app_.name] = []

def decorator(function: Callable) -> callable:
"""
Decorator for functions to convert them to web apps
Expand All @@ -304,7 +310,7 @@ def decorator(function: Callable) -> callable:
Raises:
Check code for details
"""
handled_object.append(id(function))
handled_object[app_.name].append(id(function))
if disable:
return function
if __wrapper_enabled:
Expand Down Expand Up @@ -393,30 +399,32 @@ def decorator(function: Callable) -> callable:
endpoint = get_endpoint(path, unique_function_name, function_name)

if unique_function_name:
if unique_function_name in __decorated_functions_names_list:
if unique_function_name in __decorated_functions_names_list[app_.name]:
raise ValueError(
f"Function with name {function_name} already exists, you better check other files, they may "
f"have the same function name"
)
else:
if function_title in __decorated_functions_names_list:
if function_title in __decorated_functions_names_list[app_.name]:
raise ValueError(
f"Function with name {function_title} already exists"
)
if app_secret := get_app_secret():
set_function_secret(app_secret, function_id, function_title)
if app_secret := get_app_secret(app_.name):
set_function_secret(app_.name, app_secret, function_id, function_title)
elif secret:
if isinstance(secret, bool):
set_function_secret(token_hex(16), function_id, function_title)
set_function_secret(
app_.name, token_hex(16), function_id, function_title
)
else:
set_function_secret(secret, function_id, function_title)
set_function_secret(app_.name, secret, function_id, function_title)

secret_key = get_secret_by_id(function_id) is not None
secret_key = get_secret_by_id(app_.name, function_id) is not None

if unique_function_name:
__decorated_functions_names_list.append(unique_function_name)
__decorated_functions_names_list[app_.name].append(unique_function_name)
else:
__decorated_functions_names_list.append(function_title)
__decorated_functions_names_list[app_.name].append(function_title)

need_websocket = isgeneratorfunction(function)

Expand Down Expand Up @@ -618,7 +626,7 @@ def decorated_function_param_getter():
flask.Response: The function's parameters
"""
return get_param_for_funix(
pre_fill, decorated_function, session_description
app_.name, pre_fill, decorated_function, session_description
)

decorated_function_param_getter_name = f"{function_name}_param_getter"
Expand Down Expand Up @@ -650,7 +658,7 @@ def verify_secret():
Returns:
flask.Response: The verification result
"""
return check_secret(function_id)
return check_secret(app_.name, function_id)

decorated_function_verify_secret_name = f"{function_name}_verify_secret"

Expand All @@ -671,6 +679,7 @@ def verify_secret():
@wraps(function)
def wrapper(ws=None):
result = funix_call(
app_.name,
limiters,
need_websocket,
__pandas_use,
Expand Down Expand Up @@ -711,7 +720,11 @@ def funix_class(disable: bool = False):


def __funix_class(cls):
handled_object.append(id(cls))
if not GlobalSwitchOption.in_notebook:
if app.name in handled_object:
handled_object[app.name].append(id(cls))
else:
handled_object[app.name] = [id(cls)]
if inspect.isclass(cls):
if not hasattr(cls, "__init__"):
raise Exception("Class must have __init__ method!")
Expand All @@ -723,6 +736,10 @@ def __funix_class(cls):

f.visit(ast.parse(class_source_code))
if GlobalSwitchOption.in_notebook:
if f.class_app.name in handled_object:
handled_object[f.class_app.name].append(id(cls))
else:
handled_object[f.class_app.name] = [id(cls)]
jupyter(f.class_app)
return cls
else:
Expand Down
7 changes: 4 additions & 3 deletions backend/funix/decorator/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def kumo_callback():


def funix_call(
app_name: str,
limiters: list[Limiter] | None,
need_websocket: bool,
use_pandas: bool,
Expand Down Expand Up @@ -125,12 +126,12 @@ def original_result_to_pre_fill_metadata(
for index_or_key in pre_fill_metadata:
if index_or_key is PreFillEmpty:
set_global_variable(
function_call_address + "_result",
app_name + function_call_address + "_result",
function_call_result,
)
else:
set_global_variable(
function_call_address + f"_{index_or_key}",
app_name + function_call_address + f"_{index_or_key}",
function_call_result[index_or_key],
)

Expand Down Expand Up @@ -243,7 +244,7 @@ def output_to_web_function(**wrapped_function_kwargs):
if secret_key:
if "__funix_secret" in function_kwargs:
if (
not get_secret_by_id(function_id)
not get_secret_by_id(app_name, function_id)
== function_kwargs["__funix_secret"]
):
incorrect_secret_error = {
Expand Down
7 changes: 5 additions & 2 deletions backend/funix/decorator/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ def create_parse_type_metadata(function_id: str):


def get_param_for_funix(
app_name: str,
pre_fill: dict | None,
decorated_function: dict,
session_description: str,
Expand All @@ -317,11 +318,13 @@ def get_param_for_funix(
for argument_key, from_function_info in pre_fill.items():
if isinstance(from_function_info, tuple):
last_result = get_global_variable(
str(id(from_function_info[0])) + f"_{from_function_info[1]}"
app_name
+ str(id(from_function_info[0]))
+ f"_{from_function_info[1]}"
)
else:
last_result = get_global_variable(
str(id(from_function_info)) + "_result"
app_name + str(id(from_function_info)) + "_result"
)
if last_result is not None:
new_decorated_function["params"][argument_key]["default"] = last_result
Expand Down
56 changes: 39 additions & 17 deletions backend/funix/decorator/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,106 @@

from flask import Response, request

__decorated_id_to_function_dict: dict[str, str] = {}
__decorated_id_to_function_dict: dict[str, dict[str, str]] = {}
"""
A dict, key is function id, value is function name.
"""

__decorated_secret_functions_dict: dict[str, str] = {}
__decorated_secret_functions_dict: dict[str, dict[str, str]] = {}
"""
A dict, key is function id, value is secret.
For checking if the secret is correct.
"""

__app_secret: str | None = None
__app_secret: dict[str, str | None] = {}
"""
App secret, for all functions.
"""


def get_secret_by_id(function_id: str) -> str | None:
def get_secret_by_id(app_name: str, function_id: str) -> str | None:
"""
Get the secret of a function by id.
Parameters:
app_name (str): The app name.
function_id (str): The function id.
Returns:
str | None: The secret.
"""
global __decorated_secret_functions_dict
return __decorated_secret_functions_dict.get(function_id, None)
if app_name not in __decorated_secret_functions_dict:
return None
return __decorated_secret_functions_dict[app_name].get(function_id, None)


def set_function_secret(secret: str, function_id: str, function_name: str) -> None:
def set_function_secret(
app_name: str, secret: str, function_id: str, function_name: str
) -> None:
"""
Set the secret of a function.
Parameters:
app_name (str): The app name.
secret (str): The secret.
function_id (str): The function id.
function_name (str): The function name (or with path).
"""
global __decorated_secret_functions_dict, __decorated_id_to_function_dict
__decorated_secret_functions_dict[function_id] = secret
__decorated_id_to_function_dict[function_id] = function_name
if app_name not in __decorated_secret_functions_dict:
__decorated_secret_functions_dict[app_name] = {
function_id: secret,
}
__decorated_id_to_function_dict[app_name] = {
function_id: function_name,
}
else:
__decorated_secret_functions_dict[app_name][function_id] = secret
__decorated_id_to_function_dict[app_name][function_id] = function_name


def set_app_secret(secret: str) -> None:
def set_app_secret(app_name: str, secret: str) -> None:
"""
Set the app secret, it will be used for all functions.
Parameters:
app_name (str): The app name.
secret (str): The secret.
"""
global __app_secret
__app_secret = secret
__app_secret[app_name] = secret


def get_app_secret() -> str | None:
def get_app_secret(app_name: str) -> str | None:
"""
Get the app secret.
Parameters:
app_name (str): The app name.
Returns:
str | None: The app secret.
"""
global __app_secret
return __app_secret
return __app_secret.get(app_name, None)


def export_secrets():
def export_secrets(app_name: str):
"""
Export all secrets from the decorated functions.
Parameters:
app_name (str): The app name.
"""
__new_dict: dict[str, str] = {}
for function_id, secret in __decorated_secret_functions_dict.items():
__new_dict[__decorated_id_to_function_dict[function_id]] = secret
if app_name in __decorated_secret_functions_dict:
for function_id, secret in __decorated_secret_functions_dict[app_name].items():
__new_dict[__decorated_id_to_function_dict[app_name][function_id]] = secret
return __new_dict


def check_secret(function_id: str):
def check_secret(app_name: str, function_id: str):
data = request.get_json()

failed_data = Response(
Expand All @@ -99,7 +121,7 @@ def check_secret(function_id: str):
return failed_data

user_secret = request.get_json()["secret"]
if user_secret == __decorated_secret_functions_dict[function_id]:
if user_secret == __decorated_secret_functions_dict[app_name][function_id]:
return Response(
dumps(
{
Expand Down
Loading

0 comments on commit c6b5d00

Please sign in to comment.