diff --git a/pychunkedgraph/app/__init__.py b/pychunkedgraph/app/__init__.py index 6bc30b0ce..33f5d6321 100644 --- a/pychunkedgraph/app/__init__.py +++ b/pychunkedgraph/app/__init__.py @@ -20,6 +20,7 @@ from .segmentation.legacy.routes import bp as segmentation_api_legacy from .segmentation.v1.routes import bp as segmentation_api_v1 from .segmentation.generic.routes import bp as generic_api +from .app_utils import get_instance_folder_path class CustomJsonEncoder(json.JSONEncoder): @@ -42,10 +43,14 @@ def default(self, obj): def create_app(test_config=None): - app = Flask(__name__) + app = Flask( + __name__, + instance_path=get_instance_folder_path(), + instance_relative_config=True, + ) app.json_encoder = CustomJsonEncoder - CORS(app, expose_headers='WWW-Authenticate') + CORS(app, expose_headers="WWW-Authenticate") configure_app(app) @@ -65,33 +70,32 @@ def create_app(test_config=None): def configure_app(app): # Load logging scheme from config.py - app_settings = os.getenv('APP_SETTINGS') + app_settings = os.getenv("APP_SETTINGS") if not app_settings: app.config.from_object(config.BaseConfig) else: app.config.from_object(app_settings) - - + app.config.from_pyfile("config.cfg", silent=True) # Configure logging # handler = logging.FileHandler(app.config['LOGGING_LOCATION']) handler = logging.StreamHandler(sys.stdout) - handler.setLevel(app.config['LOGGING_LEVEL']) + handler.setLevel(app.config["LOGGING_LEVEL"]) formatter = jsonformatter.JsonFormatter( - fmt=app.config['LOGGING_FORMAT'], - datefmt=app.config['LOGGING_DATEFORMAT']) + fmt=app.config["LOGGING_FORMAT"], datefmt=app.config["LOGGING_DATEFORMAT"] + ) formatter.converter = time.gmtime handler.setFormatter(formatter) app.logger.removeHandler(default_handler) app.logger.addHandler(handler) - app.logger.setLevel(app.config['LOGGING_LEVEL']) + app.logger.setLevel(app.config["LOGGING_LEVEL"]) app.logger.propagate = False - if app.config['USE_REDIS_JOBS']: - app.redis = redis.Redis.from_url(app.config['REDIS_URL']) - app.test_q = Queue('test', connection=app.redis) + if app.config["USE_REDIS_JOBS"]: + app.redis = redis.Redis.from_url(app.config["REDIS_URL"]) + app.test_q = Queue("test", connection=app.redis) with app.app_context(): from ..ingest.rq_cli import init_rq_cmds from ..ingest.cli import init_ingest_cmds init_rq_cmds(app) - init_ingest_cmds(app) \ No newline at end of file + init_ingest_cmds(app) diff --git a/pychunkedgraph/app/app_utils.py b/pychunkedgraph/app/app_utils.py index ea121db77..06fe81f16 100644 --- a/pychunkedgraph/app/app_utils.py +++ b/pychunkedgraph/app/app_utils.py @@ -3,22 +3,128 @@ from time import gmtime import numpy as np -from flask import current_app, json +from flask import current_app, json, request from google.auth import credentials from google.auth import default as default_creds from google.cloud import bigtable, datastore from pychunkedgraph.graph import ChunkedGraph from pychunkedgraph.logging import flask_log_db, jsonformatter +from pychunkedgraph.graph import ( + exceptions as cg_exceptions, +) +from functools import wraps +from werkzeug.datastructures import ImmutableMultiDict +import time +import os CACHE = {} +def get_app_base_path(): + return os.path.dirname(os.path.realpath(__file__)) + + +def get_instance_folder_path(): + return os.path.join(get_app_base_path(), "instance") + + class DoNothingCreds(credentials.Credentials): def refresh(self, request): pass +def remap_public(func=None, *, edit=False, check_node_ids=False): + def mydecorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + virtual_tables = current_app.config.get("VIRTUAL_TABLES", None) + + # if not virtual configuration just return + if virtual_tables is None: + return f(*args, **kwargs) + table_id = kwargs.get("table_id", None) + http_args = request.args.to_dict() + + if table_id is None: + # then no table remapping necessary + return f(*args, **kwargs) + if not table_id in virtual_tables: + # if table table_id isn't in virtual + # tables then just return + return f(*args, **kwargs) + else: + # then we have a virtual table + if edit: + raise cg_exceptions.Unauthorized( + "No edits allowed on virtual tables" + ) + # and we want to remap the table name + new_table = virtual_tables[table_id]["table_id"] + kwargs["table_id"] = new_table + v_timestamp = virtual_tables[table_id]["timestamp"] + v_timetamp_float = time.mktime(v_timestamp.timetuple()) + + # we want to fix timestamp parameters too + def ceiling_timestamp(argname): + old_arg = http_args.get(argname, None) + if old_arg is not None: + # if they specified a timestamp + # enforce its less than the cap + if old_arg > v_timetamp_float: + http_args[argname] = v_timetamp_float + else: + # if they omit the timestamp, it defaults to "now" + # so we should cap it at the virtual timestamp + http_args[argname] = v_timetamp_float + + ceiling_timestamp("timestamp") + ceiling_timestamp("timestamp_future") + + request.args = ImmutableMultiDict(http_args) + + # we also want to check for endpoints + # which ask for info about IDs and + # restrict such calls to IDs that are valid + # before the timestamp cap for this virtual table + cg = get_cg(new_table) + + def assert_node_prop(prop): + node_id = kwargs.get(prop, None) + if node_id is not None: + node_id = int(node_id) + # check if this root_id is valid at this timestamp + timestamp = cg.get_node_timestamps([node_id]) + if not np.all(timestamp < np.datetime64(v_timestamp)): + raise cg_exceptions.Unauthorized( + "root_id not valid at timestamp" + ) + + assert_node_prop("root_id") + assert_node_prop("node_id") + + # some endpoints post node_ids as json, so we have to check there + # as well if the endpoint configured us to. + if check_node_ids: + node_ids = np.array( + json.loads(request.data)["node_ids"], dtype=np.uint64 + ) + timestamps = cg.get_node_timestamps(node_ids) + if not np.all(timestamps < np.datetime64(v_timestamp)): + raise cg_exceptions.Unauthorized( + "node_ids are all not valid at timestamp" + ) + + return f(*args, **kwargs) + + return decorated_function + + if func: + return mydecorator(func) + else: + return mydecorator + + def jsonify_with_kwargs(data, as_response=True, **kwargs): kwargs.setdefault("separators", (",", ":")) @@ -113,10 +219,10 @@ def get_log_db(table_id): def toboolean(value): - """ Transform value to boolean type. - :param value: bool/int/str - :return: bool - :raises: ValueError, if value is not boolean. + """Transform value to boolean type. + :param value: bool/int/str + :return: bool + :raises: ValueError, if value is not boolean. """ if not value: raise ValueError("Can't convert null to boolean") @@ -137,7 +243,7 @@ def toboolean(value): def tobinary(ids): - """ Transform id(s) to binary format + """Transform id(s) to binary format :param ids: uint64 or list of uint64s :return: binary @@ -146,7 +252,7 @@ def tobinary(ids): def tobinary_multiples(arr): - """ Transform id(s) to binary format + """Transform id(s) to binary format :param arr: list of uint64 or list of uint64s :return: binary diff --git a/pychunkedgraph/app/config.py b/pychunkedgraph/app/config.py index 1724f6f1e..79415ff60 100644 --- a/pychunkedgraph/app/config.py +++ b/pychunkedgraph/app/config.py @@ -1,6 +1,8 @@ import logging import os import json +import datetime +from pychunkedgraph.meshing.meshgen import UTC class BaseConfig(object): @@ -8,16 +10,16 @@ class BaseConfig(object): TESTING = False HOME = os.path.expanduser("~") # TODO get this secret out of source control - SECRET_KEY = '1d94e52c-1c89-4515-b87a-f48cf3cb7f0b' + SECRET_KEY = "1d94e52c-1c89-4515-b87a-f48cf3cb7f0b" LOGGING_FORMAT = '{"source":"%(name)s","time":"%(asctime)s","severity":"%(levelname)s","message":"%(message)s"}' - LOGGING_DATEFORMAT = '%Y-%m-%dT%H:%M:%S.0Z' + LOGGING_DATEFORMAT = "%Y-%m-%dT%H:%M:%S.0Z" LOGGING_LEVEL = logging.DEBUG CHUNKGRAPH_INSTANCE_ID = "pychunkedgraph" - PROJECT_ID = os.environ.get('PROJECT_ID', None) - CG_READ_ONLY = os.environ.get('CG_READ_ONLY', None) is not None - PCG_GRAPH_IDS = os.environ.get('PCG_GRAPH_IDS').split(",") + PROJECT_ID = os.environ.get("PROJECT_ID", None) + CG_READ_ONLY = os.environ.get("CG_READ_ONLY", None) is not None + PCG_GRAPH_IDS = os.environ.get("PCG_GRAPH_IDS").split(",") # TODO what is this suppose to be by default? CHUNKGRAPH_TABLE_ID = "pinky100_sv16" @@ -25,18 +27,36 @@ class BaseConfig(object): USE_REDIS_JOBS = False - MESHING_ENDPOINT = os.environ.get("MESHING_ENDPOINT", "http://meshing-service/meshing") + MESHING_ENDPOINT = os.environ.get( + "MESHING_ENDPOINT", "http://meshing-service/meshing" + ) daf_credential_path = os.environ.get("DAF_CREDENTIALS", None) - + if daf_credential_path is not None: with open(daf_credential_path, "r") as f: AUTH_TOKEN = json.load(f)["token"] else: - AUTH_TOKEN = "" - - + AUTH_TOKEN = "" + VIRTUAL_TABLES = { + "minnie65_public_v117": { + "table_id": "minnie3_v1", + "timestamp": datetime.datetime( + year=2021, + month=6, + day=11, + hour=8, + minute=10, + second=0, + microsecond=253, + tzinfo=datetime.timezone.utc, + ), + } + } + + class DevelopmentConfig(BaseConfig): """Development configuration.""" + USE_REDIS_JOBS = False DEBUG = True LOGGING_LEVEL = logging.ERROR @@ -44,24 +64,27 @@ class DevelopmentConfig(BaseConfig): class DockerDevelopmentConfig(DevelopmentConfig): """Development configuration.""" + USE_REDIS_JOBS = True - REDIS_HOST = os.environ.get('REDIS_SERVICE_HOST', 'localhost') - REDIS_PORT = os.environ.get('REDIS_SERVICE_PORT', '6379') - REDIS_PASSWORD = os.environ.get('REDIS_PASSWORD', 'dev') - REDIS_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0' + REDIS_HOST = os.environ.get("REDIS_SERVICE_HOST", "localhost") + REDIS_PORT = os.environ.get("REDIS_SERVICE_PORT", "6379") + REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "dev") + REDIS_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0" class DeploymentWithRedisConfig(BaseConfig): """Deployment configuration with Redis.""" + USE_REDIS_JOBS = True - REDIS_HOST = os.environ.get('REDIS_SERVICE_HOST') - REDIS_PORT = os.environ.get('REDIS_SERVICE_PORT') - REDIS_PASSWORD = os.environ.get('REDIS_PASSWORD') - REDIS_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0' + REDIS_HOST = os.environ.get("REDIS_SERVICE_HOST") + REDIS_PORT = os.environ.get("REDIS_SERVICE_PORT") + REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") + REDIS_URL = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/0" class TestingConfig(BaseConfig): """Testing configuration.""" + TESTING = True USE_REDIS_JOBS = False PRESERVE_CONTEXT_ON_EXCEPTION = False diff --git a/pychunkedgraph/app/meshing/legacy/routes.py b/pychunkedgraph/app/meshing/legacy/routes.py index eccd89efe..f2aa7ad0b 100644 --- a/pychunkedgraph/app/meshing/legacy/routes.py +++ b/pychunkedgraph/app/meshing/legacy/routes.py @@ -3,8 +3,11 @@ from pychunkedgraph.app.meshing import common from pychunkedgraph.graph import exceptions as cg_exceptions +from pychunkedgraph.app.app_utils import remap_public -bp = Blueprint("pcg_meshing_v0", __name__, url_prefix=f"/{common.__meshing_url_prefix__}/1.0") +bp = Blueprint( + "pcg_meshing_v0", __name__, url_prefix=f"/{common.__meshing_url_prefix__}/1.0" +) # ------------------------------- # ------ Access control and index @@ -54,6 +57,7 @@ def api_exception(e): @bp.route("///validfragments", methods=["POST", "GET"]) +@remap_public @auth_requires_permission("view") def handle_valid_frags(table_id, node_id): return common.handle_valid_frags(table_id, node_id) @@ -64,5 +68,6 @@ def handle_valid_frags(table_id, node_id): @bp.route("//manifest/:0", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_get_manifest(table_id, node_id): return common.handle_get_manifest(table_id, node_id) diff --git a/pychunkedgraph/app/meshing/v1/routes.py b/pychunkedgraph/app/meshing/v1/routes.py index 0537e0669..3a8dac3c8 100644 --- a/pychunkedgraph/app/meshing/v1/routes.py +++ b/pychunkedgraph/app/meshing/v1/routes.py @@ -3,8 +3,11 @@ from pychunkedgraph.app.meshing import common from pychunkedgraph.graph import exceptions as cg_exceptions +from pychunkedgraph.app.app_utils import remap_public -bp = Blueprint("pcg_meshing_v1", __name__, url_prefix=f"/{common.__meshing_url_prefix__}/api/v1") +bp = Blueprint( + "pcg_meshing_v1", __name__, url_prefix=f"/{common.__meshing_url_prefix__}/api/v1" +) # ------------------------------- # ------ Access control and index @@ -56,6 +59,7 @@ def api_exception(e): @bp.route("/table//node//validfragments", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_valid_frags(table_id, node_id): return common.handle_valid_frags(table_id, node_id) @@ -65,12 +69,16 @@ def handle_valid_frags(table_id, node_id): @bp.route("/table//manifest/:0", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_get_manifest(table_id, node_id): return common.handle_get_manifest(table_id, node_id) + ## ENQUE MESHING JOBS ---------------------------------------------------------- + @bp.route("/table//remeshing", methods=["POST"]) @auth_requires_permission("edit") +@remap_public(edit=True) def handle_remesh(table_id): - return common.handle_remesh(table_id) \ No newline at end of file + return common.handle_remesh(table_id) diff --git a/pychunkedgraph/app/segmentation/common.py b/pychunkedgraph/app/segmentation/common.py index fca61fd72..e9e94d233 100644 --- a/pychunkedgraph/app/segmentation/common.py +++ b/pychunkedgraph/app/segmentation/common.py @@ -32,6 +32,8 @@ from pychunkedgraph.graph.misc import get_contact_sites from middle_auth_client import get_usernames + + __api_versions__ = [0, 1] __segmentation_url_prefix__ = os.environ.get("SEGMENTATION_URL_PREFIX", "segmentation") diff --git a/pychunkedgraph/app/segmentation/generic/routes.py b/pychunkedgraph/app/segmentation/generic/routes.py index c467a648b..b17f4948f 100644 --- a/pychunkedgraph/app/segmentation/generic/routes.py +++ b/pychunkedgraph/app/segmentation/generic/routes.py @@ -1,9 +1,15 @@ from flask import Blueprint -from middle_auth_client import auth_requires_admin, auth_required, \ - auth_requires_permission +from middle_auth_client import ( + auth_requires_admin, + auth_required, + auth_requires_permission, +) from pychunkedgraph.app.segmentation import common +from pychunkedgraph.app.app_utils import remap_public -bp = Blueprint("pcg_generic_v1", __name__, url_prefix=f"/{common.__segmentation_url_prefix__}") +bp = Blueprint( + "pcg_generic_v1", __name__, url_prefix=f"/{common.__segmentation_url_prefix__}" +) # ------------------------------- @@ -37,6 +43,7 @@ def sleep_me(sleep): @bp.route("/table//info", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_info(table_id): return common.handle_info(table_id) diff --git a/pychunkedgraph/app/segmentation/legacy/routes.py b/pychunkedgraph/app/segmentation/legacy/routes.py index aa1ce7557..da1fa2f44 100644 --- a/pychunkedgraph/app/segmentation/legacy/routes.py +++ b/pychunkedgraph/app/segmentation/legacy/routes.py @@ -3,13 +3,21 @@ import numpy as np from flask import Blueprint, jsonify, request -from middle_auth_client import auth_requires_admin, auth_requires_permission, \ - auth_required +from middle_auth_client import ( + auth_requires_admin, + auth_requires_permission, + auth_required, +) from pychunkedgraph.app import app_utils from pychunkedgraph.app.segmentation import common from pychunkedgraph.graph import exceptions as cg_exceptions +from pychunkedgraph.app.app_utils import remap_public -bp = Blueprint("pcg_segmentation_v0", __name__, url_prefix=f"/{common.__segmentation_url_prefix__}/1.0") +bp = Blueprint( + "pcg_segmentation_v0", + __name__, + url_prefix=f"/{common.__segmentation_url_prefix__}/1.0", +) # ------------------------------- @@ -70,7 +78,9 @@ def sleep_me(sleep): @bp.route("//info", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_info(table_id): + print("table_id", table_id) return common.handle_info(table_id) diff --git a/pychunkedgraph/app/segmentation/v1/routes.py b/pychunkedgraph/app/segmentation/v1/routes.py index 1c4aa7c7a..778e79e4a 100644 --- a/pychunkedgraph/app/segmentation/v1/routes.py +++ b/pychunkedgraph/app/segmentation/v1/routes.py @@ -9,7 +9,7 @@ from middle_auth_client import auth_requires_admin from middle_auth_client import auth_required -from pychunkedgraph.app.app_utils import jsonify_with_kwargs, toboolean, tobinary +from pychunkedgraph.app.app_utils import jsonify_with_kwargs, toboolean, tobinary, remap_public from pychunkedgraph.app.segmentation import common from pychunkedgraph.graph import exceptions as cg_exceptions @@ -69,6 +69,7 @@ def api_exception(e): @bp.route("/table//merge", methods=["POST"]) @auth_requires_permission("edit") +@remap_public(edit=True) def handle_merge(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) merge_result = common.handle_merge(table_id) @@ -81,6 +82,7 @@ def handle_merge(table_id): @bp.route("/table//merge_admin", methods=["POST"]) @auth_requires_permission("admin") +@remap_public(edit=True) def handle_merge_admin(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) allow_same_segment_merge = request.args.get( @@ -101,6 +103,7 @@ def handle_merge_admin(table_id): @bp.route("/table//split", methods=["POST"]) @auth_requires_permission("edit") +@remap_public(edit=True) def handle_split(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) split_result = common.handle_split(table_id) @@ -113,6 +116,7 @@ def handle_split(table_id): @bp.route("/table//graph/split_preview", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=True) def handle_split_preview(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) split_preview = common.handle_split_preview(table_id) @@ -124,6 +128,7 @@ def handle_split_preview(table_id): @bp.route("/table//undo", methods=["POST"]) @auth_requires_permission("edit") +@remap_public(edit=True) def handle_undo(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) undo_result = common.handle_undo(table_id) @@ -139,6 +144,7 @@ def handle_undo(table_id): @bp.route("/table//redo", methods=["POST"]) @auth_requires_permission("edit") +@remap_public(edit=True) def handle_redo(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) redo_result = common.handle_redo(table_id) @@ -154,6 +160,7 @@ def handle_redo(table_id): @bp.route("/table//rollback_user", methods=["POST"]) @auth_requires_admin +@remap_public(edit=True) def handle_rollback(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) rollback_result = common.handle_rollback(table_id) @@ -166,6 +173,7 @@ def handle_rollback(table_id): @bp.route("/table//user_operations", methods=["GET"]) @auth_requires_permission("admin_view") +@remap_public(edit=True) def handle_user_operations(table_id): disp = request.args.get("disp", default=False, type=toboolean) user_operations = pd.DataFrame.from_dict(common.all_user_operations(table_id)) @@ -181,6 +189,7 @@ def handle_user_operations(table_id): @bp.route("/table//node//root", methods=["GET"]) @auth_requires_permission("view") +@remap_public def handle_root(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) root_id = common.handle_root(table_id, node_id) @@ -193,6 +202,7 @@ def handle_root(table_id, node_id): @bp.route("/table//roots", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_roots(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) root_ids = common.handle_roots(table_id, is_binary=False) @@ -210,6 +220,7 @@ def handle_roots(table_id): @bp.route("/table//roots_binary", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_roots_binary(table_id): root_ids = common.handle_roots(table_id, is_binary=True) return tobinary(root_ids) @@ -220,6 +231,7 @@ def handle_roots_binary(table_id): @bp.route("/table//node//children", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_children(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) children_ids = common.handle_children(table_id, node_id) @@ -232,6 +244,7 @@ def handle_children(table_id, node_id): @bp.route("/table//l2_chunk_children/", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_l2_chunk_children(table_id, chunk_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) as_array = request.args.get("as_array", default=False, type=toboolean) @@ -248,6 +261,7 @@ def handle_l2_chunk_children(table_id, chunk_id): @bp.route("/table//l2_chunk_children_binary/", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_l2_chunk_children_binary(table_id, chunk_id): as_array = request.args.get("as_array", default=False, type=toboolean) l2_chunk_children = common.handle_l2_chunk_children(table_id, chunk_id, as_array) @@ -262,6 +276,7 @@ def handle_l2_chunk_children_binary(table_id, chunk_id): @bp.route("/table//node//leaves", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_leaves(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) leaf_ids = common.handle_leaves(table_id, node_id) @@ -275,6 +290,7 @@ def handle_leaves(table_id, node_id): @bp.route("/table//node/leaves_many", methods=["POST"]) @bp.route("/table//leaves_many", methods=["POST"]) @auth_requires_permission("view") +@remap_public(check_node_ids=True) def handle_leaves_many(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) root_to_leaf_dict = common.handle_leaves_many(table_id) @@ -286,6 +302,7 @@ def handle_leaves_many(table_id): @bp.route("/table//node//subgraph", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_subgraph(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) edges = common.handle_subgraph(table_id, node_id) @@ -302,6 +319,7 @@ def handle_subgraph(table_id, node_id): @bp.route("/table//node//contact_sites", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_contact_sites(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) contact_sites, contact_site_metadata = common.handle_contact_sites( @@ -319,6 +337,7 @@ def handle_contact_sites(table_id, node_id): methods=["GET"], ) @auth_requires_permission("view") +@remap_public(edit=False) def handle_pairwise_contact_sites(table_id, first_node_id, second_node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) contact_sites, contact_site_metadata = common.handle_pairwise_contact_sites( @@ -336,6 +355,7 @@ def handle_pairwise_contact_sites(table_id, first_node_id, second_node_id): @bp.route("/table//change_log", methods=["GET"]) @auth_requires_admin +@remap_public(edit=False) def change_log_full(table_id): si = io.StringIO() cw = csv.writer(si) @@ -350,6 +370,7 @@ def change_log_full(table_id): @bp.route("/table//tabular_change_log_recent", methods=["GET"]) @auth_requires_permission("admin_view") +@remap_public(edit=True) def tabular_change_log_weekly(table_id): disp = request.args.get("disp", default=False, type=toboolean) weekly_tab_change_log = common.tabular_change_log_recent(table_id) @@ -362,6 +383,7 @@ def tabular_change_log_weekly(table_id): @bp.route("/table//root//change_log", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=True) def change_log(table_id, root_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) log = common.change_log(table_id, root_id) @@ -370,6 +392,7 @@ def change_log(table_id, root_id): @bp.route("/table//root//tabular_change_log", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def tabular_change_log(table_id, root_id): disp = request.args.get("disp", default=False, type=toboolean) get_root_ids = request.args.get("root_ids", default=False, type=toboolean) @@ -386,6 +409,7 @@ def tabular_change_log(table_id, root_id): @bp.route("/table//root//merge_log", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def merge_log(table_id, root_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) log = common.merge_log(table_id, root_id) @@ -394,6 +418,7 @@ def merge_log(table_id, root_id): @bp.route("/table//root//lineage_graph", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_lineage_graph(table_id, root_id): from networkx import node_link_data @@ -404,6 +429,7 @@ def handle_lineage_graph(table_id, root_id): @bp.route("/table//past_id_mapping", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_past_id_mapping(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) resp = common.handle_past_id_mapping(table_id) @@ -412,6 +438,7 @@ def handle_past_id_mapping(table_id): @bp.route("/table//oldest_timestamp", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def oldest_timestamp(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) delimiter = request.args.get("delimiter", default=" ", type=str) @@ -422,6 +449,7 @@ def oldest_timestamp(table_id): @bp.route("/table//root//last_edit", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def last_edit(table_id, root_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) delimiter = request.args.get("delimiter", default=" ", type=str) @@ -435,6 +463,7 @@ def last_edit(table_id, root_id): @bp.route("/table//graph/find_path", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=False) def find_path(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) precision_mode = request.args.get("precision_mode", default=True, type=toboolean) @@ -445,8 +474,10 @@ def find_path(table_id): ### ROOT INFO ----------------------------------------------------------------- + @bp.route("/table//is_latest_roots", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_is_latest_roots(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) is_latest_roots = common.handle_is_latest_roots(table_id, is_binary=False) @@ -471,6 +502,7 @@ def handle_root_timestamps(table_id): @bp.route("/table//roots_from_coords", methods=["POST"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_roots_from_coords(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) resp = common.handle_roots_from_coord(table_id) @@ -480,6 +512,7 @@ def handle_roots_from_coords(table_id): ## Get level2 graph ------------------------------------------------------------- @bp.route("/table//node//lvl2_graph", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def handle_get_lvl2_graph(table_id, node_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) resp = common.handle_get_layer2_graph(table_id, node_id) @@ -491,6 +524,7 @@ def handle_get_lvl2_graph(table_id, node_id): @bp.route("/table//operation_details", methods=["GET"]) @auth_requires_permission("view") +@remap_public(edit=False) def operation_details(table_id): int64_as_str = request.args.get("int64_as_str", default=False, type=toboolean) resp = common.operation_details(table_id)