initial commit

This commit is contained in:
Bacruru Sakaguchi
2025-09-12 17:10:13 +07:00
commit 9e5e214944
57 changed files with 1538 additions and 0 deletions

79
pythonapp/Instance/ABS.py Normal file
View File

@@ -0,0 +1,79 @@
from dataclasses import dataclass
from pathlib import Path
from pythonapp.Libs.ConfigDataClass import Config
class InstanceFileNaming:
config_dir = '.vaiola'
main_config = 'instance.json'
requirements_dir = 'requirements.d'
manual_requirements = 'requirements.txt'
pinned_packages = 'pinned.txt'
excluded_packages = 'excluded.txt'
env_dir = 'env'
app_dir = 'app'
@dataclass
class InstanceConfig(Config):
instance_type: str = 'basic'
config_dir_rel_path: Path = Path(InstanceFileNaming.config_dir)
env_path: Path = Path(InstanceFileNaming.env_dir)
env_type: str = None
requirements_dir: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.requirements_dir
manual_requirements_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.manual_requirements
pinned_packages_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.pinned_packages
excluded_packages_path: Path = Path(InstanceFileNaming.config_dir) / InstanceFileNaming.excluded_packages
created: bool = False
app: Path = Path(InstanceFileNaming.app_dir)
app_installed: bool = None
app_extensions_dir: Path = None
app_models_dir: Path = None
app_output_dir: Path = None
app_input_dir: Path = None
app_user_dir: Path = None
class Instance:
def __init__(self):
self.path: Path | None = None
self.config: InstanceConfig | None = None
def insert_component_reqs(self, name: str, req_file: str | Path):
try:
with open('req_file', 'r') as file:
lines = [line.strip() for line in file]
req = []
opt = []
req_bool = True
for line in lines:
if line.startswith("#"):
req_bool = False
if req_bool: req.append(line)
else: opt.append(line)
with open(self.path / self.config.requirements_dir / (name + '.req'), 'w') as file:
for line in req: file.write(line + '\n')
with open(self.path / self.config.requirements_dir / (name + '.opt'), 'w') as file:
for line in opt: file.write(line + '\n')
except FileNotFoundError:
raise RuntimeError(f"Cannot update requirements for {name}, file {req_file} not exists")
raise NotImplemented
def install_git_app(self, url: str, requirements_file_in_app_dir = 'requirements.txt',
extensions_dir: str = None,
models_dir: str = None,
output_dir: str = None,
input_dir: str = None,
user_dir: str = None,
):
raise NotImplemented
def install_reqs(self, name, req_file: Path):
raise NotImplemented
def create(self):
raise NotImplemented
def install_packages(self, pkgs: list, repo, extra, pin=False):
raise NotImplemented

View File

@@ -0,0 +1,144 @@
import os.path
import shutil
import tempfile
from pythonapp.Decider.ABS import SimpleDecider
from pythonapp.Env.Venv import Venv, Env
from pythonapp.Env.StandaloneEnv import StandalonePythonEnv
from pythonapp.Libs.git import git
from pythonapp.Decider.misc import *
from pythonapp.Libs.pip_api import pip_api
from .ABS import InstanceConfig, InstanceFileNaming, Instance as ABSInstance
from ..Decider.Loader import Loader
class Instance(ABSInstance):
def __init__(self, path: str, env: str = 'venv', python = 'python3'):
self.path = Path(path)
self.config = InstanceConfig(os.path.join(self.path, InstanceFileNaming.main_config))
self.config.env_type = self.config.env_type or env
self.config.created = (Path(self.path) / self.config.config_dir_rel_path).exists()
if self.config.env_type == 'venv':
self.env: Env = Venv(str(self.path / self.config.env_path), python)
elif self.config.env_type == 'standalone':
self.env: Env = StandalonePythonEnv(str(self.path / self.config.env_path), python)
def insert_component_reqs(self, name: str, req_file: str | Path):
try:
req, opt = requirements_separator(req_file)
os.makedirs(self.path / self.config.requirements_dir, exist_ok=True)
with open(self.path / self.config.requirements_dir / (name + '.req'), 'w') as file:
for line in req: file.write(line + '\n')
with open(self.path / self.config.requirements_dir / (name + '.opt'), 'w') as file:
for line in opt: file.write(line + '\n')
except FileNotFoundError:
raise RuntimeError(f"Cannot update requirements for {name}, file {req_file} not exists")
def _symlink_move_dir(self, orig_dir: Path, dest: Path, obj_name: Path):
cwd = os.getcwd()
os.chdir(self.path)
shutil.move(orig_dir / obj_name, dest)
os.symlink(os.path.relpath(orig_dir, dest), orig_dir / obj_name)
os.chdir(orig_dir)
with open(".gitignore", "a") as file:
file.write(str(obj_name) + '\n')
os.chdir(cwd)
def install_git_app(self, url: str, requirements_file_in_app_dir = 'requirements.txt',
extensions_dir: str = None,
models_dir: str = None,
output_dir: str = None,
input_dir: str = None,
user_dir: str = None,
):
if os.path.exists(self.config.app):
raise RuntimeError("App installed previously. Multiapp instances is not supported")
with tempfile.TemporaryDirectory() as tmp_dir:
git.clone(url, tmp_dir)
try:
self.install_reqs('app', Path(tmp_dir) / requirements_file_in_app_dir)
except RuntimeError as e: raise TypeError(e)
self.config.app_extensions_dir = Path(extensions_dir) if extensions_dir else None
self.config.app_models_dir = Path(models_dir) if models_dir else None
self.config.app_output_dir = Path(output_dir) if output_dir else None
self.config.app_input_dir = Path(input_dir) if input_dir else None
self.config.app_user_dir = Path(user_dir) if user_dir else None
self.config.save()
git.clone(url, self.path / self.config.app)
if self.config.app_extensions_dir:
self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_extensions_dir)),
Path(os.path.basename(self.config.app / self.config.app_extensions_dir)),
Path(os.path.basename(self.config.app / self.config.app_extensions_dir)))
if self.config.app_models_dir:
self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_models_dir)),
Path(os.path.basename(self.config.app / self.config.app_models_dir)),
Path(os.path.basename(self.config.app / self.config.app_models_dir)))
if self.config.app_output_dir:
self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_output_dir)),
Path(os.path.basename(self.config.app / self.config.app_output_dir)),
Path(os.path.basename(self.config.app / self.config.app_output_dir)))
if self.config.app_input_dir:
self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_input_dir)),
Path(os.path.basename(self.config.app / self.config.app_input_dir)),
Path(os.path.basename(self.config.app / self.config.app_input_dir)))
if self.config.app_user_dir:
self._symlink_move_dir(Path(os.path.dirname(self.config.app / self.config.app_user_dir)),
Path(os.path.basename(self.config.app / self.config.app_user_dir)),
Path(os.path.basename(self.config.app / self.config.app_user_dir)))
def install_reqs(self, name, req_file: Path):
packages, errors, state = SimpleDecider.decide(self, req_file, self.env.pip_path)
if not packages: raise RuntimeError("Cannot install packages due conflicts")
self.env.install_pkgs([p.requirement_str for p in packages])
self.insert_component_reqs(name, req_file)
def create(self):
os.makedirs(self.path / self.config.config_dir_rel_path, exist_ok=True)
self.config.save()
self.env.create()
def install_packages(self, pkgs: list, repo: str = None, extra: str = None, pin=False):
packages = [RequirementInfo.from_requirement_string(p, 'manual', 'manual') for p in pkgs]
packages, errors, state = SimpleDecider.decide(self, packages, self.env.pip_path, manual=True)
pkgs = [p.requirement_str for p in packages]
print("manually install packages", pkgs)
self.env.install_pkgs(pkgs, repo, extra)
for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.manual_requirements_path)
if pin:
for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.pinned_packages_path)
def exclude_packages(self, pkgs: list[str]):
for pkg in pkgs: self._write_unique_string(pkg, file_path=self.path / self.config.excluded_packages_path)
def _write_unique_string(self, string, file_path):
existing_lines = set()
if file_path.exists():
with open(file_path, 'r') as file:
existing_lines = set(line.strip() for line in file)
with open(file_path, 'a') as file:
if string not in existing_lines:
file.write(string + '\n')
def _delete_string(self, string, file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
filtered_lines = [line for line in lines if line.rstrip('\n') != string]
with open(file_path, 'w') as file:
file.writelines(filtered_lines)

View File

Binary file not shown.