Skip to content

Commit 95b410f

Browse files
authored
Merge pull request #291 from XpressAI/fahreza/library-management
✨ Component Library Management Update
2 parents 1567e93 + 10cd312 commit 95b410f

17 files changed

+342
-121
lines changed

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ dependencies = [
2828
"requests",
2929
"gitpython",
3030
"pygithub",
31-
"tqdm"
31+
"tqdm",
32+
"toml",
3233
]
3334
dynamic = ["version", "description", "authors", "urls", "keywords"]
3435

src/tray_library/Component.tsx

+23-11
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,33 @@ import { requestAPI } from "../server/handler";
44
import React from 'react';
55

66
async function get_all_components_method() {
7-
const response = await requestAPI<any>('components/');
8-
const components = response["components"];
9-
const error_msg = response["error_msg"];
107

11-
if (error_msg) {
8+
try {
9+
// Trigger the load library config on refresh
10+
await requestAPI('library/reload_config', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
11+
12+
// Proceed with the GET request to fetch components
13+
const componentsResponse = await requestAPI<any>('components/');
14+
const components = componentsResponse["components"];
15+
const error_msg = componentsResponse["error_msg"];
16+
17+
if (error_msg) {
1218
showDialog({
13-
title: 'Parse Component Failed',
14-
body: (
15-
<pre>{error_msg}</pre>
16-
),
17-
buttons: [Dialog.warnButton({ label: 'OK' })]
19+
title: 'Parse Component Failed',
20+
body: (
21+
<pre>{error_msg}</pre>
22+
),
23+
buttons: [Dialog.warnButton({ label: 'OK' })]
1824
});
25+
}
26+
27+
return components;
28+
} catch (error) {
29+
console.error('Failed to get components or trigger library reload', error);
30+
// Handle the error appropriately in your application
1931
}
20-
return components;
21-
}
32+
}
33+
2234

2335
export default async function ComponentList() {
2436
let component_list_result: string[] = await get_all_components_method();

xai_components/xai_controlflow/library-config.ini

-3
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "xai-controlflow"
3+
version = "0.1.0"
4+
description = "Xircuits component library for controlflow purposes."
5+
authors = [{ name = "XpressAI", email = "[email protected]" }]
6+
license = "Apache-2.0"
7+
readme = "readme.md"
8+
repository = "https://github.com/XpressAI/xircuits/tree/master/xai_components/xai_controlflow"
9+
keywords = ["xircuits", "controlflow", "if-else", "branch"]
10+
11+
# Xircuits-specific configurations
12+
[tool.xircuits]
13+
default_example_path = "ControlflowBranch.xircuits"

xai_components/xai_events/library-config.ini

-3
This file was deleted.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "xai-events"
3+
version = "0.1.0"
4+
description = "Xircuits component library for decoupling features, like functions or events."
5+
authors = [{ name = "XpressAI", email = "[email protected]" }]
6+
license = "Apache-2.0"
7+
readme = "readme.md"
8+
repository = "https://github.com/XpressAI/xircuits/tree/master/xai_components/xai_events"
9+
keywords = ["xircuits", "events"]
10+
11+
# Xircuits-specific configurations
12+
[tool.xircuits]
13+
default_example_path = "Events.xircuits"

xai_components/xai_template/library-config.ini

-2
This file was deleted.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "xai-template"
3+
version = "0.1.0"
4+
description = "Xircuits component library template for Xircuits."
5+
authors = [{ name = "XpressAI", email = "[email protected]" }]
6+
license = "Apache-2.0"
7+
readme = "README.md"
8+
repository = "https://github.com/XpressAI/xircuits/tree/master/xai_components/xai_template"
9+
keywords = ["xircuits", "example components"]
10+
11+
# Xircuits-specific configurations
12+
[tool.xircuits]
13+
requirements_path = "requirements.txt"

xai_components/xai_utils/library-config.ini

-4
This file was deleted.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[project]
2+
name = "xai-utils"
3+
version = "0.1.0"
4+
description = "A collection of utility components for Xircuits."
5+
authors = [{ name = "XpressAI", email = "[email protected]" }]
6+
license = "Apache-2.0"
7+
readme = "readme.md"
8+
repository = "https://github.com/XpressAI/xircuits/tree/master/xai_components/xai_utils"
9+
keywords = ["xircuits", "utility"]
10+
11+
# Xircuits-specific configurations
12+
[tool.xircuits]
13+
default_example_path = "UtilsZipDirectory.xircuits"

xircuits/handlers/__init__.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .config import RunConfigRouteHandler, SplitModeConfigHandler
66
from .debugger import DebuggerRouteHandler
77
from .spark_submit import SparkSubmitRouteHandler
8-
from .request_library import InstallLibraryRouteHandler, GetLibraryDirectoryRouteHandler, GetLibraryReadmeRouteHandler, GetLibraryExampleRouteHandler
8+
from .request_library import InstallLibraryRouteHandler, GetLibraryDirectoryRouteHandler, GetLibraryReadmeRouteHandler, GetLibraryExampleRouteHandler, ReloadComponentLibraryConfigHandler
99

1010

1111
def setup_handlers(web_app, url_path):
@@ -23,7 +23,7 @@ def setup_handlers(web_app, url_path):
2323
RunConfigRouteHandler
2424
),
2525
(
26-
url_path_join(base_url, url_path, "config/split_mode"),
26+
url_path_join(base_url, url_path, "config/split_mode"),
2727
SplitModeConfigHandler
2828
),
2929
(
@@ -38,6 +38,10 @@ def setup_handlers(web_app, url_path):
3838
url_path_join(base_url, url_path, "file/compile"),
3939
CompileXircuitsFileRouteHandler
4040
),
41+
(
42+
url_path_join(base_url, url_path, "library/reload_config"),
43+
ReloadComponentLibraryConfigHandler
44+
),
4145
(
4246
url_path_join(base_url, url_path, "library/install"),
4347
InstallLibraryRouteHandler
@@ -54,5 +58,4 @@ def setup_handlers(web_app, url_path):
5458
url_path_join(base_url, url_path, "library/get_example"),
5559
GetLibraryExampleRouteHandler
5660
)
57-
5861
])

xircuits/handlers/request_library.py

+25-31
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
import tornado
55
import posixpath
66
from jupyter_server.base.handlers import APIHandler
7-
from pathlib import Path
8-
from xircuits.library import install_library, get_library_config
7+
from xircuits.library import install_library, build_library_file_path_from_config, save_component_library_config
98

109
class InstallLibraryRouteHandler(APIHandler):
1110
@tornado.web.authenticated
@@ -43,37 +42,21 @@ def post(self):
4342
response = {"path": directory_path}
4443
self.finish(json.dumps(response))
4544

46-
47-
def get_library_file_path(library_name, path_key, default_filename):
48-
config = get_library_config(library_name)
49-
base_path = Path("xai_components") / f"xai_{library_name.lower()}"
50-
51-
if config and 'Paths' in config and path_key in config['Paths']:
52-
relative_path = config['Paths'][path_key]
53-
else:
54-
relative_path = default_filename
55-
56-
full_path = posixpath.join(base_path, relative_path)
57-
if os.path.isfile(full_path):
58-
return full_path
59-
else:
60-
return None
61-
6245
class GetLibraryReadmeRouteHandler(APIHandler):
6346
@tornado.web.authenticated
6447
def post(self):
6548
input_data = self.get_json_body()
6649
library_name = input_data.get("libraryName")
6750

68-
if not library_name:
69-
self.finish(json.dumps({"message": "Library name is required"}))
70-
return
51+
file_path = build_library_file_path_from_config(library_name, "readme")
7152

72-
readme_path = get_library_file_path(library_name, 'readme_path', "README.md")
73-
if readme_path:
74-
response = {"path": readme_path}
53+
if file_path:
54+
if os.path.exists(file_path):
55+
response = {"path": file_path}
56+
else:
57+
response = {"message": "Readme file not found."}
7558
else:
76-
response = {"message": "README file not found."}
59+
response = {"message": "Readme configuration not found."}
7760

7861
self.finish(json.dumps(response))
7962

@@ -83,14 +66,25 @@ def post(self):
8366
input_data = self.get_json_body()
8467
library_name = input_data.get("libraryName")
8568

86-
if not library_name:
87-
self.finish(json.dumps({"message": "Library name is required"}))
88-
return
69+
example_path = build_library_file_path_from_config(library_name, "default_example_path")
8970

90-
example_path = get_library_file_path(library_name, 'default_example_path', "example.xircuits")
9171
if example_path:
92-
response = {"path": example_path}
72+
if os.path.exists(example_path):
73+
response = {"path": example_path}
74+
else:
75+
response = {"message": "Example file not found."}
9376
else:
94-
response = {"message": "No .xircuits example file found in the library."}
77+
response = {"message": "Example configuration not found."}
78+
79+
self.finish(json.dumps(response))
80+
81+
class ReloadComponentLibraryConfigHandler(APIHandler):
82+
@tornado.web.authenticated
83+
def post(self):
84+
try:
85+
save_component_library_config()
86+
response = {"message": "Library config updated."}
87+
except Exception as e:
88+
response = {"message": f"Something went wrong: {str(e)}"}
9589

9690
self.finish(json.dumps(response))

xircuits/library/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .list_library import list_component_library
2-
from .install_fetch_library import install_library, fetch_library, get_library_config
2+
from .install_fetch_library import install_library, fetch_library, get_library_config, build_library_file_path_from_config
3+
from .generate_component_library_config import save_component_library_config
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import os
2+
import json
3+
import toml
4+
from configparser import ConfigParser
5+
6+
def parse_gitmodules(gitmodules_path):
7+
config = ConfigParser()
8+
config.read(gitmodules_path)
9+
modules = []
10+
for section in config.sections():
11+
path = config.get(section, 'path', fallback=None)
12+
url = config.get(section, 'url', fallback=None)
13+
# Extract the library ID from the path
14+
library_id = path.replace('xai_components/xai_', '').upper()
15+
if path and url:
16+
modules.append({
17+
'path': os.path.normpath(path),
18+
'url': url,
19+
'library_id': library_id
20+
})
21+
return modules
22+
23+
def parse_toml_file(toml_path):
24+
try:
25+
with open(toml_path, 'r') as toml_file:
26+
data = toml.load(toml_file)
27+
return data
28+
except Exception as e:
29+
print(f"Error parsing TOML file at {toml_path}: {e}")
30+
return None
31+
32+
def read_file_lines_to_list(file_path):
33+
if not os.path.exists(file_path):
34+
return []
35+
with open(file_path, 'r') as file:
36+
return [line.strip() for line in file.readlines()]
37+
38+
def extract_library_info(lib_path, base_path, status="installed"):
39+
relative_lib_path = os.path.join(base_path, os.path.relpath(lib_path, start=base_path))
40+
toml_path = os.path.join(lib_path, 'pyproject.toml')
41+
42+
if not os.path.exists(toml_path):
43+
return None
44+
45+
toml_data = parse_toml_file(toml_path)
46+
47+
# Check if TOML data was successfully parsed
48+
if toml_data is None:
49+
return None
50+
51+
# Remove 'xai_' or 'xai-' prefix and convert to uppercase
52+
library_id = toml_data["project"]["name"].replace("xai_", "").replace("xai-", "").upper()
53+
54+
requirements_rel_path = toml_data["tool"]["xircuits"].get("requirements_path", None)
55+
requirements_path = None
56+
requirements = []
57+
58+
if requirements_rel_path is not None:
59+
requirements_path = os.path.join(lib_path, requirements_rel_path)
60+
if os.path.isfile(requirements_path):
61+
requirements = read_file_lines_to_list(requirements_path)
62+
else:
63+
requirements_path = None # Reset to None if the file does not exist
64+
65+
lib_info = {
66+
"name": toml_data["project"]["name"],
67+
"library_id": library_id,
68+
"version": toml_data["project"].get("version", "N/A"),
69+
"description": toml_data["project"].get("description", "No description available."),
70+
"authors": toml_data["project"].get("authors", []),
71+
"license": toml_data["project"].get("license", "N/A"),
72+
"readme": toml_data["project"].get("readme", None),
73+
"repository": toml_data["project"].get("repository", None),
74+
"keywords": toml_data["project"].get("keywords", []),
75+
"local_path": relative_lib_path,
76+
"status": status,
77+
"requirements_path": requirements_path,
78+
"requirements": requirements,
79+
"default_example_path": toml_data["tool"]["xircuits"].get("default_example_path", None),
80+
}
81+
82+
return lib_info
83+
84+
def generate_component_library_config(base_path="xai_components", gitmodules_path=".gitmodules"):
85+
86+
if not os.path.exists(gitmodules_path):
87+
# Construct the .xircuits/.gitmodules path
88+
gitmodules_path = os.path.join('.xircuits', '.gitmodules')
89+
90+
libraries = {}
91+
library_id_map = {} # Map library IDs to library info
92+
93+
# Parse submodules first and set them as "remote"
94+
if os.path.exists(gitmodules_path):
95+
submodules = parse_gitmodules(gitmodules_path)
96+
for submodule in submodules:
97+
submodule_path = os.path.normpath(submodule['path'])
98+
library_info = {
99+
"name": os.path.basename(submodule_path),
100+
"library_id": submodule['library_id'], # Use the library ID from the submodule info
101+
"repository": submodule['url'],
102+
"local_path": submodule_path,
103+
"status": "remote"
104+
}
105+
libraries[submodule_path] = library_info
106+
library_id_map[submodule['library_id']] = library_info
107+
108+
def explore_directory(directory, base_path):
109+
for item in os.listdir(directory):
110+
full_path = os.path.normpath(os.path.join(directory, item))
111+
if os.path.isdir(full_path) and item.startswith("xai_"):
112+
lib_info = extract_library_info(full_path, base_path)
113+
if lib_info: # If a valid pyproject.toml is found
114+
if lib_info['library_id'] in library_id_map: # Match by library ID
115+
# Update the existing entry with the new info
116+
library_id_map[lib_info['library_id']].update(lib_info)
117+
else:
118+
libraries[full_path] = lib_info # Add new library info
119+
# Recursively explore subdirectories
120+
explore_directory(full_path, base_path)
121+
122+
explore_directory(base_path, base_path)
123+
124+
return {"libraries": list(libraries.values())}
125+
126+
def save_component_library_config(filename=".xircuits/component_library_config.json"):
127+
libraries_data = generate_component_library_config()
128+
os.makedirs(os.path.dirname(filename), exist_ok=True)
129+
with open(filename, 'w') as json_file:
130+
json.dump(libraries_data, json_file, indent=4)
131+
132+
if __name__ == "__main__":
133+
save_component_library_config()

0 commit comments

Comments
 (0)