Skip to content

Commit

Permalink
get tree stat
Browse files Browse the repository at this point in the history
  • Loading branch information
andreax79 committed Jul 25, 2023
1 parent a765204 commit a3899eb
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 71 deletions.
12 changes: 6 additions & 6 deletions airflow_code_editor/app_builder_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ def load(self, path=None):
def format(self):
return self._format()

@expose("/tree", methods=["GET"])
@expose("/tree", methods=["GET", "HEAD"])
@auth.has_access(PERMISSIONS)
def tree_base(self, path=None):
return self._tree(path, args=request.args)
return self._tree(path, args=request.args, method=request.method)

@expose("/tree/<path:path>", methods=["GET"])
@expose("/tree/<path:path>", methods=["GET", "HEAD"])
@auth.has_access(PERMISSIONS)
def tree(self, path=None):
return self._tree(path, args=request.args)
return self._tree(path, args=request.args, method=request.method)

@expose("/ping", methods=["GET"])
@auth.has_access(PERMISSIONS)
Expand Down Expand Up @@ -143,12 +143,12 @@ def format(self):
@expose("/tree", methods=["GET"])
@has_dag_access(can_dag_edit=True)
def tree_base(self, path=None):
return self._tree(path)
return self._tree(path, args=request.args, method=request.method)

@expose("/tree/<path:path>", methods=["GET"])
@has_dag_access(can_dag_edit=True)
def tree(self, path=None):
return self._tree(path)
return self._tree(path, args=request.args, method=request.method)

def _render(self, template, *args, **kargs):
return self.render_template(
Expand Down
30 changes: 15 additions & 15 deletions airflow_code_editor/code_editor_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
from flask import request, make_response
from flask_wtf.csrf import generate_csrf
from airflow.version import version
from airflow_code_editor.commons import HTTP_404_NOT_FOUND
from airflow_code_editor.tree import get_tree
from airflow_code_editor.commons import HTTP_200_OK, HTTP_404_NOT_FOUND
from airflow_code_editor.tree import get_tree, get_stat
from airflow_code_editor.utils import (
get_plugin_boolean_config,
get_plugin_int_config,
Expand Down Expand Up @@ -63,9 +63,7 @@ def _save(self, path=None):
logging.error(ex)
return prepare_api_response(
path=normalize_path(path),
error_message="Error saving {path}: {message}".format(
path=path, message=error_message(ex)
),
error_message="Error saving {path}: {message}".format(path=path, message=error_message(ex)),
)

def _git_repo(self, path):
Expand All @@ -84,9 +82,7 @@ def _git_repo_get(self, path):
attachment_filename = None
response = execute_git_command(["cat-file", "-p", path]).prepare_git_response()
if attachment_filename:
content_disposition = 'attachment; filename="{0}"'.format(
attachment_filename
)
content_disposition = 'attachment; filename="{0}"'.format(attachment_filename)
response.headers["Content-Disposition"] = content_disposition
try:
content_type = mimetypes.guess_type(attachment_filename)[0]
Expand Down Expand Up @@ -140,15 +136,19 @@ def _format(self):
)
except Exception as ex:
logging.error(ex)
return prepare_api_response(
error_message="Error formatting: {message}".format(
message=error_message(ex)
)
)
return prepare_api_response(error_message="Error formatting: {message}".format(message=error_message(ex)))

def _tree(self, path, args={}):
def _tree(self, path, args={}, method="GET"):
try:
return prepare_api_response(value=get_tree(path, args))
if method == "HEAD":
stat = get_stat(path)
response = make_response("OK", HTTP_200_OK)
response.headers["X-Id"] = stat["id"]
response.headers["X-Leaf"] = "true" if stat["leaf"] else "false"
response.headers["X-Exists"] = "true" if stat["exists"] else "false"
return response
else:
return prepare_api_response(value=get_tree(path, args))
except Exception as ex:
logging.error(ex)
return prepare_api_response(
Expand Down
8 changes: 4 additions & 4 deletions airflow_code_editor/flask_admin_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,15 @@ def load(self, path=None):
def format(self, path=None):
return self._load(path)

@expose("/tree", methods=["GET"])
@expose("/tree", methods=["GET", "HEAD"])
@login_required
def tree_base(self, path=None):
return self._tree(path, args=request.args)
return self._tree(path, args=request.args, method=request.method)

@expose("/tree/<path:path>", methods=["GET"])
@expose("/tree/<path:path>", methods=["GET", "HEAD"])
@login_required
def tree(self, path=None):
return self._tree(path, args=request.args)
return self._tree(path, args=request.args, method=request.method)

@expose("/ping", methods=["GET"])
@login_required
Expand Down
25 changes: 18 additions & 7 deletions airflow_code_editor/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the Licens

import re
import fs
from datetime import datetime
from typing import Any, Callable, Dict, List, NamedTuple, Optional
from airflow_code_editor.commons import (
Expand All @@ -39,7 +40,7 @@
)
from airflow_code_editor.fs import RootFS

__all__ = ['get_tree']
__all__ = ['get_tree', 'get_stat']


class NodeDef(NamedTuple):
Expand Down Expand Up @@ -78,9 +79,7 @@ def get_root_node(path: Optional[str], args: Args) -> TreeOutput:
if id_ is None or not node.condition():
continue
# Add the node
result.append(
{'id': id_, 'label': node.label, 'leaf': node.leaf, 'icon': node.icon}
)
result.append({'id': id_, 'label': node.label, 'leaf': node.leaf, 'icon': node.icon})
# If the node is files, add the mount points
if id_ == 'files':
mount_points = read_mount_points_config()
Expand Down Expand Up @@ -115,9 +114,7 @@ def get_files_node(path: Optional[str], args: Args) -> TreeOutput:
'leaf': leaf,
'size': size,
'mode': s.st_mode,
'mtime': datetime.fromtimestamp(int(s.st_mtime)).isoformat()
if s.st_mtime
else None,
'mtime': datetime.fromtimestamp(int(s.st_mtime)).isoformat() if s.st_mtime else None,
}
)
else: # Short format
Expand Down Expand Up @@ -216,3 +213,17 @@ def get_tree(path: Optional[str] = None, args: Args = {}) -> TreeOutput:
return []
# Execute node function
return TREE_NODES[root].get_children(path_argv, args)


def get_stat(path: Optional[str] = None, args: Args = {}) -> TreeOutput:
"Get stat for the given path"
try:
if not path:
return {'id': path, 'leaf': False, 'exists': True}
path = normalize_path(path)
get_tree(path, args)
return {'id': path, 'leaf': False, 'exists': True}
except fs.errors.DirectoryExpected:
return {'id': path, 'leaf': True, 'exists': True}
except fs.errors.ResourceNotFound:
return {'id': path, 'leaf': None, 'exists': False}
19 changes: 5 additions & 14 deletions airflow_code_editor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,15 @@ def get_plugin_boolean_config(key: str) -> bool:
"Get a plugin boolean configuration/default for a given key"
return cast(
bool,
configuration.conf.getboolean(
PLUGIN_NAME, key, fallback=PLUGIN_DEFAULT_CONFIG[key]
),
configuration.conf.getboolean(PLUGIN_NAME, key, fallback=PLUGIN_DEFAULT_CONFIG[key]),
) # type: ignore


def get_plugin_int_config(key: str) -> int:
"Get a plugin int configuration/default for a given key"
return cast(
int,
configuration.conf.getint(
PLUGIN_NAME, key, fallback=PLUGIN_DEFAULT_CONFIG[key]
),
configuration.conf.getint(PLUGIN_NAME, key, fallback=PLUGIN_DEFAULT_CONFIG[key]),
) # type: ignore


Expand All @@ -94,8 +90,7 @@ def is_enabled() -> bool:
def get_root_folder() -> Path:
"Return the configured root folder or Airflow DAGs folder"
return Path(
get_plugin_config('root_directory')
or cast(str, configuration.conf.get('core', 'dags_folder')) # type: ignore
get_plugin_config('root_directory') or cast(str, configuration.conf.get('core', 'dags_folder')) # type: ignore
).resolve()


Expand Down Expand Up @@ -141,17 +136,13 @@ def read_mount_points_config() -> Dict[str, MountPoint]:
else:
suffix = str(i)
try:
if not configuration.conf.has_option(
PLUGIN_NAME, 'mount{}_name'.format(suffix)
):
if not configuration.conf.has_option(PLUGIN_NAME, 'mount{}_name'.format(suffix)):
break
except Exception: # backports.configparser.NoSectionError and friends
break
name = configuration.conf.get(PLUGIN_NAME, 'mount{}_name'.format(suffix))
path = configuration.conf.get(PLUGIN_NAME, 'mount{}_path'.format(suffix))
config[name] = MountPoint(
path=path, default=mount_conf['name'] == ROOT_MOUNTPOUNT
)
config[name] = MountPoint(path=path, default=mount_conf['name'] == ROOT_MOUNTPOUNT)
return config


Expand Down
128 changes: 103 additions & 25 deletions tests/test_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
class TestTree(TestCase):
def setUp(self):
self.root_dir = Path(__file__).parent
configuration.conf.set(PLUGIN_NAME, 'git_init_repo', 'False')
configuration.conf.set(PLUGIN_NAME, 'root_directory', str(self.root_dir))
configuration.conf.set(PLUGIN_NAME, "git_init_repo", "False")
configuration.conf.set(PLUGIN_NAME, "root_directory", str(self.root_dir))

def test_tree(self):
with app.app_context():
t = get_tree()
assert len(t) > 0
assert 'git' in (x['id'] for x in t)
assert "git" in (x["id"] for x in t)

def test_tags(self):
with app.app_context():
Expand All @@ -48,28 +48,28 @@ def test_remote_branches(self):
def test_files(self):
with app.app_context():
t = get_tree("files")
assert len([x.get('id') for x in t if x.get('id') == 'test_utils.py']) == 1
assert len([x.get("id") for x in t if x.get("id") == "test_utils.py"]) == 1
t = get_tree("files/folder")
assert len([x.get('id') for x in t if x.get('id') == '1']) == 1
assert len([x.get("id") for x in t if x.get("id") == "1"]) == 1

def test_files_long(self):
with app.app_context():
t = get_tree("files", ["long"])
assert len([x.get('id') for x in t if x.get('id') == 'folder']) == 1
folder = [x for x in t if x.get('id') == 'folder'][0]
assert not folder['leaf']
assert folder['size'] == 3
assert stat.S_ISDIR(folder['mode'])
assert len([x.get("id") for x in t if x.get("id") == "folder"]) == 1
folder = [x for x in t if x.get("id") == "folder"][0]
assert not folder["leaf"]
assert folder["size"] == 3
assert stat.S_ISDIR(folder["mode"])

self.assertEqual(len([x.get('id') for x in t if x.get('id') == 'test_utils.py']), 1)
test_utils = [x for x in t if x.get('id') == 'test_utils.py'][0]
assert test_utils['leaf']
assert not stat.S_ISDIR(test_utils['mode'])
self.assertEqual(len([x.get("id") for x in t if x.get("id") == "test_utils.py"]), 1)
test_utils = [x for x in t if x.get("id") == "test_utils.py"][0]
assert test_utils["leaf"]
assert not stat.S_ISDIR(test_utils["mode"])

t = get_tree("files/folder", ["long"])
assert len([x.get('id') for x in t if x.get('id') == '1']) == 1
one = [x for x in t if x.get('id') == '1'][0]
assert one['leaf']
assert len([x.get("id") for x in t if x.get("id") == "1"]) == 1
one = [x for x in t if x.get("id") == "1"][0]
assert one["leaf"]

def test_git(self):
with app.app_context():
Expand All @@ -80,17 +80,17 @@ def test_git(self):
class TestTreeGitDisabled(TestCase):
def setUp(self):
self.root_dir = Path(__file__).parent
configuration.conf.set(PLUGIN_NAME, 'git_init_repo', 'False')
configuration.conf.set(PLUGIN_NAME, 'root_directory', str(self.root_dir))
configuration.conf.set(PLUGIN_NAME, 'git_enabled', 'False')
os.environ['GIT_AUTHOR_NAME'] = os.environ['GIT_COMMITTER_NAME'] = 'git_author_name'
os.environ['GIT_AUTHOR_EMAIL'] = os.environ['GIT_COMMITTER_EMAIL'] = 'git_author_email'
configuration.conf.set(PLUGIN_NAME, "git_init_repo", "False")
configuration.conf.set(PLUGIN_NAME, "root_directory", str(self.root_dir))
configuration.conf.set(PLUGIN_NAME, "git_enabled", "False")
os.environ["GIT_AUTHOR_NAME"] = os.environ["GIT_COMMITTER_NAME"] = "git_author_name"
os.environ["GIT_AUTHOR_EMAIL"] = os.environ["GIT_COMMITTER_EMAIL"] = "git_author_email"

def test_tree(self):
with app.app_context():
t = get_tree()
assert len(t) > 0
assert 'git' not in (x['id'] for x in t)
assert "git" not in (x["id"] for x in t)
t = get_tree("tags")
assert t == []
t = get_tree("local-branches")
Expand All @@ -99,6 +99,84 @@ def test_tree(self):
assert t == []
t = get_tree("files")
print(t)
assert len([x.get('id') for x in t if x.get('id') == 'test_utils.py']) == 1
assert len([x.get("id") for x in t if x.get("id") == "test_utils.py"]) == 1
t = get_tree("files/folder")
assert len([x.get('id') for x in t if x.get('id') == '1']) == 1
assert len([x.get("id") for x in t if x.get("id") == "1"]) == 1


class TestStat(TestCase):
def setUp(self):
self.root_dir = Path(__file__).parent
configuration.conf.set(PLUGIN_NAME, "git_init_repo", "False")
configuration.conf.set(PLUGIN_NAME, "root_directory", str(self.root_dir))

def test_tree(self):
with app.app_context():
t = get_stat()
print(t)
assert t is not None
assert t["id"] is None
assert t["exists"]
assert not t["leaf"]

def test_tags(self):
with app.app_context():
t = get_stat("tags")
assert t is not None
assert t["id"] == "tags"
assert t["exists"]
assert not t["leaf"]

def test_local_branches(self):
with app.app_context():
t = get_stat("local-branches")
assert t is not None
assert t["id"] == "local-branches"
assert t["exists"]
assert not t["leaf"]

def test_remote_branches(self):
with app.app_context():
t = get_stat("remote-branches")
assert t is not None
assert t["id"] == "remote-branches"
assert t["exists"]
assert not t["leaf"]

def test_files(self):
with app.app_context():
t = get_stat("files")
assert t is not None
assert t["id"] == "files"
assert t["exists"]
assert not t["leaf"]

t = get_stat("files/test_utils.py")
assert t is not None
assert t["id"] == "files/test_utils.py"
assert t["exists"]
assert t["leaf"]

t = get_stat("files/not-found")
assert t is not None
assert not t["exists"]
assert t["leaf"] is None

def test_files_folder(self):
with app.app_context():
t = get_stat("files/folder")
assert t is not None
assert t["id"] == "files/folder"
assert t["exists"]
assert not t["leaf"]

t = get_stat("files/folder/1")
assert t is not None
assert t["id"] == "files/folder/1"
assert t["exists"]
assert t["leaf"]

t = get_stat("files/folder/not-found")
assert t is not None
assert not t["exists"]
assert t["leaf"] is None

0 comments on commit a3899eb

Please sign in to comment.