Compare commits
5 Commits
7e520e685a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d0ac475ba | |||
| faaa8bb357 | |||
| d209c1abe9 | |||
| f0b6a5c6fe | |||
| 1e07749716 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
PyApp - A Python application framework
|
||||
pyboot - A Python application framework
|
||||
"""
|
||||
|
||||
__version__ = "0.1"
|
||||
@@ -1,11 +1,11 @@
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
application_class = {}
|
||||
def PyApplication(arg=None):
|
||||
def PybootApplication(arg=None):
|
||||
if callable(arg):
|
||||
cls = arg
|
||||
if "default" in application_class:
|
||||
Log.error("Multiple classes annotated with default @PyApplication, require a 'name' parameter.", True)
|
||||
Log.error("Multiple classes annotated with default @PybootApplication, require a 'name' parameter.", True)
|
||||
application_class["default"] = cls
|
||||
return cls
|
||||
|
||||
@@ -13,9 +13,9 @@ def PyApplication(arg=None):
|
||||
name = arg
|
||||
def decorator(cls):
|
||||
if name is None:
|
||||
raise Log.error("The 'name' parameter is required when using @PyApplication with arguments.", True)
|
||||
raise Log.error("The 'name' parameter is required when using @PybootApplication with arguments.", True)
|
||||
if name in application_class:
|
||||
raise Log.error(f"Multiple classes annotated with @PyApplication with the same name '{name}' found.", True)
|
||||
raise Log.error(f"Multiple classes annotated with @PybootApplication with the same name '{name}' found.", True)
|
||||
application_class[name] = cls
|
||||
return cls
|
||||
return decorator
|
||||
@@ -1,14 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pyapp.application import application_class
|
||||
from pyapp.stereotype import get_all_component_classes, get_all_component_comments
|
||||
from pyapp.utils.log import Log
|
||||
from pyapp.utils.timer import Timer
|
||||
from pyapp.utils.project import Project
|
||||
from pyapp.templates.application import template as app_template
|
||||
from pyapp.templates.config import template as config_template
|
||||
from pyapp.ui.server.app import app
|
||||
from pyboot.application import application_class
|
||||
from pyboot.stereotype import get_all_component_classes, get_all_component_comments
|
||||
from pyboot.utils.log import Log
|
||||
from pyboot.utils.timer import Timer
|
||||
from pyboot.utils.project import Project
|
||||
from pyboot.templates.application import template as app_template
|
||||
from pyboot.templates.config import template as config_template
|
||||
from pyboot.ui.server.app import app
|
||||
|
||||
|
||||
def run():
|
||||
@@ -17,19 +17,19 @@ def run():
|
||||
|
||||
app_name = "default"
|
||||
if len(application_class) == 0:
|
||||
Log.error("No class annotated with @PyApplication found.", True)
|
||||
Log.error("No class annotated with @PybootApplication found.", True)
|
||||
if len(sys.argv) < 3 and "default" not in application_class:
|
||||
Log.error("No default @PyApplication found. Please specify the 'name' parameter.", True)
|
||||
Log.error("No default @PybootApplication found. Please specify the 'name' parameter.", True)
|
||||
if len(sys.argv) == 3:
|
||||
app_name = sys.argv[2]
|
||||
|
||||
app_cls = application_class.get(app_name)
|
||||
|
||||
if app_cls is None:
|
||||
Log.error(f"No class annotated with @PyApplication found with the name '{app_name}'.", True)
|
||||
Log.error(f"No class annotated with @PybootApplication found with the name '{app_name}'.", True)
|
||||
|
||||
if not hasattr(app_cls, "start"):
|
||||
Log.error("The class annotated with @PyApplication should have a 'start' method.", True)
|
||||
Log.error("The class annotated with @PybootApplication should have a 'start' method.", True)
|
||||
|
||||
Log.info(f"Application '{app_cls.__name__}' started.")
|
||||
timer = Timer(f"Application({app_name})")
|
||||
@@ -42,7 +42,7 @@ def run():
|
||||
|
||||
|
||||
def init():
|
||||
Log.info("Initializing Pyapp project.")
|
||||
Log.info("Initializing Pyboot project.")
|
||||
root_path = os.getcwd()
|
||||
if len(os.listdir(root_path)) > 0:
|
||||
Log.error("Current directory is not empty. Please provide an empty directory.")
|
||||
@@ -52,7 +52,7 @@ def init():
|
||||
with open(os.path.join(root_path, "config.yaml"), "w") as file:
|
||||
file.write(config_template)
|
||||
|
||||
Log.success("Pyapp project initialized.")
|
||||
Log.success("Pyboot project initialized.")
|
||||
Log.info("Now you can create your components and run the application.")
|
||||
|
||||
def scan():
|
||||
@@ -80,16 +80,16 @@ def ui():
|
||||
port = 5000
|
||||
if len(sys.argv) == 3:
|
||||
port = int(sys.argv[2])
|
||||
Log.success(f"Pyapp UI server started at http://localhost:{port}")
|
||||
Log.success(f"Pyboot UI server started at http://localhost:{port}")
|
||||
app.run(port=port, host="0.0.0.0")
|
||||
|
||||
|
||||
def help():
|
||||
Log.info("Pyapp commands:")
|
||||
Log.info(" init: Initialize a new Pyapp project in the current directory.")
|
||||
Log.info(" run [name]: Run the Pyapp application with the specified name. If no name is provided, the default application will be run.")
|
||||
Log.info(" scan: Scan the project for Pyapp components.")
|
||||
Log.info(" ui [port]: Start the Pyapp UI server. If no port is provided, the default port 5000 will be used.")
|
||||
Log.info("Pyboot commands:")
|
||||
Log.info(" init: Initialize a new Pyboot project in the current directory.")
|
||||
Log.info(" run [name]: Run the Pyboot application with the specified name. If no name is provided, the default application will be run.")
|
||||
Log.info(" scan: Scan the project for Pyboot components.")
|
||||
Log.info(" ui [port]: Start the Pyboot UI server. If no port is provided, the default port 5000 will be used.")
|
||||
Log.info(" help: Display this help message.")
|
||||
|
||||
def main():
|
||||
@@ -105,6 +105,6 @@ def main():
|
||||
elif sys.argv[1] == "help":
|
||||
help()
|
||||
else:
|
||||
Log.error("Invalid command: " + sys.argv[1] + ". Use 'pyapp help' for help.")
|
||||
Log.error("Invalid command: " + sys.argv[1] + ". Use 'pyboot help' for help.")
|
||||
else:
|
||||
Log.error("Please provide a command to run the application.")
|
||||
@@ -1,4 +1,4 @@
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class Component:
|
||||
TYPE: str
|
||||
@@ -1,7 +1,7 @@
|
||||
from pyapp.component import Component
|
||||
from pyapp.stereotype import *
|
||||
from pyapp.utils.log import Log
|
||||
from pyapp.config import ConfigManager
|
||||
from pyboot.component import Component
|
||||
from pyboot.stereotype import *
|
||||
from pyboot.utils.log import Log
|
||||
from pyboot.config import ConfigManager
|
||||
|
||||
class ComponentFactory:
|
||||
@staticmethod
|
||||
@@ -2,7 +2,7 @@ import argparse
|
||||
import os.path
|
||||
import shutil
|
||||
import yaml
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class ConfigManager:
|
||||
config = None
|
||||
@@ -2,8 +2,8 @@ import os
|
||||
import time
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from pyapp.config import ConfigManager
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.config import ConfigManager
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class Runner(ABC):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import inspect
|
||||
|
||||
from pyapp.component import Component
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.component import Component
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
|
||||
def ensure_component_subclass(cls, type_name, name):
|
||||
@@ -24,9 +24,9 @@ def ensure_component_subclass(cls, type_name, name):
|
||||
all_component_classes = {}
|
||||
all_component_comments = {}
|
||||
|
||||
def register_component_type(component_type, comment=None):
|
||||
def register_component_type(component_type):
|
||||
all_component_classes[component_type] = {}
|
||||
all_component_comments[component_type] = comment
|
||||
all_component_comments[component_type] = {}
|
||||
|
||||
# --- Classes --- #
|
||||
|
||||
@@ -34,49 +34,47 @@ def component(component_type, component_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "COMPONENT", component_name)
|
||||
if component_type not in all_component_classes:
|
||||
register_component_type(component_type, comment)
|
||||
register_component_type(component_type)
|
||||
all_component_classes[component_type][component_name] = cls
|
||||
all_component_comments[component_type][component_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
runner_classes = {}
|
||||
runner_comments = {}
|
||||
def runner(runner_name, comment=None):
|
||||
def decorator(cls):
|
||||
if not hasattr(cls, 'run') or not callable(getattr(cls, 'run')):
|
||||
Log.error(f"runner <{cls.__name__}> must implement a 'run' method", True)
|
||||
cls = ensure_component_subclass(cls, "RUNNER", runner_name)
|
||||
runner_classes[runner_name] = cls
|
||||
runner_comments[runner_name] = comment
|
||||
if "runner" not in all_component_classes:
|
||||
register_component_type("runner")
|
||||
all_component_classes["runner"][runner_name] = cls
|
||||
all_component_comments["runner"][runner_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
factory_classes = {}
|
||||
factory_comments = {}
|
||||
def factory(factory_name, comment=None):
|
||||
def decorator(cls):
|
||||
if not hasattr(cls, 'create') or not callable(getattr(cls, 'create')):
|
||||
Log.error(f"factory <{cls.__name__}> must implement a 'create' method", True)
|
||||
|
||||
cls = ensure_component_subclass(cls, "FACTORY", factory_name)
|
||||
factory_classes[factory_name] = cls
|
||||
factory_comments[factory_name] = comment
|
||||
if "factory" not in all_component_classes:
|
||||
register_component_type("factory")
|
||||
all_component_classes["factory"][factory_name] = cls
|
||||
all_component_comments["factory"][factory_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
client_classes = {}
|
||||
client_comments = {}
|
||||
def client(client_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "CLIENT", client_name)
|
||||
client_classes[client_name] = cls
|
||||
client_comments[client_name] = comment
|
||||
if "client" not in all_component_classes:
|
||||
register_component_type("client")
|
||||
all_component_classes["client"][client_name] = cls
|
||||
all_component_comments["client"][client_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
server_classes = {}
|
||||
server_comments = {}
|
||||
def server(server_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "SERVER", server_name)
|
||||
@@ -84,18 +82,15 @@ def server(server_name, comment=None):
|
||||
Log.error(f"server <{cls.__name__}> must implement 'host' and 'port' attributes", True)
|
||||
if not hasattr(cls, 'serve') or not callable(getattr(cls, 'serve')):
|
||||
Log.error(f"server <{cls.__name__}> must implement a 'serve' method", True)
|
||||
server_classes[server_name] = cls
|
||||
server_comments[server_name] = comment
|
||||
if "server" not in all_component_classes:
|
||||
register_component_type("server")
|
||||
all_component_classes["server"][server_name] = cls
|
||||
all_component_comments["server"][server_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
# --- Utils --- #
|
||||
|
||||
register_component_type("RUNNER", "Runner")
|
||||
register_component_type("FACTORY", "Factory")
|
||||
register_component_type("CLIENT", "Client")
|
||||
register_component_type("SERVER", "Server")
|
||||
|
||||
def get_all_component_classes():
|
||||
return all_component_classes
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""
|
||||
Utility modules for PyApp
|
||||
Template modules for pyboot
|
||||
"""
|
||||
@@ -1,13 +1,13 @@
|
||||
template = """from pyapp.application import PyappApplication
|
||||
template = """from pyboot.application import PybootApplication
|
||||
from runners import YourCustomRunner
|
||||
|
||||
@PyappApplication("your_application_name")
|
||||
@PybootApplication("your_application_name")
|
||||
class Application:
|
||||
@staticmethod
|
||||
def start():
|
||||
'''
|
||||
call default or your custom runners here, code will be executed
|
||||
automatically when type "pyapp run" in terminal
|
||||
automatically when type "pyboot run" or "pb run" in terminal
|
||||
|
||||
example:
|
||||
YourCustomRunner("path_to_your_config").run()
|
||||
3
build/lib/pyboot/ui/__init__.py
Normal file
3
build/lib/pyboot/ui/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
UI modules for pyboot
|
||||
"""
|
||||
3
build/lib/pyboot/ui/client/__init__.py
Normal file
3
build/lib/pyboot/ui/client/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Client-side UI components for Pyboot
|
||||
"""
|
||||
1
build/lib/pyboot/ui/client/index.html
Normal file
1
build/lib/pyboot/ui/client/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pyboot Project</title><link href=/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.9f7b4785a30f0533ee08.js></script><script type=text/javascript src=/static/js/app.d8ab046c4c34bfc5bd54.js></script></body></html>
|
||||
|
Before Width: | Height: | Size: 542 KiB After Width: | Height: | Size: 542 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
build/lib/pyboot/ui/server/__init__.py
Normal file
3
build/lib/pyboot/ui/server/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Server-side UI components for Pyboot
|
||||
"""
|
||||
@@ -8,12 +8,12 @@ import platform
|
||||
|
||||
from flask import Flask, jsonify, request, send_from_directory
|
||||
from flask_cors import CORS
|
||||
from pyapp.utils.project import Project
|
||||
from pyapp.stereotype import get_all_component_classes, get_all_component_comments
|
||||
from pyapp.application import application_class
|
||||
from pyapp.status import status_manager
|
||||
from pyapp.utils.log import Log
|
||||
from pyapp.utils.timer import Timer
|
||||
from pyboot.utils.project import Project
|
||||
from pyboot.stereotype import get_all_component_classes, get_all_component_comments
|
||||
from pyboot.application import application_class
|
||||
from pyboot.status import status_manager
|
||||
from pyboot.utils.log import Log
|
||||
from pyboot.utils.timer import Timer
|
||||
|
||||
app = Flask(__name__, static_folder="../client")
|
||||
app.logger.setLevel("WARNING")
|
||||
@@ -66,7 +66,7 @@ def run_application():
|
||||
|
||||
if app_cls is None:
|
||||
Log.error(
|
||||
f"No class annotated with @PyApplication found with the name '{app_name}'.",
|
||||
f"No class annotated with @Pybootlication found with the name '{app_name}'.",
|
||||
True,
|
||||
)
|
||||
return jsonify(
|
||||
@@ -78,7 +78,7 @@ def run_application():
|
||||
|
||||
if not hasattr(app_cls, "start"):
|
||||
Log.error(
|
||||
"The class annotated with @PyApplication should have a 'start' method.",
|
||||
"The class annotated with @Pybootlication should have a 'start' method.",
|
||||
True,
|
||||
)
|
||||
return jsonify(
|
||||
3
build/lib/pyboot/utils/__init__.py
Normal file
3
build/lib/pyboot/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Utility modules for
|
||||
"""
|
||||
@@ -1,7 +1,7 @@
|
||||
import time
|
||||
import json
|
||||
from pyapp.status import status_manager
|
||||
from pyapp.namespace import LogType
|
||||
from pyboot.status import status_manager
|
||||
from pyboot.namespace import LogType
|
||||
class Log:
|
||||
MAX_TITLE_LENGTH:int = 7
|
||||
TYPE_COLOR_MAP = {
|
||||
@@ -13,22 +13,28 @@ class Log:
|
||||
LogType.TERMINATE: "\033[96m"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_time():
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
|
||||
@staticmethod
|
||||
def blue(message):
|
||||
# blue
|
||||
print(f"\033[94m{message}\033[0m")
|
||||
@staticmethod
|
||||
def red(message):
|
||||
# red
|
||||
print(f"\033[91m{message}\033[0m")
|
||||
@staticmethod
|
||||
def yellow(message):
|
||||
# yellow
|
||||
print(f"\033[93m{message}\033[0m")
|
||||
@staticmethod
|
||||
def green(message):
|
||||
# green
|
||||
print(f"\033[92m{message}\033[0m")
|
||||
|
||||
@staticmethod
|
||||
def log(message, log_type: str):
|
||||
time_str = Log.get_time()
|
||||
space = ""
|
||||
@@ -42,28 +48,36 @@ class Log:
|
||||
print (f"\033[1m\033[4m({time_str})\033[0m \033[1m{Log.TYPE_COLOR_MAP[log_type]}[{log_type.capitalize()}]\033[0m{space} {Log.TYPE_COLOR_MAP[log_type]}{formatted_message}\033[0m")
|
||||
status_manager.add_log(time_str, log_type, formatted_message)
|
||||
|
||||
@staticmethod
|
||||
def bold(message):
|
||||
print(f"\033[1m{message}\033[0m")
|
||||
@staticmethod
|
||||
def underline(message):
|
||||
print(f"\033[4m{message}\033[0m")
|
||||
|
||||
@staticmethod
|
||||
def info(message):
|
||||
Log.log(message, LogType.INFO)
|
||||
|
||||
@staticmethod
|
||||
def error(message, terminate=False):
|
||||
Log.log(message, LogType.ERROR)
|
||||
if terminate:
|
||||
Log.terminate("Application Terminated.")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def warning(message):
|
||||
Log.log(message, LogType.WARNING)
|
||||
@staticmethod
|
||||
def success(message):
|
||||
Log.log(message, LogType.SUCCESS)
|
||||
|
||||
@staticmethod
|
||||
def debug(message):
|
||||
Log.log(message, LogType.DEBUG)
|
||||
|
||||
@staticmethod
|
||||
def terminate(message):
|
||||
Log.log(message, LogType.TERMINATE)
|
||||
exit(1)
|
||||
116
build/lib/pyboot/utils/project.py
Normal file
116
build/lib/pyboot/utils/project.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import importlib
|
||||
import fnmatch
|
||||
|
||||
class Project:
|
||||
@staticmethod
|
||||
def _load_pbignore(root_path):
|
||||
"""Load .pbignore file and return list of ignore patterns"""
|
||||
pbignore_path = os.path.join(root_path, '.pbignore')
|
||||
ignore_patterns = []
|
||||
|
||||
if os.path.exists(pbignore_path):
|
||||
try:
|
||||
with open(pbignore_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
# Skip empty lines and comments
|
||||
if line and not line.startswith('#'):
|
||||
ignore_patterns.append(line)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read .pbignore file: {e}")
|
||||
|
||||
return ignore_patterns
|
||||
|
||||
@staticmethod
|
||||
def _should_ignore(path, ignore_patterns, root_path):
|
||||
"""Check if a path should be ignored based on .pbignore patterns"""
|
||||
if not ignore_patterns:
|
||||
return False
|
||||
|
||||
# Get relative path from root
|
||||
try:
|
||||
rel_path = os.path.relpath(path, root_path)
|
||||
except ValueError:
|
||||
# If path is not relative to root, don't ignore
|
||||
return False
|
||||
|
||||
# Normalize path separators
|
||||
rel_path = rel_path.replace(os.sep, '/')
|
||||
|
||||
for pattern in ignore_patterns:
|
||||
# Handle directory patterns (ending with /)
|
||||
if pattern.endswith('/'):
|
||||
pattern = pattern[:-1]
|
||||
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(rel_path, pattern + '/*'):
|
||||
return True
|
||||
else:
|
||||
# Handle file/directory patterns
|
||||
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(os.path.basename(rel_path), pattern):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def scan_project(root_path):
|
||||
sys.path.append(root_path)
|
||||
if not os.path.exists(root_path) or not os.path.isdir(root_path):
|
||||
raise ValueError(f"The provided root_path '{root_path}' is not a valid directory.")
|
||||
|
||||
parent_dir = os.path.dirname(root_path)
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
# Load .pbignore patterns
|
||||
ignore_patterns = Project._load_pbignore(root_path)
|
||||
|
||||
def import_all_modules(path, package_name):
|
||||
for root, dirs, files in os.walk(path):
|
||||
# Check if current directory should be ignored
|
||||
if Project._should_ignore(root, ignore_patterns, root_path):
|
||||
dirs[:] = [] # Don't traverse into ignored directories
|
||||
continue
|
||||
|
||||
relative_path = os.path.relpath(root, root_path)
|
||||
if relative_path == '.':
|
||||
module_package = package_name
|
||||
else:
|
||||
module_package = f"{package_name}.{relative_path.replace(os.sep, '.')}"
|
||||
|
||||
for file in files:
|
||||
if file.endswith(".py") and file != "__init__.py":
|
||||
file_path = os.path.join(root, file)
|
||||
# Check if file should be ignored
|
||||
if Project._should_ignore(file_path, ignore_patterns, root_path):
|
||||
continue
|
||||
|
||||
module_name = file[:-3]
|
||||
full_module_name = f"{module_package}.{module_name}"
|
||||
if full_module_name not in sys.modules:
|
||||
importlib.import_module(full_module_name)
|
||||
|
||||
# Filter out ignored directories from further traversal
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.') and
|
||||
not Project._should_ignore(os.path.join(root, d), ignore_patterns, root_path)]
|
||||
|
||||
package_name = os.path.basename(root_path)
|
||||
import_all_modules(root_path, package_name)
|
||||
|
||||
@staticmethod
|
||||
def scan_configs(root_path):
|
||||
configs = {}
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
for file in files:
|
||||
if file.endswith(('.yaml', '.yml')):
|
||||
if file.startswith('__'):
|
||||
continue
|
||||
file_path = os.path.join(root, file)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
try:
|
||||
content = yaml.safe_load(f)
|
||||
configs[os.path.splitext(file)[0]] = content
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error reading {file_path}: {e}")
|
||||
|
||||
return configs
|
||||
@@ -1,9 +1,9 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: pyapp
|
||||
Metadata-Version: 2.4
|
||||
Name: pyboot
|
||||
Version: 0.1
|
||||
Summary: UNKNOWN
|
||||
License: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
|
||||
UNKNOWN
|
||||
|
||||
Requires-Dist: pyyaml
|
||||
Requires-Dist: psutil
|
||||
Requires-Dist: GPUtil
|
||||
Requires-Dist: flask
|
||||
Requires-Dist: flask_cors
|
||||
Dynamic: requires-dist
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
README.md
|
||||
setup.py
|
||||
pyapp/__init__.py
|
||||
pyapp/application.py
|
||||
pyapp/boot.py
|
||||
pyapp/component.py
|
||||
pyapp/component_factory.py
|
||||
pyapp/config.py
|
||||
pyapp/namespace.py
|
||||
pyapp/runner.py
|
||||
pyapp/status.py
|
||||
pyapp/stereotype.py
|
||||
pyapp.egg-info/PKG-INFO
|
||||
pyapp.egg-info/SOURCES.txt
|
||||
pyapp.egg-info/dependency_links.txt
|
||||
pyapp.egg-info/entry_points.txt
|
||||
pyapp.egg-info/requires.txt
|
||||
pyapp.egg-info/top_level.txt
|
||||
pyapp/templates/__init__.py
|
||||
pyapp/templates/application.py
|
||||
pyapp/templates/config.py
|
||||
pyapp/ui/__init__.py
|
||||
pyapp/ui/client/__init__.py
|
||||
pyapp/ui/client/index.html
|
||||
pyapp/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css
|
||||
pyapp/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css.map
|
||||
pyapp/ui/client/static/fonts/ionicons.143146f.woff2
|
||||
pyapp/ui/client/static/fonts/ionicons.99ac330.woff
|
||||
pyapp/ui/client/static/fonts/ionicons.d535a25.ttf
|
||||
pyapp/ui/client/static/img/ionicons.a2c4a26.svg
|
||||
pyapp/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js
|
||||
pyapp/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js.map
|
||||
pyapp/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js
|
||||
pyapp/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js.map
|
||||
pyapp/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js
|
||||
pyapp/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js.map
|
||||
pyapp/ui/server/__init__.py
|
||||
pyapp/ui/server/app.py
|
||||
pyapp/utils/__init__.py
|
||||
pyapp/utils/log.py
|
||||
pyapp/utils/project.py
|
||||
pyapp/utils/timer.py
|
||||
/__init__.py
|
||||
/application.py
|
||||
/boot.py
|
||||
/component.py
|
||||
/component_factory.py
|
||||
/config.py
|
||||
/namespace.py
|
||||
/runner.py
|
||||
/status.py
|
||||
/stereotype.py
|
||||
.egg-info/PKG-INFO
|
||||
.egg-info/SOURCES.txt
|
||||
.egg-info/dependency_links.txt
|
||||
.egg-info/entry_points.txt
|
||||
.egg-info/requires.txt
|
||||
.egg-info/top_level.txt
|
||||
/templates/__init__.py
|
||||
/templates/application.py
|
||||
/templates/config.py
|
||||
/ui/__init__.py
|
||||
/ui/client/__init__.py
|
||||
/ui/client/index.html
|
||||
/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css
|
||||
/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css.map
|
||||
/ui/client/static/fonts/ionicons.143146f.woff2
|
||||
/ui/client/static/fonts/ionicons.99ac330.woff
|
||||
/ui/client/static/fonts/ionicons.d535a25.ttf
|
||||
/ui/client/static/img/ionicons.a2c4a26.svg
|
||||
/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js
|
||||
/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js.map
|
||||
/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js
|
||||
/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js.map
|
||||
/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js
|
||||
/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js.map
|
||||
/ui/server/__init__.py
|
||||
/ui/server/app.py
|
||||
/utils/__init__.py
|
||||
/utils/log.py
|
||||
/utils/project.py
|
||||
/utils/timer.py
|
||||
@@ -1,2 +1,2 @@
|
||||
[console_scripts]
|
||||
pa = pyapp.boot:main
|
||||
pa = pyboot.boot:main
|
||||
|
||||
@@ -1 +1 @@
|
||||
pyapp
|
||||
pyboot
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Template modules for PyApp
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
UI modules for PyApp
|
||||
"""
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Client-side UI components for PyApp
|
||||
"""
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>PyApp Project</title><link href=/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.9f7b4785a30f0533ee08.js></script><script type=text/javascript src=/static/js/app.d8ab046c4c34bfc5bd54.js></script></body></html>
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Server-side UI components for PyApp
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,50 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import importlib
|
||||
|
||||
class Project:
|
||||
@staticmethod
|
||||
def scan_project(root_path):
|
||||
sys.path.append(root_path)
|
||||
if not os.path.exists(root_path) or not os.path.isdir(root_path):
|
||||
raise ValueError(f"The provided root_path '{root_path}' is not a valid directory.")
|
||||
|
||||
parent_dir = os.path.dirname(root_path)
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
def import_all_modules(path, package_name):
|
||||
for root, dirs, files in os.walk(path):
|
||||
relative_path = os.path.relpath(root, root_path)
|
||||
if relative_path == '.':
|
||||
module_package = package_name
|
||||
else:
|
||||
module_package = f"{package_name}.{relative_path.replace(os.sep, '.')}"
|
||||
for file in files:
|
||||
if file.endswith(".py") and file != "__init__.py":
|
||||
module_name = file[:-3]
|
||||
full_module_name = f"{module_package}.{module_name}"
|
||||
if full_module_name not in sys.modules:
|
||||
importlib.import_module(full_module_name)
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.')]
|
||||
|
||||
package_name = os.path.basename(root_path)
|
||||
import_all_modules(root_path, package_name)
|
||||
|
||||
@staticmethod
|
||||
def scan_configs(root_path):
|
||||
configs = {}
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
for file in files:
|
||||
if file.endswith(('.yaml', '.yml')):
|
||||
if file.startswith('__'):
|
||||
continue
|
||||
file_path = os.path.join(root, file)
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
try:
|
||||
content = yaml.safe_load(f)
|
||||
configs[os.path.splitext(file)[0]] = content
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error reading {file_path}: {e}")
|
||||
|
||||
return configs
|
||||
9
pyboot.egg-info/PKG-INFO
Normal file
9
pyboot.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,9 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: pyboot
|
||||
Version: 0.2
|
||||
Requires-Dist: pyyaml
|
||||
Requires-Dist: psutil
|
||||
Requires-Dist: GPUtil
|
||||
Requires-Dist: flask
|
||||
Requires-Dist: flask_cors
|
||||
Dynamic: requires-dist
|
||||
50
pyboot.egg-info/SOURCES.txt
Normal file
50
pyboot.egg-info/SOURCES.txt
Normal file
@@ -0,0 +1,50 @@
|
||||
.gitignore
|
||||
README.md
|
||||
setup.py
|
||||
__pycache__/setup.cpython-39.pyc
|
||||
pyapp.egg-info/PKG-INFO
|
||||
pyapp.egg-info/SOURCES.txt
|
||||
pyapp.egg-info/dependency_links.txt
|
||||
pyapp.egg-info/entry_points.txt
|
||||
pyapp.egg-info/requires.txt
|
||||
pyapp.egg-info/top_level.txt
|
||||
pyboot/__init__.py
|
||||
pyboot/application.py
|
||||
pyboot/boot.py
|
||||
pyboot/component.py
|
||||
pyboot/component_factory.py
|
||||
pyboot/config.py
|
||||
pyboot/namespace.py
|
||||
pyboot/runner.py
|
||||
pyboot/status.py
|
||||
pyboot/stereotype.py
|
||||
pyboot.egg-info/PKG-INFO
|
||||
pyboot.egg-info/SOURCES.txt
|
||||
pyboot.egg-info/dependency_links.txt
|
||||
pyboot.egg-info/entry_points.txt
|
||||
pyboot.egg-info/requires.txt
|
||||
pyboot.egg-info/top_level.txt
|
||||
pyboot/templates/__init__.py
|
||||
pyboot/templates/application.py
|
||||
pyboot/templates/config.py
|
||||
pyboot/ui/__init__.py
|
||||
pyboot/ui/client/__init__.py
|
||||
pyboot/ui/client/index.html
|
||||
pyboot/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css
|
||||
pyboot/ui/client/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css.map
|
||||
pyboot/ui/client/static/fonts/ionicons.143146f.woff2
|
||||
pyboot/ui/client/static/fonts/ionicons.99ac330.woff
|
||||
pyboot/ui/client/static/fonts/ionicons.d535a25.ttf
|
||||
pyboot/ui/client/static/img/ionicons.a2c4a26.svg
|
||||
pyboot/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js
|
||||
pyboot/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js.map
|
||||
pyboot/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js
|
||||
pyboot/ui/client/static/js/manifest.2ae2e69a05c33dfc65f8.js.map
|
||||
pyboot/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js
|
||||
pyboot/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js.map
|
||||
pyboot/ui/server/__init__.py
|
||||
pyboot/ui/server/app.py
|
||||
pyboot/utils/__init__.py
|
||||
pyboot/utils/log.py
|
||||
pyboot/utils/project.py
|
||||
pyboot/utils/timer.py
|
||||
1
pyboot.egg-info/dependency_links.txt
Normal file
1
pyboot.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
3
pyboot.egg-info/entry_points.txt
Normal file
3
pyboot.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
pb = pyboot.boot:main
|
||||
pyboot = pyboot.boot:main
|
||||
5
pyboot.egg-info/requires.txt
Normal file
5
pyboot.egg-info/requires.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
pyyaml
|
||||
psutil
|
||||
GPUtil
|
||||
flask
|
||||
flask_cors
|
||||
1
pyboot.egg-info/top_level.txt
Normal file
1
pyboot.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
pyboot
|
||||
6
pyboot/__init__.py
Normal file
6
pyboot/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
pyboot - A Python application framework
|
||||
"""
|
||||
|
||||
__version__ = "0.1"
|
||||
__author__ = "Hofee"
|
||||
21
pyboot/application.py
Normal file
21
pyboot/application.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
application_class = {}
|
||||
def PybootApplication(arg=None):
|
||||
if callable(arg):
|
||||
cls = arg
|
||||
if "default" in application_class:
|
||||
Log.error("Multiple classes annotated with default @PybootApplication, require a 'name' parameter.", True)
|
||||
application_class["default"] = cls
|
||||
return cls
|
||||
|
||||
else:
|
||||
name = arg
|
||||
def decorator(cls):
|
||||
if name is None:
|
||||
raise Log.error("The 'name' parameter is required when using @PybootApplication with arguments.", True)
|
||||
if name in application_class:
|
||||
raise Log.error(f"Multiple classes annotated with @PybootApplication with the same name '{name}' found.", True)
|
||||
application_class[name] = cls
|
||||
return cls
|
||||
return decorator
|
||||
110
pyboot/boot.py
Normal file
110
pyboot/boot.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pyboot.application import application_class
|
||||
from pyboot.stereotype import get_all_component_classes, get_all_component_comments
|
||||
from pyboot.utils.log import Log
|
||||
from pyboot.utils.timer import Timer
|
||||
from pyboot.utils.project import Project
|
||||
from pyboot.templates.application import template as app_template
|
||||
from pyboot.templates.config import template as config_template
|
||||
from pyboot.ui.server.app import app
|
||||
|
||||
|
||||
def run():
|
||||
root_path = os.getcwd()
|
||||
Project.scan_project(root_path)
|
||||
|
||||
app_name = "default"
|
||||
if len(application_class) == 0:
|
||||
Log.error("No class annotated with @PybootApplication found.", True)
|
||||
if len(sys.argv) < 3 and "default" not in application_class:
|
||||
Log.error("No default @PybootApplication found. Please specify the 'name' parameter.", True)
|
||||
if len(sys.argv) == 3:
|
||||
app_name = sys.argv[2]
|
||||
|
||||
app_cls = application_class.get(app_name)
|
||||
|
||||
if app_cls is None:
|
||||
Log.error(f"No class annotated with @PybootApplication found with the name '{app_name}'.", True)
|
||||
|
||||
if not hasattr(app_cls, "start"):
|
||||
Log.error("The class annotated with @PybootApplication should have a 'start' method.", True)
|
||||
|
||||
Log.info(f"Application '{app_cls.__name__}' started.")
|
||||
timer = Timer(f"Application({app_name})")
|
||||
|
||||
timer.start()
|
||||
app_cls.start()
|
||||
timer.stop()
|
||||
Log.info(timer.get_elasped_time_str(Timer.HOURS))
|
||||
Log.success("Application finished.")
|
||||
|
||||
|
||||
def init():
|
||||
Log.info("Initializing Pyboot project.")
|
||||
root_path = os.getcwd()
|
||||
if len(os.listdir(root_path)) > 0:
|
||||
Log.error("Current directory is not empty. Please provide an empty directory.")
|
||||
else:
|
||||
with open(os.path.join(root_path, "application.py"), "w") as file:
|
||||
file.write(app_template)
|
||||
with open(os.path.join(root_path, "config.yaml"), "w") as file:
|
||||
file.write(config_template)
|
||||
|
||||
Log.success("Pyboot project initialized.")
|
||||
Log.info("Now you can create your components and run the application.")
|
||||
|
||||
def scan():
|
||||
root_path = os.getcwd()
|
||||
Project.scan_project(root_path)
|
||||
comments = get_all_component_comments()
|
||||
Log.info("Components detected in the project:")
|
||||
for stereotype, classes in get_all_component_classes().items():
|
||||
if len(classes) == 0:
|
||||
continue
|
||||
Log.info(f" {stereotype}:")
|
||||
for name, cls in classes.items():
|
||||
comment = comments[stereotype].get(name)
|
||||
if comment is not None:
|
||||
Log.warning(f" - {name}: {cls.__module__}.{cls.__name__} ({comment})")
|
||||
else:
|
||||
Log.success(f" - {name}: {cls.__module__}.{cls.__name__}")
|
||||
|
||||
Log.info("Applications detected in the project:")
|
||||
for app_name, app_cls in application_class.items():
|
||||
Log.success(f" - {app_name}: {app_cls.__module__}.{app_cls.__name__}")
|
||||
Log.success("Scan completed.")
|
||||
|
||||
def ui():
|
||||
port = 5000
|
||||
if len(sys.argv) == 3:
|
||||
port = int(sys.argv[2])
|
||||
Log.success(f"Pyboot UI server started at http://localhost:{port}")
|
||||
app.run(port=port, host="0.0.0.0")
|
||||
|
||||
|
||||
def help():
|
||||
Log.info("Pyboot commands:")
|
||||
Log.info(" init: Initialize a new Pyboot project in the current directory.")
|
||||
Log.info(" run [name]: Run the Pyboot application with the specified name. If no name is provided, the default application will be run.")
|
||||
Log.info(" scan: Scan the project for Pyboot components.")
|
||||
Log.info(" ui [port]: Start the Pyboot UI server. If no port is provided, the default port 5000 will be used.")
|
||||
Log.info(" help: Display this help message.")
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "init":
|
||||
init()
|
||||
elif sys.argv[1] == "run":
|
||||
run()
|
||||
elif sys.argv[1] == "scan":
|
||||
scan()
|
||||
elif sys.argv[1] == "ui":
|
||||
ui()
|
||||
elif sys.argv[1] == "help":
|
||||
help()
|
||||
else:
|
||||
Log.error("Invalid command: " + sys.argv[1] + ". Use 'pyboot help' for help.")
|
||||
else:
|
||||
Log.error("Please provide a command to run the application.")
|
||||
21
pyboot/component.py
Normal file
21
pyboot/component.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class Component:
|
||||
TYPE: str
|
||||
NAME: str
|
||||
|
||||
def get_name(self):
|
||||
return self.NAME
|
||||
|
||||
def get_type(self):
|
||||
return self.TYPE
|
||||
|
||||
def get_config(self):
|
||||
return self.config
|
||||
|
||||
def print(self):
|
||||
Log.blue("Component Information")
|
||||
Log.blue(f"- Type: {self.TYPE}")
|
||||
Log.blue(f"- Name: {self.NAME}")
|
||||
Log.blue(f"- Config: \n\t{self.config}")
|
||||
|
||||
27
pyboot/component_factory.py
Normal file
27
pyboot/component_factory.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pyboot.component import Component
|
||||
from pyboot.stereotype import *
|
||||
from pyboot.utils.log import Log
|
||||
from pyboot.config import ConfigManager
|
||||
|
||||
class ComponentFactory:
|
||||
@staticmethod
|
||||
def create(component_type: str, name: str) -> Component:
|
||||
component_classes = get_component_classes(component_type=component_type)
|
||||
if component_classes is None:
|
||||
Log.error(f"Unsupported component type: {component_type}", True)
|
||||
|
||||
if component_type == "COMPONENT":
|
||||
config = ConfigManager.get(component_type, name)
|
||||
cls = component_classes[config["source"]]
|
||||
component_obj = cls(config)
|
||||
component_obj.NAME = name
|
||||
component_obj.TYPE = component_type
|
||||
return component_obj
|
||||
|
||||
if name not in component_classes:
|
||||
Log.error(f"Unsupported component name: {name}", True)
|
||||
|
||||
cls = component_classes[name]
|
||||
config = ConfigManager.get(component_type, name)
|
||||
return cls(config)
|
||||
|
||||
59
pyboot/config.py
Normal file
59
pyboot/config.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import argparse
|
||||
import os.path
|
||||
import shutil
|
||||
import yaml
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class ConfigManager:
|
||||
config = None
|
||||
config_path = None
|
||||
|
||||
@staticmethod
|
||||
def get(*args):
|
||||
result = ConfigManager.config
|
||||
for arg in args:
|
||||
result = result[arg]
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def load_config_with(config_file_path):
|
||||
ConfigManager.config_path = config_file_path
|
||||
if not os.path.exists(ConfigManager.config_path):
|
||||
raise ValueError(f"Config file <{config_file_path}> does not exist")
|
||||
with open(config_file_path, 'r') as file:
|
||||
ConfigManager.config = yaml.safe_load(file)
|
||||
|
||||
@staticmethod
|
||||
def backup_config_to(target_config_dir, file_name, prefix="config"):
|
||||
file_name = f"__{prefix}_{file_name}.yaml"
|
||||
target_config_file_path = str(os.path.join(target_config_dir, file_name))
|
||||
shutil.copy(ConfigManager.config_path, target_config_file_path)
|
||||
|
||||
@staticmethod
|
||||
def load_config():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--config', type=str, default='', help='config file path')
|
||||
args = parser.parse_args()
|
||||
if args.config:
|
||||
ConfigManager.load_config_with(args.config)
|
||||
|
||||
@staticmethod
|
||||
def print_config(key: str = None, group: dict = None, level=0):
|
||||
table_size = 80
|
||||
if key and group:
|
||||
value = group[key]
|
||||
if type(value) is dict:
|
||||
Log.blue("\t" * level + f"+-{key}:")
|
||||
for k in value:
|
||||
ConfigManager.print_config(k, value, level=level + 1)
|
||||
else:
|
||||
Log.blue("\t" * level + f"| {key}: {value}")
|
||||
elif key:
|
||||
ConfigManager.print_config(key, ConfigManager.config, level=level)
|
||||
else:
|
||||
Log.blue("+" + "-" * table_size + "+")
|
||||
Log.blue(f"| Configurations in <{ConfigManager.config_path}>:")
|
||||
Log.blue("+" + "-" * table_size + "+")
|
||||
for key in ConfigManager.config:
|
||||
ConfigManager.print_config(key, level=level + 1)
|
||||
Log.blue("+" + "-" * table_size + "+")
|
||||
15
pyboot/namespace.py
Normal file
15
pyboot/namespace.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
class Stereotype:
|
||||
APPLICATION:str = "application"
|
||||
RUNNER:str = "runner"
|
||||
FACTORY:str = "factory"
|
||||
SERVER:str = "server"
|
||||
CLIENT:str = "client"
|
||||
|
||||
class LogType:
|
||||
INFO:str = "info"
|
||||
ERROR:str = "error"
|
||||
WARNING:str = "warning"
|
||||
SUCCESS:str = "success"
|
||||
DEBUG:str = "debug"
|
||||
TERMINATE:str = "terminate"
|
||||
51
pyboot/runner.py
Normal file
51
pyboot/runner.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import time
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from pyboot.config import ConfigManager
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class Runner(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, config_path):
|
||||
ConfigManager.load_config_with(config_path)
|
||||
ConfigManager.print_config()
|
||||
self.config = ConfigManager.get("runner")
|
||||
self.workspace_config = self.config["workspace"]
|
||||
self.workspace_path = os.path.join(self.workspace_config["root_dir"], self.workspace_config["name"])
|
||||
lt = time.localtime()
|
||||
self.file_name = f"{lt.tm_year}_{lt.tm_mon}_{lt.tm_mday}_{lt.tm_hour}h{lt.tm_min}m{lt.tm_sec}s"
|
||||
self.load_workspace()
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def load_workspace(self, backup_name=None):
|
||||
if not os.path.exists(self.workspace_path):
|
||||
Log.info(f"workspace {self.workspace_config['name']} does not exists.")
|
||||
self.create_workspace(backup_name)
|
||||
else:
|
||||
Log.info(f"workspace {self.workspace_config['name']}")
|
||||
backup_config_dir = os.path.join(str(self.workspace_path), "configs")
|
||||
if not os.path.exists(backup_config_dir):
|
||||
os.makedirs(backup_config_dir)
|
||||
ConfigManager.backup_config_to(backup_config_dir, self.file_name, backup_name)
|
||||
|
||||
def create_workspace(self, backup_name=None):
|
||||
Log.info("creating workspace: " + self.workspace_config["name"])
|
||||
os.makedirs(self.workspace_path)
|
||||
backup_config_dir = os.path.join(str(self.workspace_path), "configs")
|
||||
os.makedirs(backup_config_dir)
|
||||
ConfigManager.backup_config_to(backup_config_dir, self.file_name, backup_name)
|
||||
log_dir = os.path.join(str(self.workspace_path), "logs")
|
||||
os.makedirs(log_dir)
|
||||
cache_dir = os.path.join(str(self.workspace_path), "cache")
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
def print_info(self):
|
||||
table_size = 80
|
||||
Log.blue("+" + "-" * table_size + "+")
|
||||
Log.blue(f"| Workspace <{self.workspace_config['name']}>")
|
||||
Log.blue("+" + "-" * table_size + "+")
|
||||
56
pyboot/status.py
Normal file
56
pyboot/status.py
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
class StatusManager:
|
||||
def __init__(self):
|
||||
self.running_app = {}
|
||||
self.last_status = {}
|
||||
self.curr_status = {}
|
||||
self.progress = {}
|
||||
self.log = []
|
||||
|
||||
def is_running(self):
|
||||
return len(self.running_app) > 0
|
||||
|
||||
def run_app(self, app_name, app):
|
||||
self.running_app[app_name] = app
|
||||
|
||||
def end_app(self, app_name):
|
||||
self.running_app.pop(app_name)
|
||||
|
||||
def set_status(self, app_name, runner_name, key, value):
|
||||
self.last_status = self.curr_status
|
||||
if app_name not in self.curr_status:
|
||||
self.curr_status[app_name] = {}
|
||||
if runner_name not in self.curr_status[app_name]:
|
||||
self.curr_status[app_name][runner_name] = {}
|
||||
self.curr_status[app_name][runner_name][key] = value
|
||||
|
||||
def set_progress(self, app_name, runner_name, key, curr_value, max_value):
|
||||
if app_name not in self.progress:
|
||||
self.progress[app_name] = {}
|
||||
if runner_name not in self.progress[app_name]:
|
||||
self.progress[app_name][runner_name] = {}
|
||||
self.progress[app_name][runner_name][key] = (curr_value, max_value)
|
||||
|
||||
def get_status(self):
|
||||
return self.curr_status
|
||||
|
||||
def get_progress(self):
|
||||
return self.progress
|
||||
|
||||
def add_log(self, time_str, log_type, message):
|
||||
self.log.append((time_str, log_type, message))
|
||||
|
||||
def get_log(self):
|
||||
return self.log
|
||||
|
||||
def get_running_apps(self):
|
||||
return list(self.running_app.keys())
|
||||
|
||||
def get_last_status(self):
|
||||
return self.last_status
|
||||
|
||||
def reset_status(self):
|
||||
self.last_status = {}
|
||||
self.curr_status = {}
|
||||
|
||||
status_manager = StatusManager()
|
||||
101
pyboot/stereotype.py
Normal file
101
pyboot/stereotype.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import inspect
|
||||
|
||||
from pyboot.component import Component
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
|
||||
def ensure_component_subclass(cls, type_name, name):
|
||||
if not issubclass(cls, Component):
|
||||
new_cls = type(cls.__name__, (Component, cls), {
|
||||
**cls.__dict__,
|
||||
"TYPE": type_name,
|
||||
"NAME": name
|
||||
})
|
||||
new_cls.__original_class__ = cls
|
||||
else:
|
||||
new_cls = cls
|
||||
for method_name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
|
||||
if getattr(method, "__isabstractmethod__", False):
|
||||
Log.error(f"Component <{name}> contains abstract method <{method_name}>.", True)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
all_component_classes = {}
|
||||
all_component_comments = {}
|
||||
|
||||
def register_component_type(component_type):
|
||||
all_component_classes[component_type] = {}
|
||||
all_component_comments[component_type] = {}
|
||||
|
||||
# --- Classes --- #
|
||||
|
||||
def component(component_type, component_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "COMPONENT", component_name)
|
||||
if component_type not in all_component_classes:
|
||||
register_component_type(component_type)
|
||||
all_component_classes[component_type][component_name] = cls
|
||||
all_component_comments[component_type][component_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
def runner(runner_name, comment=None):
|
||||
def decorator(cls):
|
||||
if not hasattr(cls, 'run') or not callable(getattr(cls, 'run')):
|
||||
Log.error(f"runner <{cls.__name__}> must implement a 'run' method", True)
|
||||
cls = ensure_component_subclass(cls, "RUNNER", runner_name)
|
||||
if "runner" not in all_component_classes:
|
||||
register_component_type("runner")
|
||||
all_component_classes["runner"][runner_name] = cls
|
||||
all_component_comments["runner"][runner_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
def factory(factory_name, comment=None):
|
||||
def decorator(cls):
|
||||
if not hasattr(cls, 'create') or not callable(getattr(cls, 'create')):
|
||||
Log.error(f"factory <{cls.__name__}> must implement a 'create' method", True)
|
||||
|
||||
cls = ensure_component_subclass(cls, "FACTORY", factory_name)
|
||||
if "factory" not in all_component_classes:
|
||||
register_component_type("factory")
|
||||
all_component_classes["factory"][factory_name] = cls
|
||||
all_component_comments["factory"][factory_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
def client(client_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "CLIENT", client_name)
|
||||
if "client" not in all_component_classes:
|
||||
register_component_type("client")
|
||||
all_component_classes["client"][client_name] = cls
|
||||
all_component_comments["client"][client_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
def server(server_name, comment=None):
|
||||
def decorator(cls):
|
||||
cls = ensure_component_subclass(cls, "SERVER", server_name)
|
||||
if not hasattr(cls, 'host') or not hasattr(cls, 'port'):
|
||||
Log.error(f"server <{cls.__name__}> must implement 'host' and 'port' attributes", True)
|
||||
if not hasattr(cls, 'serve') or not callable(getattr(cls, 'serve')):
|
||||
Log.error(f"server <{cls.__name__}> must implement a 'serve' method", True)
|
||||
if "server" not in all_component_classes:
|
||||
register_component_type("server")
|
||||
all_component_classes["server"][server_name] = cls
|
||||
all_component_comments["server"][server_name] = comment
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
# --- Utils --- #
|
||||
|
||||
def get_all_component_classes():
|
||||
return all_component_classes
|
||||
|
||||
def get_all_component_comments():
|
||||
return all_component_comments
|
||||
|
||||
def get_component_classes(component_type):
|
||||
return all_component_classes.get(component_type, None)
|
||||
3
pyboot/templates/__init__.py
Normal file
3
pyboot/templates/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Template modules for pyboot
|
||||
"""
|
||||
15
pyboot/templates/application.py
Normal file
15
pyboot/templates/application.py
Normal file
@@ -0,0 +1,15 @@
|
||||
template = """from pyboot.application import PybootApplication
|
||||
from runners import YourCustomRunner
|
||||
|
||||
@PybootApplication("your_application_name")
|
||||
class Application:
|
||||
@staticmethod
|
||||
def start():
|
||||
'''
|
||||
call default or your custom runners here, code will be executed
|
||||
automatically when type "pyboot run" or "pb run" in terminal
|
||||
|
||||
example:
|
||||
YourCustomRunner("path_to_your_config").run()
|
||||
'''
|
||||
"""
|
||||
9
pyboot/templates/config.py
Normal file
9
pyboot/templates/config.py
Normal file
@@ -0,0 +1,9 @@
|
||||
template = """
|
||||
runner:
|
||||
general:
|
||||
|
||||
workspace:
|
||||
name: workspace_name
|
||||
root_dir: "workspaces"
|
||||
|
||||
"""
|
||||
3
pyboot/ui/__init__.py
Normal file
3
pyboot/ui/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
UI modules for pyboot
|
||||
"""
|
||||
3
pyboot/ui/client/__init__.py
Normal file
3
pyboot/ui/client/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Client-side UI components for Pyboot
|
||||
"""
|
||||
1
pyboot/ui/client/index.html
Normal file
1
pyboot/ui/client/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pyboot Project</title><link href=/static/css/app.f0ad959581daf87e4158e4f8da1285ba.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.9f7b4785a30f0533ee08.js></script><script type=text/javascript src=/static/js/app.d8ab046c4c34bfc5bd54.js></script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
pyboot/ui/client/static/fonts/ionicons.143146f.woff2
Normal file
BIN
pyboot/ui/client/static/fonts/ionicons.143146f.woff2
Normal file
Binary file not shown.
BIN
pyboot/ui/client/static/fonts/ionicons.99ac330.woff
Normal file
BIN
pyboot/ui/client/static/fonts/ionicons.99ac330.woff
Normal file
Binary file not shown.
BIN
pyboot/ui/client/static/fonts/ionicons.d535a25.ttf
Normal file
BIN
pyboot/ui/client/static/fonts/ionicons.d535a25.ttf
Normal file
Binary file not shown.
870
pyboot/ui/client/static/img/ionicons.a2c4a26.svg
Normal file
870
pyboot/ui/client/static/img/ionicons.a2c4a26.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 542 KiB |
2
pyboot/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js
Normal file
2
pyboot/ui/client/static/js/app.d8ab046c4c34bfc5bd54.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
!function(r){var n=window.webpackJsonp;window.webpackJsonp=function(e,u,c){for(var f,i,p,a=0,l=[];a<e.length;a++)i=e[a],o[i]&&l.push(o[i][0]),o[i]=0;for(f in u)Object.prototype.hasOwnProperty.call(u,f)&&(r[f]=u[f]);for(n&&n(e,u,c);l.length;)l.shift()();if(c)for(a=0;a<c.length;a++)p=t(t.s=c[a]);return p};var e={},o={2:0};function t(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return r[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=r,t.c=e,t.d=function(r,n,e){t.o(r,n)||Object.defineProperty(r,n,{configurable:!1,enumerable:!0,get:e})},t.n=function(r){var n=r&&r.__esModule?function(){return r.default}:function(){return r};return t.d(n,"a",n),n},t.o=function(r,n){return Object.prototype.hasOwnProperty.call(r,n)},t.p="/",t.oe=function(r){throw console.error(r),r}}([]);
|
||||
//# sourceMappingURL=manifest.2ae2e69a05c33dfc65f8.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["webpack:///webpack/bootstrap 43a1e13c45f80b089c40"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 43a1e13c45f80b089c40"],"sourceRoot":""}
|
||||
91
pyboot/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js
Normal file
91
pyboot/ui/client/static/js/vendor.9f7b4785a30f0533ee08.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
pyboot/ui/server/__init__.py
Normal file
3
pyboot/ui/server/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Server-side UI components for Pyboot
|
||||
"""
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user