From 0a6a17acf6f87c074150de793f4cc95694d944d0 Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 9 Oct 2025 17:17:39 +0200 Subject: [PATCH 01/22] Added cpl-cli --- cpl.workspace.json | 11 ++ src/cpl-cli/cpl.project.json | 21 +++ src/cpl-cli/cpl/cli/__init__.py | 0 src/cpl-cli/cpl/cli/cli.py | 33 ++++ src/cpl-cli/cpl/cli/command/__init__.py | 0 src/cpl-cli/cpl/cli/command/init.py | 86 +++++++++ src/cpl-cli/cpl/cli/command/install.py | 41 ++++ src/cpl-cli/cpl/cli/command/uninstall.py | 39 ++++ src/cpl-cli/cpl/cli/const.py | 2 + src/cpl-cli/cpl/cli/main.py | 66 +++++++ src/cpl-cli/cpl/cli/model/__init__.py | 0 .../cpl/cli/model/cpl_structure_model.py | 131 +++++++++++++ src/cpl-cli/cpl/cli/model/project.py | 178 ++++++++++++++++++ src/cpl-cli/cpl/cli/model/workspace.py | 62 ++++++ src/cpl-cli/cpl/cli/utils/__init__.py | 0 src/cpl-cli/cpl/cli/utils/custom_command.py | 18 ++ src/cpl-cli/cpl/cli/utils/json.py | 14 ++ src/cpl-cli/cpl/cli/utils/pip.py | 50 +++++ src/cpl-cli/cpl/cli/utils/prompt.py | 20 ++ src/cpl-cli/cpl/cli/utils/structure.py | 30 +++ src/cpl-cli/cpl/cli/utils/venv.py | 44 +++++ src/cpl-cli/pyproject.toml | 29 +++ src/cpl-cli/requirements.dev.txt | 1 + src/cpl-cli/requirements.txt | 1 + src/cpl-core/cpl.project.json | 24 +++ 25 files changed, 901 insertions(+) create mode 100644 cpl.workspace.json create mode 100644 src/cpl-cli/cpl.project.json create mode 100644 src/cpl-cli/cpl/cli/__init__.py create mode 100644 src/cpl-cli/cpl/cli/cli.py create mode 100644 src/cpl-cli/cpl/cli/command/__init__.py create mode 100644 src/cpl-cli/cpl/cli/command/init.py create mode 100644 src/cpl-cli/cpl/cli/command/install.py create mode 100644 src/cpl-cli/cpl/cli/command/uninstall.py create mode 100644 src/cpl-cli/cpl/cli/const.py create mode 100644 src/cpl-cli/cpl/cli/main.py create mode 100644 src/cpl-cli/cpl/cli/model/__init__.py create mode 100644 src/cpl-cli/cpl/cli/model/cpl_structure_model.py create mode 100644 src/cpl-cli/cpl/cli/model/project.py create mode 100644 src/cpl-cli/cpl/cli/model/workspace.py create mode 100644 src/cpl-cli/cpl/cli/utils/__init__.py create mode 100644 src/cpl-cli/cpl/cli/utils/custom_command.py create mode 100644 src/cpl-cli/cpl/cli/utils/json.py create mode 100644 src/cpl-cli/cpl/cli/utils/pip.py create mode 100644 src/cpl-cli/cpl/cli/utils/prompt.py create mode 100644 src/cpl-cli/cpl/cli/utils/structure.py create mode 100644 src/cpl-cli/cpl/cli/utils/venv.py create mode 100644 src/cpl-cli/pyproject.toml create mode 100644 src/cpl-cli/requirements.dev.txt create mode 100644 src/cpl-cli/requirements.txt create mode 100644 src/cpl-core/cpl.project.json diff --git a/cpl.workspace.json b/cpl.workspace.json new file mode 100644 index 00000000..885653e9 --- /dev/null +++ b/cpl.workspace.json @@ -0,0 +1,11 @@ +{ + "name": "cpl", + "projects": [ + "src/cpl-core/cpl.project.json", + "src/cpl-cli/cpl.project.json" + ], + "defaultProject": "cpl-cli", + "scripts": { + "format": "black src" + } +} \ No newline at end of file diff --git a/src/cpl-cli/cpl.project.json b/src/cpl-cli/cpl.project.json new file mode 100644 index 00000000..36b88951 --- /dev/null +++ b/src/cpl-cli/cpl.project.json @@ -0,0 +1,21 @@ +{ + "name": "cpl-cli", + "version": "0.1.0", + "type": "console", + "license": "MIT", + "author": "Sven Heidemann", + "description": "CLI for the CPL library", + "homepage": "", + "keywords": [], + "dependencies": { + "click": "~8.3.0" + }, + "devDependencies": { + "black": "25.1.0" + }, + "references": [ + "../cpl/cpl.project.json" + ], + "main": "cpl/cli/main.py", + "directory": "cpl/cli" +} \ No newline at end of file diff --git a/src/cpl-cli/cpl/cli/__init__.py b/src/cpl-cli/cpl/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl-cli/cpl/cli/cli.py b/src/cpl-cli/cpl/cli/cli.py new file mode 100644 index 00000000..66d633fc --- /dev/null +++ b/src/cpl-cli/cpl/cli/cli.py @@ -0,0 +1,33 @@ +import click + + +class AliasedGroup(click.Group): + def command(self, *args, **kwargs): + aliases = kwargs.pop("aliases", []) + + def decorator(f): + cmd = super(AliasedGroup, self).command(*args, **kwargs)(f) + + for alias in aliases: + self.add_command(cmd, alias) + return cmd + + return decorator + + def format_commands(self, ctx, formatter): + commands = [] + seen = set() + for name, cmd in self.commands.items(): + if cmd in seen: + continue + seen.add(cmd) + aliases = [a for a, c in self.commands.items() if c is cmd and a != name] + alias_text = f" (aliases: {', '.join(aliases)})" if aliases else "" + commands.append((name, f"{cmd.short_help or ''}{alias_text}")) + + with formatter.section("Commands"): + formatter.write_dl(commands) + + +@click.group(cls=AliasedGroup) +def cli(): ... diff --git a/src/cpl-cli/cpl/cli/command/__init__.py b/src/cpl-cli/cpl/cli/command/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl-cli/cpl/cli/command/init.py b/src/cpl-cli/cpl/cli/command/init.py new file mode 100644 index 00000000..ff4bc7cd --- /dev/null +++ b/src/cpl-cli/cpl/cli/command/init.py @@ -0,0 +1,86 @@ +from pathlib import Path + +import click + +from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.prompt import SmartChoice +from cpl.core.console import Console + + +@click.command("init") +@click.argument("target", required=False) +@click.argument("name", required=False) +def init(target: str, name: str): + if target is None: + Console.write_line("CPL Init Wizard") + target = click.prompt( + "What do you want to initialize?", + type=SmartChoice(["workspace"] + PROJECT_TYPES, {"workspace": "ws"}), + show_choices=True, + ) + + if target in PROJECT_TYPES_SHORT: + target = [pt for pt in PROJECT_TYPES if pt.startswith(target)][0] + + if target in ["workspace", "ws"]: + _init_workspace(name or click.prompt("Workspace name", default="my-workspace")) + elif target in PROJECT_TYPES: + _init_project(name or click.prompt("Project name", default=f"my-{target}"), target) + else: + Console.error(f"Unknown target '{target}'") + raise SystemExit(1) + + +def _init_workspace(name: str): + path = Path("cpl.workspace.json") + + if path.exists(): + Console.write_line("workspace.json already exists.") + return + + data = { + "name": name, + "projects": [], + "defaultProject": None, + "scripts": {}, + } + workspace = Workspace.new("./", name) + workspace.save() + + Console.write_line(f"Created workspace '{name}'") + + +def _init_project(name: str, project_type: str): + project = Project.new("./", name, project_type) + + path = Path("cpl.project.json") + if path.exists(): + Console.write_line("cpl.project.json already exists.") + return + + if not Path("src").exists(): + project.directory = click.prompt( + "Project directory", type=click.Path(exists=True, file_okay=False), default="src" + ) + + if project_type in ["console", "web", "service"]: + project.main = click.prompt( + "Main executable", type=click.Path(exists=True, dir_okay=False), default="src/main.py" + ) + + project.save() + + workspace_file = Path("../cpl.workspace.json") + if workspace_file.exists(): + workspace = Workspace.from_file(workspace_file) + + rel_path = str(Path.cwd().relative_to(workspace_file.parent)) + if rel_path not in workspace.projects: + workspace.projects.append(rel_path) + workspace.save() + + Console.write_line(f"Registered '{name}' in workspace.json") + + Console.write_line(f"Created {project_type} project '{name}'") diff --git a/src/cpl-cli/cpl/cli/command/install.py b/src/cpl-cli/cpl/cli/command/install.py new file mode 100644 index 00000000..807b2c4f --- /dev/null +++ b/src/cpl-cli/cpl/cli/command/install.py @@ -0,0 +1,41 @@ +import subprocess +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.pip import Pip +from cpl.cli.utils.structure import resolve_project +from cpl.core.console import Console + + +@cli.command("install", aliases=["i"]) +@click.option( + "--path", + "project_path", + type=click.Path(file_okay=False, exists=False), + default=".", + help="Path to project directory", +) +@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.option("--dev", is_flag=True, help="Include dev dependencies") +@click.option("--verbose", is_flag=True, help="Enable verbose output") +def install(project_path: str, project_name: str, dev: bool, verbose: bool): + project = resolve_project(Path(project_path), project_name) + deps: dict = project.dependencies + if dev: + deps.update(project.dev_dependencies) + + if not deps: + Console.error("No dependencies to install.") + return + + Console.write_line(f"Installing dependencies for '{project.name}':") + + for name, version in deps.items(): + dep = Pip.normalize_dep(name, version) + Console.write_line(f" -> {dep}") + try: + Pip.command(project_path, "install", dep, verbose=verbose) + except subprocess.CalledProcessError as e: + Console.error(f"Failed to install {dep}: exit code {e.returncode}") diff --git a/src/cpl-cli/cpl/cli/command/uninstall.py b/src/cpl-cli/cpl/cli/command/uninstall.py new file mode 100644 index 00000000..a2a7bfe1 --- /dev/null +++ b/src/cpl-cli/cpl/cli/command/uninstall.py @@ -0,0 +1,39 @@ +import subprocess +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.pip import Pip +from cpl.cli.utils.structure import resolve_project +from cpl.core.console import Console + + +@cli.command("uninstall", aliases=["ui"]) +@click.argument("package", required=False) +@click.option( + "--path", + "project_path", + type=click.Path(file_okay=False, exists=False), + default=".", + help="Path to project directory", +) +@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.option("--dev", is_flag=True, help="Include dev dependencies") +@click.option("--verbose", is_flag=True, help="Enable verbose output") +def uninstall(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): + if package is None: + package = Console.read("Package name to uninstall: ").strip() + + project = resolve_project(Path(project_path), project_name) + deps = project.dependencies if not dev else project.dev_dependencies + + try: + Pip.command(project_path, "install", package, verbose=verbose) + except subprocess.CalledProcessError as e: + Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") + + if package in deps: + del deps[package] + project.save() + Console.write_line(f"Removed {package} from project dependencies.") diff --git a/src/cpl-cli/cpl/cli/const.py b/src/cpl-cli/cpl/cli/const.py new file mode 100644 index 00000000..a5810f03 --- /dev/null +++ b/src/cpl-cli/cpl/cli/const.py @@ -0,0 +1,2 @@ +PROJECT_TYPES = ["console", "web", "library", "service"] +PROJECT_TYPES_SHORT = [x[0] for x in PROJECT_TYPES] diff --git a/src/cpl-cli/cpl/cli/main.py b/src/cpl-cli/cpl/cli/main.py new file mode 100644 index 00000000..3d8f2bea --- /dev/null +++ b/src/cpl-cli/cpl/cli/main.py @@ -0,0 +1,66 @@ +import os +from pathlib import Path + +from cpl.cli.cli import cli +from cpl.cli.command.init import init +from cpl.cli.command.install import install +from cpl.cli.command.uninstall import uninstall +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.custom_command import script_command +from cpl.core.configuration import Configuration + + +def _load_workspace(path: str) -> Workspace | None: + path = Path(path) + if not path.exists() or path.is_dir(): + return None + + return Workspace.from_file(path) + + +def _load_scripts(): + for p in [ + "./cpl.workspace.json", + "../cpl.workspace.json", + "../../cpl.workspace.json", + ]: + ws = _load_workspace(p) + if ws is None: + continue + + Configuration.set("workspace_path", os.path.abspath(p)) + return ws.scripts + + return {} + + +def prepare(): + scripts = _load_scripts() + for name, command in scripts.items(): + script_command(cli, name, command) + + +def configure(): + cli.add_command(init) + cli.add_command(install) + cli.add_command(uninstall) + + +def main(): + prepare() + configure() + cli() + + +if __name__ == "__main__": + main() + +# (( +# ( `) +# ; / , +# / \/ +# / | +# / ~/ +# / ) ) ~ edraft +# ___// | / +# `--' \_~-, diff --git a/src/cpl-cli/cpl/cli/model/__init__.py b/src/cpl-cli/cpl/cli/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl-cli/cpl/cli/model/cpl_structure_model.py b/src/cpl-cli/cpl/cli/model/cpl_structure_model.py new file mode 100644 index 00000000..c9aa02f4 --- /dev/null +++ b/src/cpl-cli/cpl/cli/model/cpl_structure_model.py @@ -0,0 +1,131 @@ +import inspect +import json +from pathlib import Path +from typing import Any, Dict, List, Optional, Type, TypeVar + +T = TypeVar("T", bound="CPLStructureModel") + + +class CPLStructureModel: + def __init__(self, path: Optional[str] = None): + self._path = path + + @property + def path(self) -> Optional[str]: + return self._path + + @classmethod + def from_file(cls: Type[T], path: Path | str) -> T: + if isinstance(path, str): + path = Path(path) + + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + return cls.from_json(data, path=path) + + @classmethod + def from_json(cls: Type[T], data: Dict[str, Any], path: Optional[Path | str] = None) -> T: + if isinstance(path, str): + path = Path(path) + + sig = inspect.signature(cls.__init__) + kwargs: Dict[str, Any] = {} + for name, param in list(sig.parameters.items())[1:]: + if name == "path": + kwargs[name] = path + continue + + if name in data: + kwargs[name] = data[name] + continue + + priv = "_" + name + if priv in data: + kwargs[name] = data[priv] + continue + + camel = _self_or_cls_snake_to_camel(name) + if camel in data: + kwargs[name] = data[camel] + continue + + if param.default is not inspect._empty: + kwargs[name] = param.default + continue + + raise KeyError(f"Missing required field '{name}' for {cls.__name__}.") + + return cls(**kwargs) + + def to_json(self) -> Dict[str, Any]: + result: Dict[str, Any] = {} + for key, value in self.__dict__.items(): + if not key.startswith("_") or key == "_path": + continue + out_key = _self_or_cls_snake_to_camel(key[1:]) + result[out_key] = value + return result + + def save(self): + if not self._path: + raise ValueError("Cannot save model without a path.") + + with open(self._path, "w", encoding="utf-8") as f: + json.dump(self.to_json(), f, indent=2) + + @staticmethod + def _require_str(value, field: str, allow_empty: bool = True) -> str: + if not isinstance(value, str): + raise TypeError(f"{field} must be of type str") + if not allow_empty and not value.strip(): + raise ValueError(f"{field} must not be empty") + return value + + @staticmethod + def _require_optional_non_empty_str(value, field: str) -> Optional[str]: + if value is None: + return None + if not isinstance(value, str): + raise TypeError(f"{field} must be str or None") + s = value.strip() + if not s: + raise ValueError(f"{field} must not be empty when set") + return s + + @staticmethod + def _require_list_of_str(value, field: str) -> List[str]: + if not isinstance(value, list): + raise TypeError(f"{field} must be a list") + out: List[str] = [] + for i, v in enumerate(value): + if not isinstance(v, str): + raise TypeError(f"{field}[{i}] must be of type str") + s = v.strip() + if s: + out.append(s) + + seen = set() + uniq: List[str] = [] + for s in out: + if s not in seen: + seen.add(s) + uniq.append(s) + return uniq + + @staticmethod + def _require_dict_str_str(value, field: str) -> Dict[str, str]: + if not isinstance(value, dict): + raise TypeError(f"{field} must be a dict") + out: Dict[str, str] = {} + for k, v in value.items(): + if not isinstance(k, str) or not k.strip(): + raise TypeError(f"Keys in {field} must be non-empty strings") + if not isinstance(v, str) or not v.strip(): + raise TypeError(f"Values in {field} must be non-empty strings") + out[k.strip()] = v.strip() + return out + + +def _self_or_cls_snake_to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:]) diff --git a/src/cpl-cli/cpl/cli/model/project.py b/src/cpl-cli/cpl/cli/model/project.py new file mode 100644 index 00000000..1944a48f --- /dev/null +++ b/src/cpl-cli/cpl/cli/model/project.py @@ -0,0 +1,178 @@ +import re +from typing import Optional, List, Dict +from urllib.parse import urlparse + +from cpl.cli.model.cpl_structure_model import CPLStructureModel + + +class Project(CPLStructureModel): + _ALLOWED_TYPES = {"application", "library"} + _SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$") + + @staticmethod + def new(path: str, name: str, project_type: str) -> "Project": + return Project( + path, + name, + "0.1.0", + project_type, + "", + "", + "", + "", + [], + {}, + {}, + [], + None, + "src", + ) + + def __init__( + self, + path: str, + name: str, + version: str, + type: str, + license: str, + author: str, + description: str, + homepage: str, + keywords: List[str], + dependencies: Dict[str, str], + dev_dependencies: Dict[str, str], + references: List[str], + main: Optional[str], + directory: str, + ): + CPLStructureModel.__init__(self, path) + + self._name = name + self._version = version + self._type = type + self._license = license + self._author = author + self._description = description + self._homepage = homepage + self._keywords = keywords + self._dependencies = dependencies + self._dev_dependencies = dev_dependencies + self._references = references + self._main = main + self._directory = directory + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = self._require_str(value, "name", allow_empty=False).strip() + + @property + def version(self) -> str: + return self._version + + @version.setter + def version(self, value: str): + value = self._require_str(value, "version", allow_empty=False).strip() + if not self._SEMVER_RE.match(value): + raise ValueError("version must follow SemVer X.Y.Z (optionally with -/+)") + self._version = value + + @property + def type(self) -> str: + return self._type + + @type.setter + def type(self, value: str): + value = self._require_str(value, "type", allow_empty=False).strip() + if value not in self._ALLOWED_TYPES: + allowed = ", ".join(sorted(self._ALLOWED_TYPES)) + raise ValueError(f"type must be one of: {allowed}") + self._type = value + + @property + def license(self) -> str: + return self._license + + @license.setter + def license(self, value: str): + self._license = self._require_str(value, "license", allow_empty=True).strip() + + @property + def author(self) -> str: + return self._author + + @author.setter + def author(self, value: str): + self._author = self._require_str(value, "author", allow_empty=True).strip() + + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, value: str): + self._description = self._require_str(value, "description", allow_empty=True).strip() + + @property + def homepage(self) -> str: + return self._homepage + + @homepage.setter + def homepage(self, value: str): + value = self._require_str(value, "homepage", allow_empty=True).strip() + if value: + parsed = urlparse(value) + if parsed.scheme not in ("http", "https") or not parsed.netloc: + raise ValueError("homepage must be a valid http/https URL") + self._homepage = value + + @property + def keywords(self) -> List[str]: + return self._keywords + + @keywords.setter + def keywords(self, value: List[str]): + self._keywords = self._require_list_of_str(value, "keywords") + + @property + def dependencies(self) -> Dict[str, str]: + return self._dependencies + + @dependencies.setter + def dependencies(self, value: Dict[str, str]): + self._dependencies = self._require_dict_str_str(value, "dependencies") + + @property + def dev_dependencies(self) -> Dict[str, str]: + return self._dev_dependencies + + @dev_dependencies.setter + def dev_dependencies(self, value: Dict[str, str]): + self._dev_dependencies = self._require_dict_str_str(value, "devDependencies") + + @property + def references(self) -> List[str]: + return self._references + + @references.setter + def references(self, value: List[str]): + self._references = self._require_list_of_str(value, "references") + + @property + def main(self) -> Optional[str]: + return self._main + + @main.setter + def main(self, value: Optional[str]): + self._main = self._require_optional_non_empty_str(value, "main") + + @property + def directory(self) -> str: + return self._directory + + @directory.setter + def directory(self, value: str): + self._directory = self._require_str(value, "directory", allow_empty=False).strip() diff --git a/src/cpl-cli/cpl/cli/model/workspace.py b/src/cpl-cli/cpl/cli/model/workspace.py new file mode 100644 index 00000000..842ec47c --- /dev/null +++ b/src/cpl-cli/cpl/cli/model/workspace.py @@ -0,0 +1,62 @@ +from typing import Optional, List, Dict + +from cpl.cli.model.cpl_structure_model import CPLStructureModel + + +class Workspace(CPLStructureModel): + @staticmethod + def new(path: str, name: str) -> "Workspace": + return Workspace( + path=path, + name=name, + projects=[], + default_project=None, + scripts={}, + ) + + def __init__( + self, + path: str, + name: str, + projects: List[str], + default_project: Optional[str], + scripts: Dict[str, str], + ): + CPLStructureModel.__init__(self, path) + + self._name = name + self._projects = projects + self._default_project = default_project + self._scripts = scripts + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = self._require_str(value, "name", allow_empty=False).strip() + + @property + def projects(self) -> List[str]: + return self._projects + + @projects.setter + def projects(self, value: List[str]): + self._projects = self._require_list_of_str(value, "projects") + + @property + def default_project(self) -> Optional[str]: + return self._default_project + + @default_project.setter + def default_project(self, value: Optional[str]): + self._default_project = self._require_optional_non_empty_str(value, "defaultProject") + + @property + def scripts(self) -> Dict[str, str]: + return self._scripts + + @scripts.setter + def scripts(self, value: Dict[str, str]): + self._scripts = self._require_dict_str_str(value, "scripts") diff --git a/src/cpl-cli/cpl/cli/utils/__init__.py b/src/cpl-cli/cpl/cli/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl-cli/cpl/cli/utils/custom_command.py b/src/cpl-cli/cpl/cli/utils/custom_command.py new file mode 100644 index 00000000..866fd186 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/custom_command.py @@ -0,0 +1,18 @@ +import subprocess + +import click + + +def script_command(cli_group, name, command): + + @cli_group.command(name) + @click.argument("args", nargs=-1) + def _run_script(args): + click.echo(f"Running script: {name}") + try: + cmd = command.split() + list(args) + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + click.echo(f"Script '{name}' failed with exit code {e.returncode}", err=True) + except FileNotFoundError: + click.echo(f"Command not found: {command.split()[0]}", err=True) diff --git a/src/cpl-cli/cpl/cli/utils/json.py b/src/cpl-cli/cpl/cli/utils/json.py new file mode 100644 index 00000000..f39e0400 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/json.py @@ -0,0 +1,14 @@ +import json +from pathlib import Path + + +def load_project_json(path: Path) -> dict: + if not path.exists(): + return {} + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def save_project_json(path: Path, data: dict): + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) diff --git a/src/cpl-cli/cpl/cli/utils/pip.py b/src/cpl-cli/cpl/cli/utils/pip.py new file mode 100644 index 00000000..5c51dd16 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/pip.py @@ -0,0 +1,50 @@ +import subprocess +from pathlib import Path + +from cpl.cli.utils.venv import ensure_venv, get_venv_pip +from cpl.core.console import Console + + +class Pip: + + @staticmethod + def normalize_dep(name: str, raw: str) -> str: + raw = raw.strip() + if not raw: + return name + + table = { + "!": "!=", + ">": ">=", + ">>": ">", + "<": "<=", + "<<": "<", + "~": "~=", + "=": "===", + } + + op = "==" + for prefix, pip_op in table.items(): + if raw.startswith(prefix): + op = pip_op + raw = raw[len(prefix) :] + break + + return f"{name}{op}{raw}" + + @staticmethod + def command(project_path: str, command: str, *args,verbose:bool=False): + venv_path = ensure_venv(Path(project_path)) + pip = get_venv_pip(venv_path) + + if verbose: + Console.write_line() + + subprocess.run( + f"{pip} {command} {''.join(args)}", + shell=True, + check=True, + stdin=subprocess.DEVNULL if not verbose else None, + stdout=subprocess.DEVNULL if not verbose else None, + stderr=subprocess.STDOUT if not verbose else None, + ) \ No newline at end of file diff --git a/src/cpl-cli/cpl/cli/utils/prompt.py b/src/cpl-cli/cpl/cli/utils/prompt.py new file mode 100644 index 00000000..e90e7708 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/prompt.py @@ -0,0 +1,20 @@ +import click + + +class SmartChoice(click.Choice): + + def __init__(self, choices: list, aliases: dict = None): + click.Choice.__init__(self, choices, case_sensitive=False) + + self._aliases = {c: c[0].lower() for c in choices if len(c) > 0} + if aliases: + self._aliases.update({k: v.lower() for k, v in aliases.items()}) + + if any([x[0].lower in self._aliases for x in choices if len(x) > 1]): + raise ValueError("Alias conflict with first letters of choices") + + def convert(self, value, param, ctx): + val_lower = value.lower() + if val_lower in self._aliases.values(): + value = [k for k, v in self._aliases.items() if v == val_lower][0] + return super().convert(value, param, ctx) diff --git a/src/cpl-cli/cpl/cli/utils/structure.py b/src/cpl-cli/cpl/cli/utils/structure.py new file mode 100644 index 00000000..17266631 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/structure.py @@ -0,0 +1,30 @@ +import json +from pathlib import Path + +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.core.console import Console + + +def resolve_project(path: Path, name: str | None) -> Project: + project_file = path / "cpl.project.json" + if project_file.exists(): + return Project.from_file(project_file) + + workspace_file = path / "cpl.workspace.json" + if workspace_file.exists(): + workspace = Workspace.from_file(workspace_file) + if name: + for p in workspace.projects: + project = Project.from_file(p) + if project.name == name: + return project + + elif workspace.default_project: + for p in workspace.projects: + project = Project.from_file(p) + if project.name == workspace.default_project: + return project + + Console.error(f"Could not find project file '{path}'") + exit(1) diff --git a/src/cpl-cli/cpl/cli/utils/venv.py b/src/cpl-cli/cpl/cli/utils/venv.py new file mode 100644 index 00000000..b34b84f7 --- /dev/null +++ b/src/cpl-cli/cpl/cli/utils/venv.py @@ -0,0 +1,44 @@ +import os +import sys +import venv +from pathlib import Path + +from cpl.core.configuration import Configuration +from cpl.core.console import Console + + +def ensure_venv(start_path: Path | None = None) -> Path: + start_path = start_path or Path.cwd() + workspace_path = Configuration.get("workspace_path") + + if workspace_path is not None: + workspace_path = Path(os.path.dirname(workspace_path)) + + ws_venv = workspace_path / ".venv" + if ws_venv.exists(): + return ws_venv + + for parent in [start_path, *start_path.parents]: + venv_path = parent / ".venv" + if venv_path.exists(): + return venv_path + + if workspace_path is not None: + venv_path = workspace_path / ".venv" + else: + venv_path = start_path / ".venv" + + Console.write_line(f"Creating virtual environment at {venv_path}...") + venv.EnvBuilder(with_pip=True).create(venv_path) + return venv_path + + +def get_venv_python(venv_path: Path) -> Path: + if sys.platform == "win32": + return venv_path / "Scripts" / "python.exe" + return venv_path / "bin" / "python" + + +def get_venv_pip(venv_path: Path) -> str: + python_exe = get_venv_python(venv_path) + return f"{python_exe} -m pip" diff --git a/src/cpl-cli/pyproject.toml b/src/cpl-cli/pyproject.toml new file mode 100644 index 00000000..afbbe062 --- /dev/null +++ b/src/cpl-cli/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=70.1.0", "wheel>=0.43.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "cpl-cli" +version = "2024.7.0" +description = "CPL cli" +readme = "CPL cli package" +requires-python = ">=3.12" +license = "MIT" +authors = [ + { name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" } +] +keywords = ["cpl", "cli", "backend", "shared", "library"] + +dynamic = ["dependencies", "optional-dependencies"] + +[project.urls] +Homepage = "https://www.sh-edraft.de" + +[tool.setuptools.packages.find] +where = ["."] +include = ["cpl*"] + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } +optional-dependencies.dev = { file = ["requirements.dev.txt"] } + diff --git a/src/cpl-cli/requirements.dev.txt b/src/cpl-cli/requirements.dev.txt new file mode 100644 index 00000000..e7664b42 --- /dev/null +++ b/src/cpl-cli/requirements.dev.txt @@ -0,0 +1 @@ +black==25.1.0 \ No newline at end of file diff --git a/src/cpl-cli/requirements.txt b/src/cpl-cli/requirements.txt new file mode 100644 index 00000000..a8244b30 --- /dev/null +++ b/src/cpl-cli/requirements.txt @@ -0,0 +1 @@ +cpl-core \ No newline at end of file diff --git a/src/cpl-core/cpl.project.json b/src/cpl-core/cpl.project.json new file mode 100644 index 00000000..66ebca33 --- /dev/null +++ b/src/cpl-core/cpl.project.json @@ -0,0 +1,24 @@ +{ + "name": "cpl-core", + "version": "0.1.0", + "type": "library", + "license": "MIT", + "author": "Sven Heidemann", + "description": "CLI for the CPL library", + "homepage": "", + "keywords": [], + "dependencies": { + "art": "~6.5", + "colorama": "~0.4.6", + "tabulate": "~0.9.0", + "termcolor": "~3.1.0", + "pynput": "~1.8.1", + "croniter": "~6.0.0" + }, + "devDependencies": { + "black": "25.1.0" + }, + "references": [], + "main": null, + "directory": "cpl/core" +} \ No newline at end of file From c4334f32ed71a61c45735c24a0d53e13dfe532a4 Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 9 Oct 2025 19:58:49 +0200 Subject: [PATCH 02/22] Fixed install & added update --- src/cpl-cli/cpl.project.json | 2 +- src/cpl-cli/cpl/cli/command/install.py | 23 +++- src/cpl-cli/cpl/cli/command/uninstall.py | 3 +- src/cpl-cli/cpl/cli/command/update.py | 81 ++++++++++++++ src/cpl-cli/cpl/cli/main.py | 2 + src/cpl-cli/cpl/cli/utils/pip.py | 93 +++++++++++++++- src/cpl-cli/requirements.txt | 3 +- src/cpl-core/cpl.project.json | 2 +- src/cpl-core/cpl/core/utils/__init__.py | 1 - src/cpl-core/cpl/core/utils/pip.py | 130 ----------------------- 10 files changed, 199 insertions(+), 141 deletions(-) create mode 100644 src/cpl-cli/cpl/cli/command/update.py delete mode 100644 src/cpl-core/cpl/core/utils/pip.py diff --git a/src/cpl-cli/cpl.project.json b/src/cpl-cli/cpl.project.json index 36b88951..edaa4ca7 100644 --- a/src/cpl-cli/cpl.project.json +++ b/src/cpl-cli/cpl.project.json @@ -11,7 +11,7 @@ "click": "~8.3.0" }, "devDependencies": { - "black": "25.1.0" + "black": "~25.9" }, "references": [ "../cpl/cpl.project.json" diff --git a/src/cpl-cli/cpl/cli/command/install.py b/src/cpl-cli/cpl/cli/command/install.py index 807b2c4f..ae5108dd 100644 --- a/src/cpl-cli/cpl/cli/command/install.py +++ b/src/cpl-cli/cpl/cli/command/install.py @@ -10,6 +10,7 @@ from cpl.core.console import Console @cli.command("install", aliases=["i"]) +@click.argument("package", type=click.STRING, required=False) @click.option( "--path", "project_path", @@ -20,8 +21,26 @@ from cpl.core.console import Console @click.option("--name", "project_name", help="Project name (lookup via workspace)") @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", is_flag=True, help="Enable verbose output") -def install(project_path: str, project_name: str, dev: bool, verbose: bool): +def install(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): project = resolve_project(Path(project_path), project_name) + if package is not None: + Console.write_line(f"Installing {package} to '{project.name}':") + try: + Pip.command("install", package, verbose=verbose, path=project_path) + except subprocess.CalledProcessError as e: + Console.error(f"Failed to install {package}: exit code {e.returncode}") + + package_name = Pip.get_package_without_version(package) + installed_version = Pip.get_package_version(package_name, path=project_path) + if installed_version is None: + Console.error(f"Package '{package_name}' not found after installation.") + return + + project.dependencies[package_name] = Pip.apply_prefix(installed_version, Pip.get_package_full_version(package)) + project.save() + Console.write_line(f"Added {package_name}~{installed_version} to project dependencies.") + return + deps: dict = project.dependencies if dev: deps.update(project.dev_dependencies) @@ -36,6 +55,6 @@ def install(project_path: str, project_name: str, dev: bool, verbose: bool): dep = Pip.normalize_dep(name, version) Console.write_line(f" -> {dep}") try: - Pip.command(project_path, "install", dep, verbose=verbose) + Pip.command("install", dep, verbose=verbose, path=project_path) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {dep}: exit code {e.returncode}") diff --git a/src/cpl-cli/cpl/cli/command/uninstall.py b/src/cpl-cli/cpl/cli/command/uninstall.py index a2a7bfe1..68e7a17e 100644 --- a/src/cpl-cli/cpl/cli/command/uninstall.py +++ b/src/cpl-cli/cpl/cli/command/uninstall.py @@ -29,9 +29,10 @@ def uninstall(package: str, project_path: str, project_name: str, dev: bool, ver deps = project.dependencies if not dev else project.dev_dependencies try: - Pip.command(project_path, "install", package, verbose=verbose) + Pip.command("install", package, verbose=verbose, path=project_path) except subprocess.CalledProcessError as e: Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") + return if package in deps: del deps[package] diff --git a/src/cpl-cli/cpl/cli/command/update.py b/src/cpl-cli/cpl/cli/command/update.py new file mode 100644 index 00000000..a10206a0 --- /dev/null +++ b/src/cpl-cli/cpl/cli/command/update.py @@ -0,0 +1,81 @@ +import subprocess +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.pip import Pip +from cpl.cli.utils.structure import resolve_project +from cpl.core.console import Console + + +@cli.command("update", aliases=["u"]) +@click.argument("package", type=click.STRING, required=False) +@click.option( + "--path", + "project_path", + type=click.Path(file_okay=False, exists=False), + default=".", + help="Path to project directory", +) +@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.option("--dev", is_flag=True, help="Include dev dependencies") +@click.option("--verbose", is_flag=True, help="Enable verbose output") +def update(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): + project = resolve_project(Path(project_path), project_name) + deps: dict = project.dependencies + if dev: + deps = project.dev_dependencies + + if package is not None: + if package not in deps: + Console.error(f"Package '{package}' not installed.") + return + + old_spec = deps[package] + + Console.write_line(f"Updating {package} to '{project.name}':") + try: + Pip.command( + "install --upgrade", + f"{Pip.normalize_dep(package, old_spec)}", + verbose=verbose, + path=project_path, + ) + except subprocess.CalledProcessError as e: + Console.error(f"Failed to install {package}: exit code {e.returncode}") + return + + installed_version = Pip.get_package_version(package, path=project_path) + if installed_version is None: + Console.error(f"Package '{package}' not found after update.") + return + + deps[package] = Pip.apply_prefix(installed_version, old_spec) + project.save() + Console.write_line(f"Updated {package} to {deps[package]}") + return + + if not deps: + Console.error("No dependencies to install.") + return + + Console.write_line(f"Updating dependencies for '{project.name}':") + + for name, version in list(deps.items()): + dep = Pip.normalize_dep(name, version) + Console.write_line(f" -> {dep}") + try: + Pip.command("install --upgrade", dep, verbose=verbose, path=project_path) + except subprocess.CalledProcessError as e: + Console.error(f"Failed to update {dep}: exit code {e.returncode}") + return + + installed_version = Pip.get_package_version(name, path=project_path) + if installed_version is None: + Console.error(f"Package '{name}' not found after update.") + continue + + deps[name] = Pip.apply_prefix(installed_version, version) + + project.save() diff --git a/src/cpl-cli/cpl/cli/main.py b/src/cpl-cli/cpl/cli/main.py index 3d8f2bea..5f7357de 100644 --- a/src/cpl-cli/cpl/cli/main.py +++ b/src/cpl-cli/cpl/cli/main.py @@ -5,6 +5,7 @@ from cpl.cli.cli import cli from cpl.cli.command.init import init from cpl.cli.command.install import install from cpl.cli.command.uninstall import uninstall +from cpl.cli.command.update import update from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command from cpl.core.configuration import Configuration @@ -44,6 +45,7 @@ def configure(): cli.add_command(init) cli.add_command(install) cli.add_command(uninstall) + cli.add_command(update) def main(): diff --git a/src/cpl-cli/cpl/cli/utils/pip.py b/src/cpl-cli/cpl/cli/utils/pip.py index 5c51dd16..aa3da223 100644 --- a/src/cpl-cli/cpl/cli/utils/pip.py +++ b/src/cpl-cli/cpl/cli/utils/pip.py @@ -1,3 +1,4 @@ +import re import subprocess from pathlib import Path @@ -6,6 +7,7 @@ from cpl.core.console import Console class Pip: + _ANY_PREFIX_RE = re.compile(r"(===|~=|==|!=|>=|<=|>>|<<|>|<|!|~|=|\^)") @staticmethod def normalize_dep(name: str, raw: str) -> str: @@ -27,14 +29,75 @@ class Pip: for prefix, pip_op in table.items(): if raw.startswith(prefix): op = pip_op - raw = raw[len(prefix) :] + raw = raw[len(prefix):] break return f"{name}{op}{raw}" + @classmethod + def apply_prefix(cls, installed: str, spec: str = None) -> str: + if spec is None or not spec.strip(): + return f"~{installed}" + + s = spec.strip() + if "," in s: + return s + + m = cls._ANY_PREFIX_RE.search(s) + if not m: + return f"~{installed}" + + op = m.group(1) + rest = s[m.end():].strip() + if "," in rest: + rest = rest.split(",", 1)[0].strip() + if " " in rest: + rest = rest.split()[0] + + orig_version = rest + + installed_parts = [p for p in installed.split(".") if p != ""] + if orig_version: + orig_parts = [p for p in orig_version.split(".") if p != ""] + trimmed_installed = ".".join(installed_parts[:len(orig_parts)]) or installed + else: + trimmed_installed = installed + + pip_to_cpl = { + "==": "", + "!=": "!", + ">=": ">", + ">": ">>", + "<=": "<", + "<": "<<", + "~=": "~", + "===": "=", + "^": "~", + } + + if op in pip_to_cpl: + cpl_op = pip_to_cpl[op] + else: + cpl_op = op + + return f"{cpl_op}{trimmed_installed}" + + @classmethod + def get_package_without_version(cls, spec: str) -> str: + for sep in ["==", ">=", "<=", ">", "<", "~=", "!="]: + if sep in spec: + return spec.split(sep, 1)[0].strip() or None + + return spec.strip() or None + + @classmethod + def get_package_full_version(cls, spec: str) -> str | None: + package_name = cls.get_package_without_version(spec) + return spec.replace(package_name, "").strip() or None + @staticmethod - def command(project_path: str, command: str, *args,verbose:bool=False): - venv_path = ensure_venv(Path(project_path)) + def command(command: str, *args,verbose:bool=False, path: str=None): + venv_path = ensure_venv(Path(path or './')) pip = get_venv_pip(venv_path) if verbose: @@ -47,4 +110,26 @@ class Pip: stdin=subprocess.DEVNULL if not verbose else None, stdout=subprocess.DEVNULL if not verbose else None, stderr=subprocess.STDOUT if not verbose else None, - ) \ No newline at end of file + ) + + @staticmethod + def get_package_version(package: str, path: str=None) -> str | None: + venv_path = ensure_venv(Path(path or './')) + pip = get_venv_pip(venv_path) + + try: + result = subprocess.run( + f"{pip} show {package}", + shell=True, + check=True, + capture_output=True, + text=True, + stdin=subprocess.DEVNULL, + ) + for line in result.stdout.splitlines(): + if line.startswith("Version:"): + return line.split(":", 1)[1].strip() + except subprocess.CalledProcessError: + return None + + return None \ No newline at end of file diff --git a/src/cpl-cli/requirements.txt b/src/cpl-cli/requirements.txt index a8244b30..838c3e83 100644 --- a/src/cpl-cli/requirements.txt +++ b/src/cpl-cli/requirements.txt @@ -1 +1,2 @@ -cpl-core \ No newline at end of file +cpl-core +click==8.3.0 \ No newline at end of file diff --git a/src/cpl-core/cpl.project.json b/src/cpl-core/cpl.project.json index 66ebca33..7a3bd3b2 100644 --- a/src/cpl-core/cpl.project.json +++ b/src/cpl-core/cpl.project.json @@ -16,7 +16,7 @@ "croniter": "~6.0.0" }, "devDependencies": { - "black": "25.1.0" + "black": "~25.9" }, "references": [], "main": null, diff --git a/src/cpl-core/cpl/core/utils/__init__.py b/src/cpl-core/cpl/core/utils/__init__.py index c5a89180..6cad83e0 100644 --- a/src/cpl-core/cpl/core/utils/__init__.py +++ b/src/cpl-core/cpl/core/utils/__init__.py @@ -1,6 +1,5 @@ from .base64 import Base64 from .credential_manager import CredentialManager from .json_processor import JSONProcessor -from .pip import Pip from .string import String from .get_value import get_value diff --git a/src/cpl-core/cpl/core/utils/pip.py b/src/cpl-core/cpl/core/utils/pip.py deleted file mode 100644 index bc626e16..00000000 --- a/src/cpl-core/cpl/core/utils/pip.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import subprocess -import sys -from contextlib import suppress -from typing import Optional - - -class Pip: - r"""Executes pip commands""" - - _executable = sys.executable - _env = os.environ - - """Getter""" - - @classmethod - def get_executable(cls) -> str: - return cls._executable - - """Setter""" - - @classmethod - def set_executable(cls, executable: str): - r"""Sets the executable - - Parameter: - executable: :class:`str` - The python command - """ - if executable is None or executable == sys.executable: - return - - cls._executable = executable - if not os.path.islink(cls._executable) or not os.path.isfile(executable): - return - - path = os.path.dirname(os.path.dirname(cls._executable)) - cls._env = os.environ - if sys.platform == "win32": - cls._env["PATH"] = f"{path}\\bin" + os.pathsep + os.environ.get("PATH", "") - else: - cls._env["PATH"] = f"{path}/bin" + os.pathsep + os.environ.get("PATH", "") - cls._env["VIRTUAL_ENV"] = path - - @classmethod - def reset_executable(cls): - r"""Resets the executable to system standard""" - cls._executable = sys.executable - - """Public utils functions""" - - @classmethod - def get_package(cls, package: str) -> Optional[str]: - r"""Gets given package py local pip list - - Parameter: - package: :class:`str` - - Returns: - The package name as string - """ - result = None - with suppress(Exception): - args = [cls._executable, "-m", "pip", "freeze", "--all"] - - result = subprocess.check_output(args, stderr=subprocess.DEVNULL, env=cls._env) - - if result is None: - return None - for p in str(result.decode()).split("\n"): - if p.startswith(package): - return p - - return None - - @classmethod - def get_outdated(cls) -> bytes: - r"""Gets table of outdated packages - - Returns: - Bytes string of the command result - """ - args = [cls._executable, "-m", "pip", "list", "--outdated"] - - return subprocess.check_output(args, env=cls._env) - - @classmethod - def install(cls, package: str, *args, source: str = None, stdout=None, stderr=None): - r"""Installs given package - - Parameter: - package: :class:`str` - The name of the package - args: :class:`list` - Arguments for the command - source: :class:`str` - Extra index URL - stdout: :class:`str` - Stdout of subprocess.run - stderr: :class:`str` - Stderr of subprocess.run - """ - pip_args = [cls._executable, "-m", "pip", "install"] - - for arg in args: - pip_args.append(arg) - - pip_args.append(package) - - if source is not None: - pip_args.append(f"--extra-index-url") - pip_args.append(source) - - subprocess.run(pip_args, stdout=stdout, stderr=stderr, env=cls._env) - - @classmethod - def uninstall(cls, package: str, stdout=None, stderr=None): - r"""Uninstalls given package - - Parameter: - package: :class:`str` - The name of the package - stdout: :class:`str` - Stdout of subprocess.run - stderr: :class:`str` - Stderr of subprocess.run - """ - args = [cls._executable, "-m", "pip", "uninstall", "--yes", package] - - subprocess.run(args, stdout=stdout, stderr=stderr, env=cls._env) From 5f8519d4b367de0c94802aaec523433604923653 Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 9 Oct 2025 20:51:17 +0200 Subject: [PATCH 03/22] Added version --- .gitea/workflows/package.yaml | 6 +++ src/cpl-cli/cpl/cli/__init__.py | 1 + src/cpl-cli/cpl/cli/command/version.py | 27 +++++++++++ src/cpl-cli/cpl/cli/main.py | 14 ++++++ src/cpl-cli/cpl/cli/utils/pip.py | 57 +++++++++++++++++++---- src/cpl-core/cpl/core/__init__.py | 1 + src/cpl-core/cpl/core/console/_spinner.py | 6 +-- src/cpl-core/cpl/core/console/console.py | 20 ++++++++ 8 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 src/cpl-cli/cpl/cli/command/version.py diff --git a/.gitea/workflows/package.yaml b/.gitea/workflows/package.yaml index f4a6d0ce..8db3a1eb 100644 --- a/.gitea/workflows/package.yaml +++ b/.gitea/workflows/package.yaml @@ -36,6 +36,12 @@ jobs: echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)" cat pyproject.toml + - name: Set package version + run: | + sed -i -E "s/^__version__ = \".*\"/__version__ = \"$(cat /workspace/sh-edraft.de/cpl/version.txt)\"/" cpl/*/__init__.py + echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)" + cat cpl/*/__init__.py + - name: Set pip conf run: | cat > .pip.conf <<'EOF' diff --git a/src/cpl-cli/cpl/cli/__init__.py b/src/cpl-cli/cpl/cli/__init__.py index e69de29b..5becc17c 100644 --- a/src/cpl-cli/cpl/cli/__init__.py +++ b/src/cpl-cli/cpl/cli/__init__.py @@ -0,0 +1 @@ +__version__ = "1.0.0" diff --git a/src/cpl-cli/cpl/cli/command/version.py b/src/cpl-cli/cpl/cli/command/version.py new file mode 100644 index 00000000..5709f43a --- /dev/null +++ b/src/cpl-cli/cpl/cli/command/version.py @@ -0,0 +1,27 @@ +import platform + +import cpl +from cpl.cli.cli import cli +from cpl.cli.utils.pip import Pip +from cpl.core.console import Console, ForegroundColorEnum + + +@cli.command("version", aliases=["v"]) +def version(): + Console.set_foreground_color(ForegroundColorEnum.yellow) + Console.banner("CPL CLI") + Console.set_foreground_color(ForegroundColorEnum.default) + + Console.write_line() + Console.write_line(f"CPL CLI: {getattr(cpl.cli, '__version__', "1.0")}") + Console.write_line(f"Python: {platform.python_version()}") + Console.write_line(f"PIP: {Pip.get_pip_version()}") + Console.write_line(f"OS: {platform.system()} {platform.release()}") + + Console.write_line("\nCPL Packages:\n") + cpl_packages = {n: v for n, v in Pip.get_packages().items() if n.startswith("cpl-")} + if len(cpl_packages) == 0: + Console.write_line("No CPL packages installed") + return + + Console.table(["Package", "Version"], [[n, v] for n, v in cpl_packages.items()]) diff --git a/src/cpl-cli/cpl/cli/main.py b/src/cpl-cli/cpl/cli/main.py index 5f7357de..69a1e0c8 100644 --- a/src/cpl-cli/cpl/cli/main.py +++ b/src/cpl-cli/cpl/cli/main.py @@ -6,6 +6,7 @@ from cpl.cli.command.init import init from cpl.cli.command.install import install from cpl.cli.command.uninstall import uninstall from cpl.cli.command.update import update +from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command from cpl.core.configuration import Configuration @@ -42,10 +43,23 @@ def prepare(): def configure(): + # cli + cli.add_command(version) + + # structure cli.add_command(init) + # cli.add_command(new) + + # packaging cli.add_command(install) cli.add_command(uninstall) cli.add_command(update) + # cli.add_command(add) + # cli.add_command(remove) + + # run + # cli.add_command(run) + # cli.add_command(start) def main(): diff --git a/src/cpl-cli/cpl/cli/utils/pip.py b/src/cpl-cli/cpl/cli/utils/pip.py index aa3da223..5c00eb92 100644 --- a/src/cpl-cli/cpl/cli/utils/pip.py +++ b/src/cpl-cli/cpl/cli/utils/pip.py @@ -29,7 +29,7 @@ class Pip: for prefix, pip_op in table.items(): if raw.startswith(prefix): op = pip_op - raw = raw[len(prefix):] + raw = raw[len(prefix) :] break return f"{name}{op}{raw}" @@ -48,7 +48,7 @@ class Pip: return f"~{installed}" op = m.group(1) - rest = s[m.end():].strip() + rest = s[m.end() :].strip() if "," in rest: rest = rest.split(",", 1)[0].strip() if " " in rest: @@ -59,7 +59,7 @@ class Pip: installed_parts = [p for p in installed.split(".") if p != ""] if orig_version: orig_parts = [p for p in orig_version.split(".") if p != ""] - trimmed_installed = ".".join(installed_parts[:len(orig_parts)]) or installed + trimmed_installed = ".".join(installed_parts[: len(orig_parts)]) or installed else: trimmed_installed = installed @@ -96,8 +96,8 @@ class Pip: return spec.replace(package_name, "").strip() or None @staticmethod - def command(command: str, *args,verbose:bool=False, path: str=None): - venv_path = ensure_venv(Path(path or './')) + def command(command: str, *args, verbose: bool = False, path: str = None): + venv_path = ensure_venv(Path(path or "./")) pip = get_venv_pip(venv_path) if verbose: @@ -113,8 +113,8 @@ class Pip: ) @staticmethod - def get_package_version(package: str, path: str=None) -> str | None: - venv_path = ensure_venv(Path(path or './')) + def get_package_version(package: str, path: str = None) -> str | None: + venv_path = ensure_venv(Path(path or "./")) pip = get_venv_pip(venv_path) try: @@ -132,4 +132,45 @@ class Pip: except subprocess.CalledProcessError: return None - return None \ No newline at end of file + return None + + @staticmethod + def get_packages(path: str = None): + venv_path = ensure_venv(Path(path or "./")) + pip = get_venv_pip(venv_path) + try: + result = subprocess.run( + f"{pip} list --format=freeze", + shell=True, + check=True, + capture_output=True, + text=True, + stdin=subprocess.DEVNULL, + ) + packages = {} + for line in result.stdout.splitlines(): + if "==" in line: + name, version = line.split("==", 1) + packages[name] = version + return packages + except subprocess.CalledProcessError: + return {} + + @staticmethod + def get_pip_version(path: str = None) -> str | None: + venv_path = ensure_venv(Path(path or "./")) + pip = get_venv_pip(venv_path) + + try: + result = subprocess.run( + f"{pip} --version", + shell=True, + check=True, + capture_output=True, + text=True, + stdin=subprocess.DEVNULL, + ) + version = result.stdout.split()[1] + return version + except subprocess.CalledProcessError: + return None diff --git a/src/cpl-core/cpl/core/__init__.py b/src/cpl-core/cpl/core/__init__.py index e69de29b..5becc17c 100644 --- a/src/cpl-core/cpl/core/__init__.py +++ b/src/cpl-core/cpl/core/__init__.py @@ -0,0 +1 @@ +__version__ = "1.0.0" diff --git a/src/cpl-core/cpl/core/console/_spinner.py b/src/cpl-core/cpl/core/console/_spinner.py index 74fab96a..a9f28ab2 100644 --- a/src/cpl-core/cpl/core/console/_spinner.py +++ b/src/cpl-core/cpl/core/console/_spinner.py @@ -1,4 +1,5 @@ import os +import shutil import sys import multiprocessing import time @@ -56,9 +57,8 @@ class Spinner(Process): if sys.platform == "win32": columns = os.get_terminal_size().columns else: - values = os.popen("stty size", "r").read().split() - term_rows, term_columns = values if len(values) == 2 else (0, 0) - columns = int(term_columns) + size = shutil.get_terminal_size(fallback=(80, 24)) + columns = max(1, size.columns) end_msg = "done" diff --git a/src/cpl-core/cpl/core/console/console.py b/src/cpl-core/cpl/core/console/console.py index ce764832..86e6fa9c 100644 --- a/src/cpl-core/cpl/core/console/console.py +++ b/src/cpl-core/cpl/core/console/console.py @@ -1,4 +1,5 @@ import os +import shutil import sys import time from collections.abc import Callable @@ -251,6 +252,25 @@ class Console: Console.read() sys.exit() + @classmethod + def divider(cls, char: str = "-"): + r"""Prints a divider line + + Parameter: + char: :class:`str` + Character to use for the divider + """ + if cls._disabled: + return + + if cls._hold_back: + cls._hold_back_calls.append(ConsoleCall(cls.divider, char)) + return + + size = shutil.get_terminal_size(fallback=(80, 24)) + columns = max(1, size.columns) + cls.write_line(char * columns) + @classmethod def disable(cls): r"""Disables console interaction""" From f1aaaf2a5b48da88a208ed8dd479f31743a2e61c Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 9 Oct 2025 21:24:25 +0200 Subject: [PATCH 04/22] Set version to all packages --- src/cpl-api/cpl/api/__init__.py | 2 ++ src/cpl-application/cpl/application/__init__.py | 2 ++ src/cpl-auth/cpl/auth/__init__.py | 2 ++ src/cpl-cli/cpl/cli/__init__.py | 1 + src/cpl-core/cpl/core/__init__.py | 1 + src/cpl-database/cpl/database/__init__.py | 2 ++ src/cpl-dependency/cpl/dependency/__init__.py | 2 ++ src/cpl-graphql/cpl/graphql/__init__.py | 1 + src/cpl-mail/cpl/mail/__init__.py | 2 ++ src/cpl-query/cpl/query/__init__.py | 2 ++ src/cpl-translation/cpl/translation/__init__.py | 2 ++ 11 files changed, 19 insertions(+) diff --git a/src/cpl-api/cpl/api/__init__.py b/src/cpl-api/cpl/api/__init__.py index 8332d8f7..dd8eb491 100644 --- a/src/cpl-api/cpl/api/__init__.py +++ b/src/cpl-api/cpl/api/__init__.py @@ -2,3 +2,5 @@ from .error import APIError, AlreadyExists, EndpointNotImplemented, Forbidden, N from .logger import APILogger from .settings import ApiSettings from .api_module import ApiModule + +__version__ = "1.0.0" diff --git a/src/cpl-application/cpl/application/__init__.py b/src/cpl-application/cpl/application/__init__.py index 576b53a8..5b2d5598 100644 --- a/src/cpl-application/cpl/application/__init__.py +++ b/src/cpl-application/cpl/application/__init__.py @@ -1,2 +1,4 @@ from .application_builder import ApplicationBuilder from .host import Host + +__version__ = "1.0.0" diff --git a/src/cpl-auth/cpl/auth/__init__.py b/src/cpl-auth/cpl/auth/__init__.py index a0f3854a..e811347f 100644 --- a/src/cpl-auth/cpl/auth/__init__.py +++ b/src/cpl-auth/cpl/auth/__init__.py @@ -4,3 +4,5 @@ from cpl.auth.keycloak.keycloak_client import KeycloakClient as _KeycloakClient from .auth_module import AuthModule from .keycloak_settings import KeycloakSettings from .logger import AuthLogger + +__version__ = "1.0.0" diff --git a/src/cpl-cli/cpl/cli/__init__.py b/src/cpl-cli/cpl/cli/__init__.py index 5becc17c..a6d8caf7 100644 --- a/src/cpl-cli/cpl/cli/__init__.py +++ b/src/cpl-cli/cpl/cli/__init__.py @@ -1 +1,2 @@ __version__ = "1.0.0" +__version__ = "1.0.0" diff --git a/src/cpl-core/cpl/core/__init__.py b/src/cpl-core/cpl/core/__init__.py index 5becc17c..a6d8caf7 100644 --- a/src/cpl-core/cpl/core/__init__.py +++ b/src/cpl-core/cpl/core/__init__.py @@ -1 +1,2 @@ __version__ = "1.0.0" +__version__ = "1.0.0" diff --git a/src/cpl-database/cpl/database/__init__.py b/src/cpl-database/cpl/database/__init__.py index b9576582..c86740a8 100644 --- a/src/cpl-database/cpl/database/__init__.py +++ b/src/cpl-database/cpl/database/__init__.py @@ -3,3 +3,5 @@ from . import postgres as _postgres from .database_module import DatabaseModule from .logger import DBLogger from .table_manager import TableManager + +__version__ = "1.0.0" diff --git a/src/cpl-dependency/cpl/dependency/__init__.py b/src/cpl-dependency/cpl/dependency/__init__.py index fa94d32c..17394656 100644 --- a/src/cpl-dependency/cpl/dependency/__init__.py +++ b/src/cpl-dependency/cpl/dependency/__init__.py @@ -5,3 +5,5 @@ from .service_descriptor import ServiceDescriptor from .service_lifetime import ServiceLifetimeEnum from .service_provider import ServiceProvider from .service_provider import ServiceProvider + +__version__ = "1.0.0" diff --git a/src/cpl-graphql/cpl/graphql/__init__.py b/src/cpl-graphql/cpl/graphql/__init__.py index e69de29b..5becc17c 100644 --- a/src/cpl-graphql/cpl/graphql/__init__.py +++ b/src/cpl-graphql/cpl/graphql/__init__.py @@ -0,0 +1 @@ +__version__ = "1.0.0" diff --git a/src/cpl-mail/cpl/mail/__init__.py b/src/cpl-mail/cpl/mail/__init__.py index 97336c8e..c0b64ed2 100644 --- a/src/cpl-mail/cpl/mail/__init__.py +++ b/src/cpl-mail/cpl/mail/__init__.py @@ -4,3 +4,5 @@ from .email_client_settings import EMailClientSettings from .email_model import EMail from .logger import MailLogger from .mail_module import MailModule + +__version__ = "1.0.0" diff --git a/src/cpl-query/cpl/query/__init__.py b/src/cpl-query/cpl/query/__init__.py index 9ae9b246..933ac92f 100644 --- a/src/cpl-query/cpl/query/__init__.py +++ b/src/cpl-query/cpl/query/__init__.py @@ -5,3 +5,5 @@ from .immutable_set import ImmutableSet from .list import List from .ordered_enumerable import OrderedEnumerable from .set import Set + +__version__ = "1.0.0" diff --git a/src/cpl-translation/cpl/translation/__init__.py b/src/cpl-translation/cpl/translation/__init__.py index 8bcc4f38..9ec4f91f 100644 --- a/src/cpl-translation/cpl/translation/__init__.py +++ b/src/cpl-translation/cpl/translation/__init__.py @@ -3,3 +3,5 @@ from .translation_module import TranslationModule from .translation_service import TranslationService from .translation_service_abc import TranslationServiceABC from .translation_settings import TranslationSettings + +__version__ = "1.0.0" From 90ff8d466d24431a92ee6681447827fdf24d0f76 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 09:32:13 +0200 Subject: [PATCH 05/22] Renamed project dirs --- src/{cpl-api => api}/cpl/api/__init__.py | 0 src/{cpl-api => api}/cpl/api/abc/__init__.py | 0 src/{cpl-api => api}/cpl/api/abc/asgi_middleware_abc.py | 0 src/{cpl-api => api}/cpl/api/abc/web_app_abc.py | 0 src/{cpl-api => api}/cpl/api/api_module.py | 0 src/{cpl-api => api}/cpl/api/application/__init__.py | 0 src/{cpl-api => api}/cpl/api/application/web_app.py | 0 src/{cpl-api => api}/cpl/api/error.py | 0 src/{cpl-api => api}/cpl/api/logger.py | 0 src/{cpl-api => api}/cpl/api/middleware/__init__.py | 0 src/{cpl-api => api}/cpl/api/middleware/authentication.py | 0 src/{cpl-api => api}/cpl/api/middleware/authorization.py | 0 src/{cpl-api => api}/cpl/api/middleware/logging.py | 0 src/{cpl-api => api}/cpl/api/middleware/request.py | 0 src/{cpl-api => api}/cpl/api/model/__init__.py | 0 src/{cpl-api => api}/cpl/api/model/api_route.py | 0 src/{cpl-api => api}/cpl/api/model/policy.py | 0 src/{cpl-api => api}/cpl/api/model/validation_match.py | 0 src/{cpl-api => api}/cpl/api/model/websocket_route.py | 0 src/{cpl-api => api}/cpl/api/registry/__init__.py | 0 src/{cpl-api => api}/cpl/api/registry/policy.py | 0 src/{cpl-api => api}/cpl/api/registry/route.py | 0 src/{cpl-api => api}/cpl/api/router.py | 0 src/{cpl-api => api}/cpl/api/settings.py | 0 src/{cpl-api => api}/cpl/api/typing.py | 0 src/{cpl-api => api}/pyproject.toml | 0 src/{cpl-api => api}/requirements.dev.txt | 0 src/{cpl-api => api}/requirements.txt | 0 src/{cpl-application => application}/cpl/application/__init__.py | 0 .../cpl/application/abc/__init__.py | 0 .../cpl/application/abc/application_abc.py | 0 .../cpl/application/abc/application_extension_abc.py | 0 .../cpl/application/abc/startup_abc.py | 0 .../cpl/application/abc/startup_extension_abc.py | 0 .../cpl/application/application_builder.py | 0 src/{cpl-application => application}/cpl/application/host.py | 0 src/{cpl-application => application}/pyproject.toml | 0 src/{cpl-application => application}/requirements.dev.txt | 0 src/{cpl-application => application}/requirements.txt | 0 src/{cpl-auth => auth}/cpl/auth/__init__.py | 0 src/{cpl-auth => auth}/cpl/auth/auth_module.py | 0 src/{cpl-auth => auth}/cpl/auth/keycloak/__init__.py | 0 src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_admin.py | 0 src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_client.py | 0 src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_user.py | 0 src/{cpl-auth => auth}/cpl/auth/keycloak_settings.py | 0 src/{cpl-auth => auth}/cpl/auth/logger.py | 0 src/{cpl-auth => auth}/cpl/auth/permission/__init__.py | 0 src/{cpl-auth => auth}/cpl/auth/permission/permission_module.py | 0 src/{cpl-auth => auth}/cpl/auth/permission/permission_seeder.py | 0 src/{cpl-auth => auth}/cpl/auth/permission/permissions.py | 0 .../cpl/auth/permission/permissions_registry.py | 0 src/{cpl-auth => auth}/cpl/auth/permission/role_seeder.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/__init__.py | 0 .../cpl/auth/schema/_administration/__init__.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_administration/api_key.py | 0 .../cpl/auth/schema/_administration/api_key_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_administration/user.py | 0 .../cpl/auth/schema/_administration/user_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_permission/__init__.py | 0 .../cpl/auth/schema/_permission/api_key_permission.py | 0 .../cpl/auth/schema/_permission/api_key_permission_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_permission/permission.py | 0 .../cpl/auth/schema/_permission/permission_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_permission/role.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_dao.py | 0 .../cpl/auth/schema/_permission/role_permission.py | 0 .../cpl/auth/schema/_permission/role_permission_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_user.py | 0 .../cpl/auth/schema/_permission/role_user_dao.py | 0 src/{cpl-auth => auth}/cpl/auth/scripts/mysql/1-users.sql | 0 src/{cpl-auth => auth}/cpl/auth/scripts/mysql/2-api-key.sql | 0 .../cpl/auth/scripts/mysql/3-roles-permissions.sql | 0 .../cpl/auth/scripts/mysql/4-api-key-permissions.sql | 0 src/{cpl-auth => auth}/cpl/auth/scripts/postgres/1-users.sql | 0 src/{cpl-auth => auth}/cpl/auth/scripts/postgres/2-api-key.sql | 0 .../cpl/auth/scripts/postgres/3-roles-permissions.sql | 0 .../cpl/auth/scripts/postgres/4-api-key-permissions.sql | 0 src/{cpl-auth => auth}/pyproject.toml | 0 src/{cpl-auth => auth}/requirements.dev.txt | 0 src/{cpl-auth => auth}/requirements.txt | 0 src/{cpl-cli => cli}/cpl.project.json | 0 src/{cpl-cli => cli}/cpl/cli/__init__.py | 0 src/{cpl-cli => cli}/cpl/cli/cli.py | 0 src/{cpl-cli => cli}/cpl/cli/command/__init__.py | 0 src/{cpl-cli => cli}/cpl/cli/command/init.py | 0 src/{cpl-cli => cli}/cpl/cli/command/install.py | 0 src/{cpl-cli => cli}/cpl/cli/command/uninstall.py | 0 src/{cpl-cli => cli}/cpl/cli/command/update.py | 0 src/{cpl-cli => cli}/cpl/cli/command/version.py | 0 src/{cpl-cli => cli}/cpl/cli/const.py | 0 src/{cpl-cli => cli}/cpl/cli/main.py | 0 src/{cpl-cli => cli}/cpl/cli/model/__init__.py | 0 src/{cpl-cli => cli}/cpl/cli/model/cpl_structure_model.py | 0 src/{cpl-cli => cli}/cpl/cli/model/project.py | 0 src/{cpl-cli => cli}/cpl/cli/model/workspace.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/__init__.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/custom_command.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/json.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/pip.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/prompt.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/structure.py | 0 src/{cpl-cli => cli}/cpl/cli/utils/venv.py | 0 src/{cpl-cli => cli}/pyproject.toml | 0 src/{cpl-cli => cli}/requirements.dev.txt | 0 src/{cpl-cli => cli}/requirements.txt | 0 src/{cpl-core => core}/cpl.project.json | 0 src/{cpl-core => core}/cpl/core/__init__.py | 0 src/{cpl-core => core}/cpl/core/abc/__init__.py | 0 src/{cpl-core => core}/cpl/core/abc/registry_abc.py | 0 src/{cpl-core => core}/cpl/core/configuration/__init__.py | 0 src/{cpl-core => core}/cpl/core/configuration/configuration.py | 0 .../cpl/core/configuration/configuration_model_abc.py | 0 src/{cpl-core => core}/cpl/core/console/__init__.py | 0 src/{cpl-core => core}/cpl/core/console/_call.py | 0 src/{cpl-core => core}/cpl/core/console/_spinner.py | 0 src/{cpl-core => core}/cpl/core/console/background_color_enum.py | 0 src/{cpl-core => core}/cpl/core/console/console.py | 0 src/{cpl-core => core}/cpl/core/console/foreground_color_enum.py | 0 src/{cpl-core => core}/cpl/core/ctx/__init__.py | 0 src/{cpl-core => core}/cpl/core/ctx/user_context.py | 0 src/{cpl-core => core}/cpl/core/environment/__init__.py | 0 src/{cpl-core => core}/cpl/core/environment/environment.py | 0 src/{cpl-core => core}/cpl/core/environment/environment_enum.py | 0 src/{cpl-core => core}/cpl/core/errors.py | 0 src/{cpl-core => core}/cpl/core/log/__init__.py | 0 src/{cpl-core => core}/cpl/core/log/log_level.py | 0 src/{cpl-core => core}/cpl/core/log/log_settings.py | 0 src/{cpl-core => core}/cpl/core/log/logger.py | 0 src/{cpl-core => core}/cpl/core/log/logger_abc.py | 0 src/{cpl-core => core}/cpl/core/log/structured_logger.py | 0 src/{cpl-core => core}/cpl/core/log/wrapped_logger.py | 0 src/{cpl-core => core}/cpl/core/pipes/__init__.py | 0 src/{cpl-core => core}/cpl/core/pipes/bool_pipe.py | 0 src/{cpl-core => core}/cpl/core/pipes/ip_address_pipe.py | 0 src/{cpl-core => core}/cpl/core/pipes/pipe_abc.py | 0 src/{cpl-core => core}/cpl/core/time/__init__.py | 0 src/{cpl-core => core}/cpl/core/time/cron.py | 0 src/{cpl-core => core}/cpl/core/time/time_format_settings.py | 0 src/{cpl-core => core}/cpl/core/typing.py | 0 src/{cpl-core => core}/cpl/core/utils/__init__.py | 0 src/{cpl-core => core}/cpl/core/utils/base64.py | 0 src/{cpl-core => core}/cpl/core/utils/benchmark.py | 0 src/{cpl-core => core}/cpl/core/utils/cache.py | 0 src/{cpl-core => core}/cpl/core/utils/cast.py | 0 src/{cpl-core => core}/cpl/core/utils/credential_manager.py | 0 src/{cpl-core => core}/cpl/core/utils/get_value.py | 0 src/{cpl-core => core}/cpl/core/utils/json_processor.py | 0 src/{cpl-core => core}/cpl/core/utils/number.py | 0 src/{cpl-core => core}/cpl/core/utils/string.py | 0 src/{cpl-core => core}/pyproject.toml | 0 src/{cpl-core => core}/requirements.dev.txt | 0 src/{cpl-core => core}/requirements.txt | 0 src/{cpl-database => database}/cpl/database/__init__.py | 0 src/{cpl-database => database}/cpl/database/abc/__init__.py | 0 src/{cpl-database => database}/cpl/database/abc/connection_abc.py | 0 .../cpl/database/abc/data_access_object_abc.py | 0 .../cpl/database/abc/data_seeder_abc.py | 0 src/{cpl-database => database}/cpl/database/abc/db_context_abc.py | 0 .../cpl/database/abc/db_join_model_abc.py | 0 src/{cpl-database => database}/cpl/database/abc/db_model_abc.py | 0 .../cpl/database/abc/db_model_dao_abc.py | 0 src/{cpl-database => database}/cpl/database/const.py | 0 src/{cpl-database => database}/cpl/database/database_module.py | 0 .../cpl/database/external_data_temp_table_builder.py | 0 src/{cpl-database => database}/cpl/database/logger.py | 0 src/{cpl-database => database}/cpl/database/model/__init__.py | 0 .../cpl/database/model/database_settings.py | 0 src/{cpl-database => database}/cpl/database/model/migration.py | 0 src/{cpl-database => database}/cpl/database/model/server_type.py | 0 src/{cpl-database => database}/cpl/database/mysql/__init__.py | 0 src/{cpl-database => database}/cpl/database/mysql/connection.py | 0 src/{cpl-database => database}/cpl/database/mysql/db_context.py | 0 src/{cpl-database => database}/cpl/database/mysql/mysql_module.py | 0 src/{cpl-database => database}/cpl/database/mysql/mysql_pool.py | 0 src/{cpl-database => database}/cpl/database/postgres/__init__.py | 0 .../cpl/database/postgres/db_context.py | 0 .../cpl/database/postgres/postgres_module.py | 0 .../cpl/database/postgres/postgres_pool.py | 0 .../cpl/database/postgres/sql_select_builder.py | 0 src/{cpl-database => database}/cpl/database/schema/__init__.py | 0 .../cpl/database/schema/executed_migration.py | 0 .../cpl/database/schema/executed_migration_dao.py | 0 .../cpl/database/scripts/mysql/0-cpl-initial.sql | 0 .../cpl/database/scripts/mysql/trigger.txt | 0 .../cpl/database/scripts/postgres/0-cpl-initial.sql | 0 src/{cpl-database => database}/cpl/database/service/__init__.py | 0 .../cpl/database/service/migration_service.py | 0 .../cpl/database/service/seeder_service.py | 0 src/{cpl-database => database}/cpl/database/table_manager.py | 0 src/{cpl-database => database}/cpl/database/typing.py | 0 src/{cpl-database => database}/pyproject.toml | 0 src/{cpl-database => database}/requirements.dev.txt | 0 src/{cpl-database => database}/requirements.txt | 0 src/{cpl-dependency => dependency}/cpl/dependency/__init__.py | 0 src/{cpl-dependency => dependency}/cpl/dependency/context.py | 0 src/{cpl-dependency => dependency}/cpl/dependency/event_bus.py | 0 .../cpl/dependency/hosted/__init__.py | 0 .../cpl/dependency/hosted/cronjob.py | 0 .../cpl/dependency/hosted/hosted_service.py | 0 .../cpl/dependency/hosted/startup_task.py | 0 src/{cpl-dependency => dependency}/cpl/dependency/inject.py | 0 .../cpl/dependency/module/__init__.py | 0 .../cpl/dependency/module/module.py | 0 .../cpl/dependency/module/module_abc.py | 0 .../cpl/dependency/module/module_protocol.py | 0 .../cpl/dependency/service_collection.py | 0 .../cpl/dependency/service_descriptor.py | 0 .../cpl/dependency/service_lifetime.py | 0 .../cpl/dependency/service_provider.py | 0 src/{cpl-dependency => dependency}/cpl/dependency/typing.py | 0 src/{cpl-dependency => dependency}/pyproject.toml | 0 src/{cpl-dependency => dependency}/requirements.dev.txt | 0 src/{cpl-dependency => dependency}/requirements.txt | 0 src/{cpl-graphql => graphql}/cpl/graphql/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/graphiql.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/graphql.py | 0 .../cpl/graphql/_endpoints/lazy_graphql_app.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/playground.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/abc/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/abc/query_abc.py | 0 .../cpl/graphql/abc/strawberry_protocol.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/application/__init__.py | 0 .../cpl/graphql/application/graphql_app.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/__init__.py | 0 .../cpl/graphql/auth/api_key/api_key_filter.py | 0 .../cpl/graphql/auth/api_key/api_key_graph_type.py | 0 .../cpl/graphql/auth/api_key/api_key_input.py | 0 .../cpl/graphql/auth/api_key/api_key_mutation.py | 0 .../cpl/graphql/auth/api_key/api_key_sort.py | 0 .../cpl/graphql/auth/graphql_auth_module.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/role/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_filter.py | 0 .../cpl/graphql/auth/role/role_graph_type.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_input.py | 0 .../cpl/graphql/auth/role/role_mutation.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_sort.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/user/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_filter.py | 0 .../cpl/graphql/auth/user/user_graph_type.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_input.py | 0 .../cpl/graphql/auth/user/user_mutation.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_sort.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/error.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/event_bus/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/event_bus/memory.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/graphql_module.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/query_context.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/argument.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/collection.py | 0 .../cpl/graphql/schema/db_model_graph_type.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/field.py | 0 .../cpl/graphql/schema/filter/__init__.py | 0 .../cpl/graphql/schema/filter/bool_filter.py | 0 .../cpl/graphql/schema/filter/date_filter.py | 0 .../cpl/graphql/schema/filter/db_model_filter.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/filter.py | 0 .../cpl/graphql/schema/filter/int_filter.py | 0 .../cpl/graphql/schema/filter/string_filter.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/graph_type.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/input.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/mutation.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/query.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/root_mutation.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/root_query.py | 0 .../cpl/graphql/schema/root_subscription.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/__init__.py | 0 .../cpl/graphql/schema/sort/db_model_sort.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/sort.py | 0 .../cpl/graphql/schema/sort/sort_order.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/schema/subscription.py | 0 .../cpl/graphql/schema/subscription_field.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/service/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/service/graphql.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/service/schema.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/typing.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/utils/__init__.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/utils/name_pipe.py | 0 src/{cpl-graphql => graphql}/cpl/graphql/utils/type_collector.py | 0 src/{cpl-graphql => graphql}/pyproject.toml | 0 src/{cpl-graphql => graphql}/requirements.dev.txt | 0 src/{cpl-graphql => graphql}/requirements.txt | 0 src/{cpl-mail => mail}/cpl/mail/__init__.py | 0 src/{cpl-mail => mail}/cpl/mail/abc/__init__.py | 0 src/{cpl-mail => mail}/cpl/mail/abc/email_client_abc.py | 0 src/{cpl-mail => mail}/cpl/mail/email_client.py | 0 src/{cpl-mail => mail}/cpl/mail/email_client_settings.py | 0 src/{cpl-mail => mail}/cpl/mail/email_model.py | 0 src/{cpl-mail => mail}/cpl/mail/logger.py | 0 src/{cpl-mail => mail}/cpl/mail/mail_module.py | 0 src/{cpl-mail => mail}/pyproject.toml | 0 src/{cpl-mail => mail}/requirements.dev.txt | 0 src/{cpl-mail => mail}/requirements.txt | 0 src/{cpl-query => query}/cpl/query/__init__.py | 0 src/{cpl-query => query}/cpl/query/array.py | 0 src/{cpl-query => query}/cpl/query/enumerable.py | 0 src/{cpl-query => query}/cpl/query/immutable_list.py | 0 src/{cpl-query => query}/cpl/query/immutable_set.py | 0 src/{cpl-query => query}/cpl/query/list.py | 0 src/{cpl-query => query}/cpl/query/ordered_enumerable.py | 0 src/{cpl-query => query}/cpl/query/protocol/__init__.py | 0 src/{cpl-query => query}/cpl/query/protocol/sequence.py | 0 src/{cpl-query => query}/cpl/query/set.py | 0 src/{cpl-query => query}/cpl/query/typing.py | 0 src/{cpl-query => query}/pyproject.toml | 0 src/{cpl-query => query}/requirements.dev.txt | 0 src/{cpl-query => query}/requirements.txt | 0 src/{cpl-translation => translation}/cpl/translation/__init__.py | 0 .../cpl/translation/translate_pipe.py | 0 .../cpl/translation/translation_module.py | 0 .../cpl/translation/translation_service.py | 0 .../cpl/translation/translation_service_abc.py | 0 .../cpl/translation/translation_settings.py | 0 src/{cpl-translation => translation}/pyproject.toml | 0 src/{cpl-translation => translation}/requirements.dev.txt | 0 src/{cpl-translation => translation}/requirements.txt | 0 319 files changed, 0 insertions(+), 0 deletions(-) rename src/{cpl-api => api}/cpl/api/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/abc/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/abc/asgi_middleware_abc.py (100%) rename src/{cpl-api => api}/cpl/api/abc/web_app_abc.py (100%) rename src/{cpl-api => api}/cpl/api/api_module.py (100%) rename src/{cpl-api => api}/cpl/api/application/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/application/web_app.py (100%) rename src/{cpl-api => api}/cpl/api/error.py (100%) rename src/{cpl-api => api}/cpl/api/logger.py (100%) rename src/{cpl-api => api}/cpl/api/middleware/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/middleware/authentication.py (100%) rename src/{cpl-api => api}/cpl/api/middleware/authorization.py (100%) rename src/{cpl-api => api}/cpl/api/middleware/logging.py (100%) rename src/{cpl-api => api}/cpl/api/middleware/request.py (100%) rename src/{cpl-api => api}/cpl/api/model/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/model/api_route.py (100%) rename src/{cpl-api => api}/cpl/api/model/policy.py (100%) rename src/{cpl-api => api}/cpl/api/model/validation_match.py (100%) rename src/{cpl-api => api}/cpl/api/model/websocket_route.py (100%) rename src/{cpl-api => api}/cpl/api/registry/__init__.py (100%) rename src/{cpl-api => api}/cpl/api/registry/policy.py (100%) rename src/{cpl-api => api}/cpl/api/registry/route.py (100%) rename src/{cpl-api => api}/cpl/api/router.py (100%) rename src/{cpl-api => api}/cpl/api/settings.py (100%) rename src/{cpl-api => api}/cpl/api/typing.py (100%) rename src/{cpl-api => api}/pyproject.toml (100%) rename src/{cpl-api => api}/requirements.dev.txt (100%) rename src/{cpl-api => api}/requirements.txt (100%) rename src/{cpl-application => application}/cpl/application/__init__.py (100%) rename src/{cpl-application => application}/cpl/application/abc/__init__.py (100%) rename src/{cpl-application => application}/cpl/application/abc/application_abc.py (100%) rename src/{cpl-application => application}/cpl/application/abc/application_extension_abc.py (100%) rename src/{cpl-application => application}/cpl/application/abc/startup_abc.py (100%) rename src/{cpl-application => application}/cpl/application/abc/startup_extension_abc.py (100%) rename src/{cpl-application => application}/cpl/application/application_builder.py (100%) rename src/{cpl-application => application}/cpl/application/host.py (100%) rename src/{cpl-application => application}/pyproject.toml (100%) rename src/{cpl-application => application}/requirements.dev.txt (100%) rename src/{cpl-application => application}/requirements.txt (100%) rename src/{cpl-auth => auth}/cpl/auth/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/auth_module.py (100%) rename src/{cpl-auth => auth}/cpl/auth/keycloak/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_admin.py (100%) rename src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_client.py (100%) rename src/{cpl-auth => auth}/cpl/auth/keycloak/keycloak_user.py (100%) rename src/{cpl-auth => auth}/cpl/auth/keycloak_settings.py (100%) rename src/{cpl-auth => auth}/cpl/auth/logger.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/permission_module.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/permission_seeder.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/permissions.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/permissions_registry.py (100%) rename src/{cpl-auth => auth}/cpl/auth/permission/role_seeder.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_administration/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_administration/api_key.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_administration/api_key_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_administration/user.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_administration/user_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/__init__.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/api_key_permission.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/api_key_permission_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/permission.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/permission_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_permission.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_permission_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_user.py (100%) rename src/{cpl-auth => auth}/cpl/auth/schema/_permission/role_user_dao.py (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/mysql/1-users.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/mysql/2-api-key.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/mysql/3-roles-permissions.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/mysql/4-api-key-permissions.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/postgres/1-users.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/postgres/2-api-key.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/postgres/3-roles-permissions.sql (100%) rename src/{cpl-auth => auth}/cpl/auth/scripts/postgres/4-api-key-permissions.sql (100%) rename src/{cpl-auth => auth}/pyproject.toml (100%) rename src/{cpl-auth => auth}/requirements.dev.txt (100%) rename src/{cpl-auth => auth}/requirements.txt (100%) rename src/{cpl-cli => cli}/cpl.project.json (100%) rename src/{cpl-cli => cli}/cpl/cli/__init__.py (100%) rename src/{cpl-cli => cli}/cpl/cli/cli.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/__init__.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/init.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/install.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/uninstall.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/update.py (100%) rename src/{cpl-cli => cli}/cpl/cli/command/version.py (100%) rename src/{cpl-cli => cli}/cpl/cli/const.py (100%) rename src/{cpl-cli => cli}/cpl/cli/main.py (100%) rename src/{cpl-cli => cli}/cpl/cli/model/__init__.py (100%) rename src/{cpl-cli => cli}/cpl/cli/model/cpl_structure_model.py (100%) rename src/{cpl-cli => cli}/cpl/cli/model/project.py (100%) rename src/{cpl-cli => cli}/cpl/cli/model/workspace.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/__init__.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/custom_command.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/json.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/pip.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/prompt.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/structure.py (100%) rename src/{cpl-cli => cli}/cpl/cli/utils/venv.py (100%) rename src/{cpl-cli => cli}/pyproject.toml (100%) rename src/{cpl-cli => cli}/requirements.dev.txt (100%) rename src/{cpl-cli => cli}/requirements.txt (100%) rename src/{cpl-core => core}/cpl.project.json (100%) rename src/{cpl-core => core}/cpl/core/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/abc/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/abc/registry_abc.py (100%) rename src/{cpl-core => core}/cpl/core/configuration/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/configuration/configuration.py (100%) rename src/{cpl-core => core}/cpl/core/configuration/configuration_model_abc.py (100%) rename src/{cpl-core => core}/cpl/core/console/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/console/_call.py (100%) rename src/{cpl-core => core}/cpl/core/console/_spinner.py (100%) rename src/{cpl-core => core}/cpl/core/console/background_color_enum.py (100%) rename src/{cpl-core => core}/cpl/core/console/console.py (100%) rename src/{cpl-core => core}/cpl/core/console/foreground_color_enum.py (100%) rename src/{cpl-core => core}/cpl/core/ctx/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/ctx/user_context.py (100%) rename src/{cpl-core => core}/cpl/core/environment/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/environment/environment.py (100%) rename src/{cpl-core => core}/cpl/core/environment/environment_enum.py (100%) rename src/{cpl-core => core}/cpl/core/errors.py (100%) rename src/{cpl-core => core}/cpl/core/log/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/log/log_level.py (100%) rename src/{cpl-core => core}/cpl/core/log/log_settings.py (100%) rename src/{cpl-core => core}/cpl/core/log/logger.py (100%) rename src/{cpl-core => core}/cpl/core/log/logger_abc.py (100%) rename src/{cpl-core => core}/cpl/core/log/structured_logger.py (100%) rename src/{cpl-core => core}/cpl/core/log/wrapped_logger.py (100%) rename src/{cpl-core => core}/cpl/core/pipes/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/pipes/bool_pipe.py (100%) rename src/{cpl-core => core}/cpl/core/pipes/ip_address_pipe.py (100%) rename src/{cpl-core => core}/cpl/core/pipes/pipe_abc.py (100%) rename src/{cpl-core => core}/cpl/core/time/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/time/cron.py (100%) rename src/{cpl-core => core}/cpl/core/time/time_format_settings.py (100%) rename src/{cpl-core => core}/cpl/core/typing.py (100%) rename src/{cpl-core => core}/cpl/core/utils/__init__.py (100%) rename src/{cpl-core => core}/cpl/core/utils/base64.py (100%) rename src/{cpl-core => core}/cpl/core/utils/benchmark.py (100%) rename src/{cpl-core => core}/cpl/core/utils/cache.py (100%) rename src/{cpl-core => core}/cpl/core/utils/cast.py (100%) rename src/{cpl-core => core}/cpl/core/utils/credential_manager.py (100%) rename src/{cpl-core => core}/cpl/core/utils/get_value.py (100%) rename src/{cpl-core => core}/cpl/core/utils/json_processor.py (100%) rename src/{cpl-core => core}/cpl/core/utils/number.py (100%) rename src/{cpl-core => core}/cpl/core/utils/string.py (100%) rename src/{cpl-core => core}/pyproject.toml (100%) rename src/{cpl-core => core}/requirements.dev.txt (100%) rename src/{cpl-core => core}/requirements.txt (100%) rename src/{cpl-database => database}/cpl/database/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/abc/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/abc/connection_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/data_access_object_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/data_seeder_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/db_context_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/db_join_model_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/db_model_abc.py (100%) rename src/{cpl-database => database}/cpl/database/abc/db_model_dao_abc.py (100%) rename src/{cpl-database => database}/cpl/database/const.py (100%) rename src/{cpl-database => database}/cpl/database/database_module.py (100%) rename src/{cpl-database => database}/cpl/database/external_data_temp_table_builder.py (100%) rename src/{cpl-database => database}/cpl/database/logger.py (100%) rename src/{cpl-database => database}/cpl/database/model/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/model/database_settings.py (100%) rename src/{cpl-database => database}/cpl/database/model/migration.py (100%) rename src/{cpl-database => database}/cpl/database/model/server_type.py (100%) rename src/{cpl-database => database}/cpl/database/mysql/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/mysql/connection.py (100%) rename src/{cpl-database => database}/cpl/database/mysql/db_context.py (100%) rename src/{cpl-database => database}/cpl/database/mysql/mysql_module.py (100%) rename src/{cpl-database => database}/cpl/database/mysql/mysql_pool.py (100%) rename src/{cpl-database => database}/cpl/database/postgres/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/postgres/db_context.py (100%) rename src/{cpl-database => database}/cpl/database/postgres/postgres_module.py (100%) rename src/{cpl-database => database}/cpl/database/postgres/postgres_pool.py (100%) rename src/{cpl-database => database}/cpl/database/postgres/sql_select_builder.py (100%) rename src/{cpl-database => database}/cpl/database/schema/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/schema/executed_migration.py (100%) rename src/{cpl-database => database}/cpl/database/schema/executed_migration_dao.py (100%) rename src/{cpl-database => database}/cpl/database/scripts/mysql/0-cpl-initial.sql (100%) rename src/{cpl-database => database}/cpl/database/scripts/mysql/trigger.txt (100%) rename src/{cpl-database => database}/cpl/database/scripts/postgres/0-cpl-initial.sql (100%) rename src/{cpl-database => database}/cpl/database/service/__init__.py (100%) rename src/{cpl-database => database}/cpl/database/service/migration_service.py (100%) rename src/{cpl-database => database}/cpl/database/service/seeder_service.py (100%) rename src/{cpl-database => database}/cpl/database/table_manager.py (100%) rename src/{cpl-database => database}/cpl/database/typing.py (100%) rename src/{cpl-database => database}/pyproject.toml (100%) rename src/{cpl-database => database}/requirements.dev.txt (100%) rename src/{cpl-database => database}/requirements.txt (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/__init__.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/context.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/event_bus.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/hosted/__init__.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/hosted/cronjob.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/hosted/hosted_service.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/hosted/startup_task.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/inject.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/module/__init__.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/module/module.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/module/module_abc.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/module/module_protocol.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/service_collection.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/service_descriptor.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/service_lifetime.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/service_provider.py (100%) rename src/{cpl-dependency => dependency}/cpl/dependency/typing.py (100%) rename src/{cpl-dependency => dependency}/pyproject.toml (100%) rename src/{cpl-dependency => dependency}/requirements.dev.txt (100%) rename src/{cpl-dependency => dependency}/requirements.txt (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/graphiql.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/graphql.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/lazy_graphql_app.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/_endpoints/playground.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/abc/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/abc/query_abc.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/abc/strawberry_protocol.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/application/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/application/graphql_app.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/api_key_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/api_key_graph_type.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/api_key_input.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/api_key_mutation.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/api_key/api_key_sort.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/graphql_auth_module.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_graph_type.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_input.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_mutation.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/role/role_sort.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_graph_type.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_input.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_mutation.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/auth/user/user_sort.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/error.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/event_bus/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/event_bus/memory.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/graphql_module.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/query_context.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/argument.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/collection.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/db_model_graph_type.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/field.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/bool_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/date_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/db_model_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/int_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/filter/string_filter.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/graph_type.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/input.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/mutation.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/query.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/root_mutation.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/root_query.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/root_subscription.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/db_model_sort.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/sort.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/sort/sort_order.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/subscription.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/schema/subscription_field.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/service/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/service/graphql.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/service/schema.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/typing.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/utils/__init__.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/utils/name_pipe.py (100%) rename src/{cpl-graphql => graphql}/cpl/graphql/utils/type_collector.py (100%) rename src/{cpl-graphql => graphql}/pyproject.toml (100%) rename src/{cpl-graphql => graphql}/requirements.dev.txt (100%) rename src/{cpl-graphql => graphql}/requirements.txt (100%) rename src/{cpl-mail => mail}/cpl/mail/__init__.py (100%) rename src/{cpl-mail => mail}/cpl/mail/abc/__init__.py (100%) rename src/{cpl-mail => mail}/cpl/mail/abc/email_client_abc.py (100%) rename src/{cpl-mail => mail}/cpl/mail/email_client.py (100%) rename src/{cpl-mail => mail}/cpl/mail/email_client_settings.py (100%) rename src/{cpl-mail => mail}/cpl/mail/email_model.py (100%) rename src/{cpl-mail => mail}/cpl/mail/logger.py (100%) rename src/{cpl-mail => mail}/cpl/mail/mail_module.py (100%) rename src/{cpl-mail => mail}/pyproject.toml (100%) rename src/{cpl-mail => mail}/requirements.dev.txt (100%) rename src/{cpl-mail => mail}/requirements.txt (100%) rename src/{cpl-query => query}/cpl/query/__init__.py (100%) rename src/{cpl-query => query}/cpl/query/array.py (100%) rename src/{cpl-query => query}/cpl/query/enumerable.py (100%) rename src/{cpl-query => query}/cpl/query/immutable_list.py (100%) rename src/{cpl-query => query}/cpl/query/immutable_set.py (100%) rename src/{cpl-query => query}/cpl/query/list.py (100%) rename src/{cpl-query => query}/cpl/query/ordered_enumerable.py (100%) rename src/{cpl-query => query}/cpl/query/protocol/__init__.py (100%) rename src/{cpl-query => query}/cpl/query/protocol/sequence.py (100%) rename src/{cpl-query => query}/cpl/query/set.py (100%) rename src/{cpl-query => query}/cpl/query/typing.py (100%) rename src/{cpl-query => query}/pyproject.toml (100%) rename src/{cpl-query => query}/requirements.dev.txt (100%) rename src/{cpl-query => query}/requirements.txt (100%) rename src/{cpl-translation => translation}/cpl/translation/__init__.py (100%) rename src/{cpl-translation => translation}/cpl/translation/translate_pipe.py (100%) rename src/{cpl-translation => translation}/cpl/translation/translation_module.py (100%) rename src/{cpl-translation => translation}/cpl/translation/translation_service.py (100%) rename src/{cpl-translation => translation}/cpl/translation/translation_service_abc.py (100%) rename src/{cpl-translation => translation}/cpl/translation/translation_settings.py (100%) rename src/{cpl-translation => translation}/pyproject.toml (100%) rename src/{cpl-translation => translation}/requirements.dev.txt (100%) rename src/{cpl-translation => translation}/requirements.txt (100%) diff --git a/src/cpl-api/cpl/api/__init__.py b/src/api/cpl/api/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/__init__.py rename to src/api/cpl/api/__init__.py diff --git a/src/cpl-api/cpl/api/abc/__init__.py b/src/api/cpl/api/abc/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/abc/__init__.py rename to src/api/cpl/api/abc/__init__.py diff --git a/src/cpl-api/cpl/api/abc/asgi_middleware_abc.py b/src/api/cpl/api/abc/asgi_middleware_abc.py similarity index 100% rename from src/cpl-api/cpl/api/abc/asgi_middleware_abc.py rename to src/api/cpl/api/abc/asgi_middleware_abc.py diff --git a/src/cpl-api/cpl/api/abc/web_app_abc.py b/src/api/cpl/api/abc/web_app_abc.py similarity index 100% rename from src/cpl-api/cpl/api/abc/web_app_abc.py rename to src/api/cpl/api/abc/web_app_abc.py diff --git a/src/cpl-api/cpl/api/api_module.py b/src/api/cpl/api/api_module.py similarity index 100% rename from src/cpl-api/cpl/api/api_module.py rename to src/api/cpl/api/api_module.py diff --git a/src/cpl-api/cpl/api/application/__init__.py b/src/api/cpl/api/application/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/application/__init__.py rename to src/api/cpl/api/application/__init__.py diff --git a/src/cpl-api/cpl/api/application/web_app.py b/src/api/cpl/api/application/web_app.py similarity index 100% rename from src/cpl-api/cpl/api/application/web_app.py rename to src/api/cpl/api/application/web_app.py diff --git a/src/cpl-api/cpl/api/error.py b/src/api/cpl/api/error.py similarity index 100% rename from src/cpl-api/cpl/api/error.py rename to src/api/cpl/api/error.py diff --git a/src/cpl-api/cpl/api/logger.py b/src/api/cpl/api/logger.py similarity index 100% rename from src/cpl-api/cpl/api/logger.py rename to src/api/cpl/api/logger.py diff --git a/src/cpl-api/cpl/api/middleware/__init__.py b/src/api/cpl/api/middleware/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/middleware/__init__.py rename to src/api/cpl/api/middleware/__init__.py diff --git a/src/cpl-api/cpl/api/middleware/authentication.py b/src/api/cpl/api/middleware/authentication.py similarity index 100% rename from src/cpl-api/cpl/api/middleware/authentication.py rename to src/api/cpl/api/middleware/authentication.py diff --git a/src/cpl-api/cpl/api/middleware/authorization.py b/src/api/cpl/api/middleware/authorization.py similarity index 100% rename from src/cpl-api/cpl/api/middleware/authorization.py rename to src/api/cpl/api/middleware/authorization.py diff --git a/src/cpl-api/cpl/api/middleware/logging.py b/src/api/cpl/api/middleware/logging.py similarity index 100% rename from src/cpl-api/cpl/api/middleware/logging.py rename to src/api/cpl/api/middleware/logging.py diff --git a/src/cpl-api/cpl/api/middleware/request.py b/src/api/cpl/api/middleware/request.py similarity index 100% rename from src/cpl-api/cpl/api/middleware/request.py rename to src/api/cpl/api/middleware/request.py diff --git a/src/cpl-api/cpl/api/model/__init__.py b/src/api/cpl/api/model/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/model/__init__.py rename to src/api/cpl/api/model/__init__.py diff --git a/src/cpl-api/cpl/api/model/api_route.py b/src/api/cpl/api/model/api_route.py similarity index 100% rename from src/cpl-api/cpl/api/model/api_route.py rename to src/api/cpl/api/model/api_route.py diff --git a/src/cpl-api/cpl/api/model/policy.py b/src/api/cpl/api/model/policy.py similarity index 100% rename from src/cpl-api/cpl/api/model/policy.py rename to src/api/cpl/api/model/policy.py diff --git a/src/cpl-api/cpl/api/model/validation_match.py b/src/api/cpl/api/model/validation_match.py similarity index 100% rename from src/cpl-api/cpl/api/model/validation_match.py rename to src/api/cpl/api/model/validation_match.py diff --git a/src/cpl-api/cpl/api/model/websocket_route.py b/src/api/cpl/api/model/websocket_route.py similarity index 100% rename from src/cpl-api/cpl/api/model/websocket_route.py rename to src/api/cpl/api/model/websocket_route.py diff --git a/src/cpl-api/cpl/api/registry/__init__.py b/src/api/cpl/api/registry/__init__.py similarity index 100% rename from src/cpl-api/cpl/api/registry/__init__.py rename to src/api/cpl/api/registry/__init__.py diff --git a/src/cpl-api/cpl/api/registry/policy.py b/src/api/cpl/api/registry/policy.py similarity index 100% rename from src/cpl-api/cpl/api/registry/policy.py rename to src/api/cpl/api/registry/policy.py diff --git a/src/cpl-api/cpl/api/registry/route.py b/src/api/cpl/api/registry/route.py similarity index 100% rename from src/cpl-api/cpl/api/registry/route.py rename to src/api/cpl/api/registry/route.py diff --git a/src/cpl-api/cpl/api/router.py b/src/api/cpl/api/router.py similarity index 100% rename from src/cpl-api/cpl/api/router.py rename to src/api/cpl/api/router.py diff --git a/src/cpl-api/cpl/api/settings.py b/src/api/cpl/api/settings.py similarity index 100% rename from src/cpl-api/cpl/api/settings.py rename to src/api/cpl/api/settings.py diff --git a/src/cpl-api/cpl/api/typing.py b/src/api/cpl/api/typing.py similarity index 100% rename from src/cpl-api/cpl/api/typing.py rename to src/api/cpl/api/typing.py diff --git a/src/cpl-api/pyproject.toml b/src/api/pyproject.toml similarity index 100% rename from src/cpl-api/pyproject.toml rename to src/api/pyproject.toml diff --git a/src/cpl-api/requirements.dev.txt b/src/api/requirements.dev.txt similarity index 100% rename from src/cpl-api/requirements.dev.txt rename to src/api/requirements.dev.txt diff --git a/src/cpl-api/requirements.txt b/src/api/requirements.txt similarity index 100% rename from src/cpl-api/requirements.txt rename to src/api/requirements.txt diff --git a/src/cpl-application/cpl/application/__init__.py b/src/application/cpl/application/__init__.py similarity index 100% rename from src/cpl-application/cpl/application/__init__.py rename to src/application/cpl/application/__init__.py diff --git a/src/cpl-application/cpl/application/abc/__init__.py b/src/application/cpl/application/abc/__init__.py similarity index 100% rename from src/cpl-application/cpl/application/abc/__init__.py rename to src/application/cpl/application/abc/__init__.py diff --git a/src/cpl-application/cpl/application/abc/application_abc.py b/src/application/cpl/application/abc/application_abc.py similarity index 100% rename from src/cpl-application/cpl/application/abc/application_abc.py rename to src/application/cpl/application/abc/application_abc.py diff --git a/src/cpl-application/cpl/application/abc/application_extension_abc.py b/src/application/cpl/application/abc/application_extension_abc.py similarity index 100% rename from src/cpl-application/cpl/application/abc/application_extension_abc.py rename to src/application/cpl/application/abc/application_extension_abc.py diff --git a/src/cpl-application/cpl/application/abc/startup_abc.py b/src/application/cpl/application/abc/startup_abc.py similarity index 100% rename from src/cpl-application/cpl/application/abc/startup_abc.py rename to src/application/cpl/application/abc/startup_abc.py diff --git a/src/cpl-application/cpl/application/abc/startup_extension_abc.py b/src/application/cpl/application/abc/startup_extension_abc.py similarity index 100% rename from src/cpl-application/cpl/application/abc/startup_extension_abc.py rename to src/application/cpl/application/abc/startup_extension_abc.py diff --git a/src/cpl-application/cpl/application/application_builder.py b/src/application/cpl/application/application_builder.py similarity index 100% rename from src/cpl-application/cpl/application/application_builder.py rename to src/application/cpl/application/application_builder.py diff --git a/src/cpl-application/cpl/application/host.py b/src/application/cpl/application/host.py similarity index 100% rename from src/cpl-application/cpl/application/host.py rename to src/application/cpl/application/host.py diff --git a/src/cpl-application/pyproject.toml b/src/application/pyproject.toml similarity index 100% rename from src/cpl-application/pyproject.toml rename to src/application/pyproject.toml diff --git a/src/cpl-application/requirements.dev.txt b/src/application/requirements.dev.txt similarity index 100% rename from src/cpl-application/requirements.dev.txt rename to src/application/requirements.dev.txt diff --git a/src/cpl-application/requirements.txt b/src/application/requirements.txt similarity index 100% rename from src/cpl-application/requirements.txt rename to src/application/requirements.txt diff --git a/src/cpl-auth/cpl/auth/__init__.py b/src/auth/cpl/auth/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/__init__.py rename to src/auth/cpl/auth/__init__.py diff --git a/src/cpl-auth/cpl/auth/auth_module.py b/src/auth/cpl/auth/auth_module.py similarity index 100% rename from src/cpl-auth/cpl/auth/auth_module.py rename to src/auth/cpl/auth/auth_module.py diff --git a/src/cpl-auth/cpl/auth/keycloak/__init__.py b/src/auth/cpl/auth/keycloak/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/keycloak/__init__.py rename to src/auth/cpl/auth/keycloak/__init__.py diff --git a/src/cpl-auth/cpl/auth/keycloak/keycloak_admin.py b/src/auth/cpl/auth/keycloak/keycloak_admin.py similarity index 100% rename from src/cpl-auth/cpl/auth/keycloak/keycloak_admin.py rename to src/auth/cpl/auth/keycloak/keycloak_admin.py diff --git a/src/cpl-auth/cpl/auth/keycloak/keycloak_client.py b/src/auth/cpl/auth/keycloak/keycloak_client.py similarity index 100% rename from src/cpl-auth/cpl/auth/keycloak/keycloak_client.py rename to src/auth/cpl/auth/keycloak/keycloak_client.py diff --git a/src/cpl-auth/cpl/auth/keycloak/keycloak_user.py b/src/auth/cpl/auth/keycloak/keycloak_user.py similarity index 100% rename from src/cpl-auth/cpl/auth/keycloak/keycloak_user.py rename to src/auth/cpl/auth/keycloak/keycloak_user.py diff --git a/src/cpl-auth/cpl/auth/keycloak_settings.py b/src/auth/cpl/auth/keycloak_settings.py similarity index 100% rename from src/cpl-auth/cpl/auth/keycloak_settings.py rename to src/auth/cpl/auth/keycloak_settings.py diff --git a/src/cpl-auth/cpl/auth/logger.py b/src/auth/cpl/auth/logger.py similarity index 100% rename from src/cpl-auth/cpl/auth/logger.py rename to src/auth/cpl/auth/logger.py diff --git a/src/cpl-auth/cpl/auth/permission/__init__.py b/src/auth/cpl/auth/permission/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/__init__.py rename to src/auth/cpl/auth/permission/__init__.py diff --git a/src/cpl-auth/cpl/auth/permission/permission_module.py b/src/auth/cpl/auth/permission/permission_module.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/permission_module.py rename to src/auth/cpl/auth/permission/permission_module.py diff --git a/src/cpl-auth/cpl/auth/permission/permission_seeder.py b/src/auth/cpl/auth/permission/permission_seeder.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/permission_seeder.py rename to src/auth/cpl/auth/permission/permission_seeder.py diff --git a/src/cpl-auth/cpl/auth/permission/permissions.py b/src/auth/cpl/auth/permission/permissions.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/permissions.py rename to src/auth/cpl/auth/permission/permissions.py diff --git a/src/cpl-auth/cpl/auth/permission/permissions_registry.py b/src/auth/cpl/auth/permission/permissions_registry.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/permissions_registry.py rename to src/auth/cpl/auth/permission/permissions_registry.py diff --git a/src/cpl-auth/cpl/auth/permission/role_seeder.py b/src/auth/cpl/auth/permission/role_seeder.py similarity index 100% rename from src/cpl-auth/cpl/auth/permission/role_seeder.py rename to src/auth/cpl/auth/permission/role_seeder.py diff --git a/src/cpl-auth/cpl/auth/schema/__init__.py b/src/auth/cpl/auth/schema/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/__init__.py rename to src/auth/cpl/auth/schema/__init__.py diff --git a/src/cpl-auth/cpl/auth/schema/_administration/__init__.py b/src/auth/cpl/auth/schema/_administration/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_administration/__init__.py rename to src/auth/cpl/auth/schema/_administration/__init__.py diff --git a/src/cpl-auth/cpl/auth/schema/_administration/api_key.py b/src/auth/cpl/auth/schema/_administration/api_key.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_administration/api_key.py rename to src/auth/cpl/auth/schema/_administration/api_key.py diff --git a/src/cpl-auth/cpl/auth/schema/_administration/api_key_dao.py b/src/auth/cpl/auth/schema/_administration/api_key_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_administration/api_key_dao.py rename to src/auth/cpl/auth/schema/_administration/api_key_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_administration/user.py b/src/auth/cpl/auth/schema/_administration/user.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_administration/user.py rename to src/auth/cpl/auth/schema/_administration/user.py diff --git a/src/cpl-auth/cpl/auth/schema/_administration/user_dao.py b/src/auth/cpl/auth/schema/_administration/user_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_administration/user_dao.py rename to src/auth/cpl/auth/schema/_administration/user_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/__init__.py b/src/auth/cpl/auth/schema/_permission/__init__.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/__init__.py rename to src/auth/cpl/auth/schema/_permission/__init__.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py b/src/auth/cpl/auth/schema/_permission/api_key_permission.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/api_key_permission.py rename to src/auth/cpl/auth/schema/_permission/api_key_permission.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/api_key_permission_dao.py b/src/auth/cpl/auth/schema/_permission/api_key_permission_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/api_key_permission_dao.py rename to src/auth/cpl/auth/schema/_permission/api_key_permission_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/permission.py b/src/auth/cpl/auth/schema/_permission/permission.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/permission.py rename to src/auth/cpl/auth/schema/_permission/permission.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/permission_dao.py b/src/auth/cpl/auth/schema/_permission/permission_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/permission_dao.py rename to src/auth/cpl/auth/schema/_permission/permission_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role.py b/src/auth/cpl/auth/schema/_permission/role.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role.py rename to src/auth/cpl/auth/schema/_permission/role.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_dao.py b/src/auth/cpl/auth/schema/_permission/role_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role_dao.py rename to src/auth/cpl/auth/schema/_permission/role_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_permission.py b/src/auth/cpl/auth/schema/_permission/role_permission.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role_permission.py rename to src/auth/cpl/auth/schema/_permission/role_permission.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_permission_dao.py b/src/auth/cpl/auth/schema/_permission/role_permission_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role_permission_dao.py rename to src/auth/cpl/auth/schema/_permission/role_permission_dao.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_user.py b/src/auth/cpl/auth/schema/_permission/role_user.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role_user.py rename to src/auth/cpl/auth/schema/_permission/role_user.py diff --git a/src/cpl-auth/cpl/auth/schema/_permission/role_user_dao.py b/src/auth/cpl/auth/schema/_permission/role_user_dao.py similarity index 100% rename from src/cpl-auth/cpl/auth/schema/_permission/role_user_dao.py rename to src/auth/cpl/auth/schema/_permission/role_user_dao.py diff --git a/src/cpl-auth/cpl/auth/scripts/mysql/1-users.sql b/src/auth/cpl/auth/scripts/mysql/1-users.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/mysql/1-users.sql rename to src/auth/cpl/auth/scripts/mysql/1-users.sql diff --git a/src/cpl-auth/cpl/auth/scripts/mysql/2-api-key.sql b/src/auth/cpl/auth/scripts/mysql/2-api-key.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/mysql/2-api-key.sql rename to src/auth/cpl/auth/scripts/mysql/2-api-key.sql diff --git a/src/cpl-auth/cpl/auth/scripts/mysql/3-roles-permissions.sql b/src/auth/cpl/auth/scripts/mysql/3-roles-permissions.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/mysql/3-roles-permissions.sql rename to src/auth/cpl/auth/scripts/mysql/3-roles-permissions.sql diff --git a/src/cpl-auth/cpl/auth/scripts/mysql/4-api-key-permissions.sql b/src/auth/cpl/auth/scripts/mysql/4-api-key-permissions.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/mysql/4-api-key-permissions.sql rename to src/auth/cpl/auth/scripts/mysql/4-api-key-permissions.sql diff --git a/src/cpl-auth/cpl/auth/scripts/postgres/1-users.sql b/src/auth/cpl/auth/scripts/postgres/1-users.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/postgres/1-users.sql rename to src/auth/cpl/auth/scripts/postgres/1-users.sql diff --git a/src/cpl-auth/cpl/auth/scripts/postgres/2-api-key.sql b/src/auth/cpl/auth/scripts/postgres/2-api-key.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/postgres/2-api-key.sql rename to src/auth/cpl/auth/scripts/postgres/2-api-key.sql diff --git a/src/cpl-auth/cpl/auth/scripts/postgres/3-roles-permissions.sql b/src/auth/cpl/auth/scripts/postgres/3-roles-permissions.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/postgres/3-roles-permissions.sql rename to src/auth/cpl/auth/scripts/postgres/3-roles-permissions.sql diff --git a/src/cpl-auth/cpl/auth/scripts/postgres/4-api-key-permissions.sql b/src/auth/cpl/auth/scripts/postgres/4-api-key-permissions.sql similarity index 100% rename from src/cpl-auth/cpl/auth/scripts/postgres/4-api-key-permissions.sql rename to src/auth/cpl/auth/scripts/postgres/4-api-key-permissions.sql diff --git a/src/cpl-auth/pyproject.toml b/src/auth/pyproject.toml similarity index 100% rename from src/cpl-auth/pyproject.toml rename to src/auth/pyproject.toml diff --git a/src/cpl-auth/requirements.dev.txt b/src/auth/requirements.dev.txt similarity index 100% rename from src/cpl-auth/requirements.dev.txt rename to src/auth/requirements.dev.txt diff --git a/src/cpl-auth/requirements.txt b/src/auth/requirements.txt similarity index 100% rename from src/cpl-auth/requirements.txt rename to src/auth/requirements.txt diff --git a/src/cpl-cli/cpl.project.json b/src/cli/cpl.project.json similarity index 100% rename from src/cpl-cli/cpl.project.json rename to src/cli/cpl.project.json diff --git a/src/cpl-cli/cpl/cli/__init__.py b/src/cli/cpl/cli/__init__.py similarity index 100% rename from src/cpl-cli/cpl/cli/__init__.py rename to src/cli/cpl/cli/__init__.py diff --git a/src/cpl-cli/cpl/cli/cli.py b/src/cli/cpl/cli/cli.py similarity index 100% rename from src/cpl-cli/cpl/cli/cli.py rename to src/cli/cpl/cli/cli.py diff --git a/src/cpl-cli/cpl/cli/command/__init__.py b/src/cli/cpl/cli/command/__init__.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/__init__.py rename to src/cli/cpl/cli/command/__init__.py diff --git a/src/cpl-cli/cpl/cli/command/init.py b/src/cli/cpl/cli/command/init.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/init.py rename to src/cli/cpl/cli/command/init.py diff --git a/src/cpl-cli/cpl/cli/command/install.py b/src/cli/cpl/cli/command/install.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/install.py rename to src/cli/cpl/cli/command/install.py diff --git a/src/cpl-cli/cpl/cli/command/uninstall.py b/src/cli/cpl/cli/command/uninstall.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/uninstall.py rename to src/cli/cpl/cli/command/uninstall.py diff --git a/src/cpl-cli/cpl/cli/command/update.py b/src/cli/cpl/cli/command/update.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/update.py rename to src/cli/cpl/cli/command/update.py diff --git a/src/cpl-cli/cpl/cli/command/version.py b/src/cli/cpl/cli/command/version.py similarity index 100% rename from src/cpl-cli/cpl/cli/command/version.py rename to src/cli/cpl/cli/command/version.py diff --git a/src/cpl-cli/cpl/cli/const.py b/src/cli/cpl/cli/const.py similarity index 100% rename from src/cpl-cli/cpl/cli/const.py rename to src/cli/cpl/cli/const.py diff --git a/src/cpl-cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py similarity index 100% rename from src/cpl-cli/cpl/cli/main.py rename to src/cli/cpl/cli/main.py diff --git a/src/cpl-cli/cpl/cli/model/__init__.py b/src/cli/cpl/cli/model/__init__.py similarity index 100% rename from src/cpl-cli/cpl/cli/model/__init__.py rename to src/cli/cpl/cli/model/__init__.py diff --git a/src/cpl-cli/cpl/cli/model/cpl_structure_model.py b/src/cli/cpl/cli/model/cpl_structure_model.py similarity index 100% rename from src/cpl-cli/cpl/cli/model/cpl_structure_model.py rename to src/cli/cpl/cli/model/cpl_structure_model.py diff --git a/src/cpl-cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py similarity index 100% rename from src/cpl-cli/cpl/cli/model/project.py rename to src/cli/cpl/cli/model/project.py diff --git a/src/cpl-cli/cpl/cli/model/workspace.py b/src/cli/cpl/cli/model/workspace.py similarity index 100% rename from src/cpl-cli/cpl/cli/model/workspace.py rename to src/cli/cpl/cli/model/workspace.py diff --git a/src/cpl-cli/cpl/cli/utils/__init__.py b/src/cli/cpl/cli/utils/__init__.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/__init__.py rename to src/cli/cpl/cli/utils/__init__.py diff --git a/src/cpl-cli/cpl/cli/utils/custom_command.py b/src/cli/cpl/cli/utils/custom_command.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/custom_command.py rename to src/cli/cpl/cli/utils/custom_command.py diff --git a/src/cpl-cli/cpl/cli/utils/json.py b/src/cli/cpl/cli/utils/json.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/json.py rename to src/cli/cpl/cli/utils/json.py diff --git a/src/cpl-cli/cpl/cli/utils/pip.py b/src/cli/cpl/cli/utils/pip.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/pip.py rename to src/cli/cpl/cli/utils/pip.py diff --git a/src/cpl-cli/cpl/cli/utils/prompt.py b/src/cli/cpl/cli/utils/prompt.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/prompt.py rename to src/cli/cpl/cli/utils/prompt.py diff --git a/src/cpl-cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/structure.py rename to src/cli/cpl/cli/utils/structure.py diff --git a/src/cpl-cli/cpl/cli/utils/venv.py b/src/cli/cpl/cli/utils/venv.py similarity index 100% rename from src/cpl-cli/cpl/cli/utils/venv.py rename to src/cli/cpl/cli/utils/venv.py diff --git a/src/cpl-cli/pyproject.toml b/src/cli/pyproject.toml similarity index 100% rename from src/cpl-cli/pyproject.toml rename to src/cli/pyproject.toml diff --git a/src/cpl-cli/requirements.dev.txt b/src/cli/requirements.dev.txt similarity index 100% rename from src/cpl-cli/requirements.dev.txt rename to src/cli/requirements.dev.txt diff --git a/src/cpl-cli/requirements.txt b/src/cli/requirements.txt similarity index 100% rename from src/cpl-cli/requirements.txt rename to src/cli/requirements.txt diff --git a/src/cpl-core/cpl.project.json b/src/core/cpl.project.json similarity index 100% rename from src/cpl-core/cpl.project.json rename to src/core/cpl.project.json diff --git a/src/cpl-core/cpl/core/__init__.py b/src/core/cpl/core/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/__init__.py rename to src/core/cpl/core/__init__.py diff --git a/src/cpl-core/cpl/core/abc/__init__.py b/src/core/cpl/core/abc/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/abc/__init__.py rename to src/core/cpl/core/abc/__init__.py diff --git a/src/cpl-core/cpl/core/abc/registry_abc.py b/src/core/cpl/core/abc/registry_abc.py similarity index 100% rename from src/cpl-core/cpl/core/abc/registry_abc.py rename to src/core/cpl/core/abc/registry_abc.py diff --git a/src/cpl-core/cpl/core/configuration/__init__.py b/src/core/cpl/core/configuration/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/configuration/__init__.py rename to src/core/cpl/core/configuration/__init__.py diff --git a/src/cpl-core/cpl/core/configuration/configuration.py b/src/core/cpl/core/configuration/configuration.py similarity index 100% rename from src/cpl-core/cpl/core/configuration/configuration.py rename to src/core/cpl/core/configuration/configuration.py diff --git a/src/cpl-core/cpl/core/configuration/configuration_model_abc.py b/src/core/cpl/core/configuration/configuration_model_abc.py similarity index 100% rename from src/cpl-core/cpl/core/configuration/configuration_model_abc.py rename to src/core/cpl/core/configuration/configuration_model_abc.py diff --git a/src/cpl-core/cpl/core/console/__init__.py b/src/core/cpl/core/console/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/console/__init__.py rename to src/core/cpl/core/console/__init__.py diff --git a/src/cpl-core/cpl/core/console/_call.py b/src/core/cpl/core/console/_call.py similarity index 100% rename from src/cpl-core/cpl/core/console/_call.py rename to src/core/cpl/core/console/_call.py diff --git a/src/cpl-core/cpl/core/console/_spinner.py b/src/core/cpl/core/console/_spinner.py similarity index 100% rename from src/cpl-core/cpl/core/console/_spinner.py rename to src/core/cpl/core/console/_spinner.py diff --git a/src/cpl-core/cpl/core/console/background_color_enum.py b/src/core/cpl/core/console/background_color_enum.py similarity index 100% rename from src/cpl-core/cpl/core/console/background_color_enum.py rename to src/core/cpl/core/console/background_color_enum.py diff --git a/src/cpl-core/cpl/core/console/console.py b/src/core/cpl/core/console/console.py similarity index 100% rename from src/cpl-core/cpl/core/console/console.py rename to src/core/cpl/core/console/console.py diff --git a/src/cpl-core/cpl/core/console/foreground_color_enum.py b/src/core/cpl/core/console/foreground_color_enum.py similarity index 100% rename from src/cpl-core/cpl/core/console/foreground_color_enum.py rename to src/core/cpl/core/console/foreground_color_enum.py diff --git a/src/cpl-core/cpl/core/ctx/__init__.py b/src/core/cpl/core/ctx/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/ctx/__init__.py rename to src/core/cpl/core/ctx/__init__.py diff --git a/src/cpl-core/cpl/core/ctx/user_context.py b/src/core/cpl/core/ctx/user_context.py similarity index 100% rename from src/cpl-core/cpl/core/ctx/user_context.py rename to src/core/cpl/core/ctx/user_context.py diff --git a/src/cpl-core/cpl/core/environment/__init__.py b/src/core/cpl/core/environment/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/environment/__init__.py rename to src/core/cpl/core/environment/__init__.py diff --git a/src/cpl-core/cpl/core/environment/environment.py b/src/core/cpl/core/environment/environment.py similarity index 100% rename from src/cpl-core/cpl/core/environment/environment.py rename to src/core/cpl/core/environment/environment.py diff --git a/src/cpl-core/cpl/core/environment/environment_enum.py b/src/core/cpl/core/environment/environment_enum.py similarity index 100% rename from src/cpl-core/cpl/core/environment/environment_enum.py rename to src/core/cpl/core/environment/environment_enum.py diff --git a/src/cpl-core/cpl/core/errors.py b/src/core/cpl/core/errors.py similarity index 100% rename from src/cpl-core/cpl/core/errors.py rename to src/core/cpl/core/errors.py diff --git a/src/cpl-core/cpl/core/log/__init__.py b/src/core/cpl/core/log/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/log/__init__.py rename to src/core/cpl/core/log/__init__.py diff --git a/src/cpl-core/cpl/core/log/log_level.py b/src/core/cpl/core/log/log_level.py similarity index 100% rename from src/cpl-core/cpl/core/log/log_level.py rename to src/core/cpl/core/log/log_level.py diff --git a/src/cpl-core/cpl/core/log/log_settings.py b/src/core/cpl/core/log/log_settings.py similarity index 100% rename from src/cpl-core/cpl/core/log/log_settings.py rename to src/core/cpl/core/log/log_settings.py diff --git a/src/cpl-core/cpl/core/log/logger.py b/src/core/cpl/core/log/logger.py similarity index 100% rename from src/cpl-core/cpl/core/log/logger.py rename to src/core/cpl/core/log/logger.py diff --git a/src/cpl-core/cpl/core/log/logger_abc.py b/src/core/cpl/core/log/logger_abc.py similarity index 100% rename from src/cpl-core/cpl/core/log/logger_abc.py rename to src/core/cpl/core/log/logger_abc.py diff --git a/src/cpl-core/cpl/core/log/structured_logger.py b/src/core/cpl/core/log/structured_logger.py similarity index 100% rename from src/cpl-core/cpl/core/log/structured_logger.py rename to src/core/cpl/core/log/structured_logger.py diff --git a/src/cpl-core/cpl/core/log/wrapped_logger.py b/src/core/cpl/core/log/wrapped_logger.py similarity index 100% rename from src/cpl-core/cpl/core/log/wrapped_logger.py rename to src/core/cpl/core/log/wrapped_logger.py diff --git a/src/cpl-core/cpl/core/pipes/__init__.py b/src/core/cpl/core/pipes/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/pipes/__init__.py rename to src/core/cpl/core/pipes/__init__.py diff --git a/src/cpl-core/cpl/core/pipes/bool_pipe.py b/src/core/cpl/core/pipes/bool_pipe.py similarity index 100% rename from src/cpl-core/cpl/core/pipes/bool_pipe.py rename to src/core/cpl/core/pipes/bool_pipe.py diff --git a/src/cpl-core/cpl/core/pipes/ip_address_pipe.py b/src/core/cpl/core/pipes/ip_address_pipe.py similarity index 100% rename from src/cpl-core/cpl/core/pipes/ip_address_pipe.py rename to src/core/cpl/core/pipes/ip_address_pipe.py diff --git a/src/cpl-core/cpl/core/pipes/pipe_abc.py b/src/core/cpl/core/pipes/pipe_abc.py similarity index 100% rename from src/cpl-core/cpl/core/pipes/pipe_abc.py rename to src/core/cpl/core/pipes/pipe_abc.py diff --git a/src/cpl-core/cpl/core/time/__init__.py b/src/core/cpl/core/time/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/time/__init__.py rename to src/core/cpl/core/time/__init__.py diff --git a/src/cpl-core/cpl/core/time/cron.py b/src/core/cpl/core/time/cron.py similarity index 100% rename from src/cpl-core/cpl/core/time/cron.py rename to src/core/cpl/core/time/cron.py diff --git a/src/cpl-core/cpl/core/time/time_format_settings.py b/src/core/cpl/core/time/time_format_settings.py similarity index 100% rename from src/cpl-core/cpl/core/time/time_format_settings.py rename to src/core/cpl/core/time/time_format_settings.py diff --git a/src/cpl-core/cpl/core/typing.py b/src/core/cpl/core/typing.py similarity index 100% rename from src/cpl-core/cpl/core/typing.py rename to src/core/cpl/core/typing.py diff --git a/src/cpl-core/cpl/core/utils/__init__.py b/src/core/cpl/core/utils/__init__.py similarity index 100% rename from src/cpl-core/cpl/core/utils/__init__.py rename to src/core/cpl/core/utils/__init__.py diff --git a/src/cpl-core/cpl/core/utils/base64.py b/src/core/cpl/core/utils/base64.py similarity index 100% rename from src/cpl-core/cpl/core/utils/base64.py rename to src/core/cpl/core/utils/base64.py diff --git a/src/cpl-core/cpl/core/utils/benchmark.py b/src/core/cpl/core/utils/benchmark.py similarity index 100% rename from src/cpl-core/cpl/core/utils/benchmark.py rename to src/core/cpl/core/utils/benchmark.py diff --git a/src/cpl-core/cpl/core/utils/cache.py b/src/core/cpl/core/utils/cache.py similarity index 100% rename from src/cpl-core/cpl/core/utils/cache.py rename to src/core/cpl/core/utils/cache.py diff --git a/src/cpl-core/cpl/core/utils/cast.py b/src/core/cpl/core/utils/cast.py similarity index 100% rename from src/cpl-core/cpl/core/utils/cast.py rename to src/core/cpl/core/utils/cast.py diff --git a/src/cpl-core/cpl/core/utils/credential_manager.py b/src/core/cpl/core/utils/credential_manager.py similarity index 100% rename from src/cpl-core/cpl/core/utils/credential_manager.py rename to src/core/cpl/core/utils/credential_manager.py diff --git a/src/cpl-core/cpl/core/utils/get_value.py b/src/core/cpl/core/utils/get_value.py similarity index 100% rename from src/cpl-core/cpl/core/utils/get_value.py rename to src/core/cpl/core/utils/get_value.py diff --git a/src/cpl-core/cpl/core/utils/json_processor.py b/src/core/cpl/core/utils/json_processor.py similarity index 100% rename from src/cpl-core/cpl/core/utils/json_processor.py rename to src/core/cpl/core/utils/json_processor.py diff --git a/src/cpl-core/cpl/core/utils/number.py b/src/core/cpl/core/utils/number.py similarity index 100% rename from src/cpl-core/cpl/core/utils/number.py rename to src/core/cpl/core/utils/number.py diff --git a/src/cpl-core/cpl/core/utils/string.py b/src/core/cpl/core/utils/string.py similarity index 100% rename from src/cpl-core/cpl/core/utils/string.py rename to src/core/cpl/core/utils/string.py diff --git a/src/cpl-core/pyproject.toml b/src/core/pyproject.toml similarity index 100% rename from src/cpl-core/pyproject.toml rename to src/core/pyproject.toml diff --git a/src/cpl-core/requirements.dev.txt b/src/core/requirements.dev.txt similarity index 100% rename from src/cpl-core/requirements.dev.txt rename to src/core/requirements.dev.txt diff --git a/src/cpl-core/requirements.txt b/src/core/requirements.txt similarity index 100% rename from src/cpl-core/requirements.txt rename to src/core/requirements.txt diff --git a/src/cpl-database/cpl/database/__init__.py b/src/database/cpl/database/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/__init__.py rename to src/database/cpl/database/__init__.py diff --git a/src/cpl-database/cpl/database/abc/__init__.py b/src/database/cpl/database/abc/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/abc/__init__.py rename to src/database/cpl/database/abc/__init__.py diff --git a/src/cpl-database/cpl/database/abc/connection_abc.py b/src/database/cpl/database/abc/connection_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/connection_abc.py rename to src/database/cpl/database/abc/connection_abc.py diff --git a/src/cpl-database/cpl/database/abc/data_access_object_abc.py b/src/database/cpl/database/abc/data_access_object_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/data_access_object_abc.py rename to src/database/cpl/database/abc/data_access_object_abc.py diff --git a/src/cpl-database/cpl/database/abc/data_seeder_abc.py b/src/database/cpl/database/abc/data_seeder_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/data_seeder_abc.py rename to src/database/cpl/database/abc/data_seeder_abc.py diff --git a/src/cpl-database/cpl/database/abc/db_context_abc.py b/src/database/cpl/database/abc/db_context_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/db_context_abc.py rename to src/database/cpl/database/abc/db_context_abc.py diff --git a/src/cpl-database/cpl/database/abc/db_join_model_abc.py b/src/database/cpl/database/abc/db_join_model_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/db_join_model_abc.py rename to src/database/cpl/database/abc/db_join_model_abc.py diff --git a/src/cpl-database/cpl/database/abc/db_model_abc.py b/src/database/cpl/database/abc/db_model_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/db_model_abc.py rename to src/database/cpl/database/abc/db_model_abc.py diff --git a/src/cpl-database/cpl/database/abc/db_model_dao_abc.py b/src/database/cpl/database/abc/db_model_dao_abc.py similarity index 100% rename from src/cpl-database/cpl/database/abc/db_model_dao_abc.py rename to src/database/cpl/database/abc/db_model_dao_abc.py diff --git a/src/cpl-database/cpl/database/const.py b/src/database/cpl/database/const.py similarity index 100% rename from src/cpl-database/cpl/database/const.py rename to src/database/cpl/database/const.py diff --git a/src/cpl-database/cpl/database/database_module.py b/src/database/cpl/database/database_module.py similarity index 100% rename from src/cpl-database/cpl/database/database_module.py rename to src/database/cpl/database/database_module.py diff --git a/src/cpl-database/cpl/database/external_data_temp_table_builder.py b/src/database/cpl/database/external_data_temp_table_builder.py similarity index 100% rename from src/cpl-database/cpl/database/external_data_temp_table_builder.py rename to src/database/cpl/database/external_data_temp_table_builder.py diff --git a/src/cpl-database/cpl/database/logger.py b/src/database/cpl/database/logger.py similarity index 100% rename from src/cpl-database/cpl/database/logger.py rename to src/database/cpl/database/logger.py diff --git a/src/cpl-database/cpl/database/model/__init__.py b/src/database/cpl/database/model/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/model/__init__.py rename to src/database/cpl/database/model/__init__.py diff --git a/src/cpl-database/cpl/database/model/database_settings.py b/src/database/cpl/database/model/database_settings.py similarity index 100% rename from src/cpl-database/cpl/database/model/database_settings.py rename to src/database/cpl/database/model/database_settings.py diff --git a/src/cpl-database/cpl/database/model/migration.py b/src/database/cpl/database/model/migration.py similarity index 100% rename from src/cpl-database/cpl/database/model/migration.py rename to src/database/cpl/database/model/migration.py diff --git a/src/cpl-database/cpl/database/model/server_type.py b/src/database/cpl/database/model/server_type.py similarity index 100% rename from src/cpl-database/cpl/database/model/server_type.py rename to src/database/cpl/database/model/server_type.py diff --git a/src/cpl-database/cpl/database/mysql/__init__.py b/src/database/cpl/database/mysql/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/mysql/__init__.py rename to src/database/cpl/database/mysql/__init__.py diff --git a/src/cpl-database/cpl/database/mysql/connection.py b/src/database/cpl/database/mysql/connection.py similarity index 100% rename from src/cpl-database/cpl/database/mysql/connection.py rename to src/database/cpl/database/mysql/connection.py diff --git a/src/cpl-database/cpl/database/mysql/db_context.py b/src/database/cpl/database/mysql/db_context.py similarity index 100% rename from src/cpl-database/cpl/database/mysql/db_context.py rename to src/database/cpl/database/mysql/db_context.py diff --git a/src/cpl-database/cpl/database/mysql/mysql_module.py b/src/database/cpl/database/mysql/mysql_module.py similarity index 100% rename from src/cpl-database/cpl/database/mysql/mysql_module.py rename to src/database/cpl/database/mysql/mysql_module.py diff --git a/src/cpl-database/cpl/database/mysql/mysql_pool.py b/src/database/cpl/database/mysql/mysql_pool.py similarity index 100% rename from src/cpl-database/cpl/database/mysql/mysql_pool.py rename to src/database/cpl/database/mysql/mysql_pool.py diff --git a/src/cpl-database/cpl/database/postgres/__init__.py b/src/database/cpl/database/postgres/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/postgres/__init__.py rename to src/database/cpl/database/postgres/__init__.py diff --git a/src/cpl-database/cpl/database/postgres/db_context.py b/src/database/cpl/database/postgres/db_context.py similarity index 100% rename from src/cpl-database/cpl/database/postgres/db_context.py rename to src/database/cpl/database/postgres/db_context.py diff --git a/src/cpl-database/cpl/database/postgres/postgres_module.py b/src/database/cpl/database/postgres/postgres_module.py similarity index 100% rename from src/cpl-database/cpl/database/postgres/postgres_module.py rename to src/database/cpl/database/postgres/postgres_module.py diff --git a/src/cpl-database/cpl/database/postgres/postgres_pool.py b/src/database/cpl/database/postgres/postgres_pool.py similarity index 100% rename from src/cpl-database/cpl/database/postgres/postgres_pool.py rename to src/database/cpl/database/postgres/postgres_pool.py diff --git a/src/cpl-database/cpl/database/postgres/sql_select_builder.py b/src/database/cpl/database/postgres/sql_select_builder.py similarity index 100% rename from src/cpl-database/cpl/database/postgres/sql_select_builder.py rename to src/database/cpl/database/postgres/sql_select_builder.py diff --git a/src/cpl-database/cpl/database/schema/__init__.py b/src/database/cpl/database/schema/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/schema/__init__.py rename to src/database/cpl/database/schema/__init__.py diff --git a/src/cpl-database/cpl/database/schema/executed_migration.py b/src/database/cpl/database/schema/executed_migration.py similarity index 100% rename from src/cpl-database/cpl/database/schema/executed_migration.py rename to src/database/cpl/database/schema/executed_migration.py diff --git a/src/cpl-database/cpl/database/schema/executed_migration_dao.py b/src/database/cpl/database/schema/executed_migration_dao.py similarity index 100% rename from src/cpl-database/cpl/database/schema/executed_migration_dao.py rename to src/database/cpl/database/schema/executed_migration_dao.py diff --git a/src/cpl-database/cpl/database/scripts/mysql/0-cpl-initial.sql b/src/database/cpl/database/scripts/mysql/0-cpl-initial.sql similarity index 100% rename from src/cpl-database/cpl/database/scripts/mysql/0-cpl-initial.sql rename to src/database/cpl/database/scripts/mysql/0-cpl-initial.sql diff --git a/src/cpl-database/cpl/database/scripts/mysql/trigger.txt b/src/database/cpl/database/scripts/mysql/trigger.txt similarity index 100% rename from src/cpl-database/cpl/database/scripts/mysql/trigger.txt rename to src/database/cpl/database/scripts/mysql/trigger.txt diff --git a/src/cpl-database/cpl/database/scripts/postgres/0-cpl-initial.sql b/src/database/cpl/database/scripts/postgres/0-cpl-initial.sql similarity index 100% rename from src/cpl-database/cpl/database/scripts/postgres/0-cpl-initial.sql rename to src/database/cpl/database/scripts/postgres/0-cpl-initial.sql diff --git a/src/cpl-database/cpl/database/service/__init__.py b/src/database/cpl/database/service/__init__.py similarity index 100% rename from src/cpl-database/cpl/database/service/__init__.py rename to src/database/cpl/database/service/__init__.py diff --git a/src/cpl-database/cpl/database/service/migration_service.py b/src/database/cpl/database/service/migration_service.py similarity index 100% rename from src/cpl-database/cpl/database/service/migration_service.py rename to src/database/cpl/database/service/migration_service.py diff --git a/src/cpl-database/cpl/database/service/seeder_service.py b/src/database/cpl/database/service/seeder_service.py similarity index 100% rename from src/cpl-database/cpl/database/service/seeder_service.py rename to src/database/cpl/database/service/seeder_service.py diff --git a/src/cpl-database/cpl/database/table_manager.py b/src/database/cpl/database/table_manager.py similarity index 100% rename from src/cpl-database/cpl/database/table_manager.py rename to src/database/cpl/database/table_manager.py diff --git a/src/cpl-database/cpl/database/typing.py b/src/database/cpl/database/typing.py similarity index 100% rename from src/cpl-database/cpl/database/typing.py rename to src/database/cpl/database/typing.py diff --git a/src/cpl-database/pyproject.toml b/src/database/pyproject.toml similarity index 100% rename from src/cpl-database/pyproject.toml rename to src/database/pyproject.toml diff --git a/src/cpl-database/requirements.dev.txt b/src/database/requirements.dev.txt similarity index 100% rename from src/cpl-database/requirements.dev.txt rename to src/database/requirements.dev.txt diff --git a/src/cpl-database/requirements.txt b/src/database/requirements.txt similarity index 100% rename from src/cpl-database/requirements.txt rename to src/database/requirements.txt diff --git a/src/cpl-dependency/cpl/dependency/__init__.py b/src/dependency/cpl/dependency/__init__.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/__init__.py rename to src/dependency/cpl/dependency/__init__.py diff --git a/src/cpl-dependency/cpl/dependency/context.py b/src/dependency/cpl/dependency/context.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/context.py rename to src/dependency/cpl/dependency/context.py diff --git a/src/cpl-dependency/cpl/dependency/event_bus.py b/src/dependency/cpl/dependency/event_bus.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/event_bus.py rename to src/dependency/cpl/dependency/event_bus.py diff --git a/src/cpl-dependency/cpl/dependency/hosted/__init__.py b/src/dependency/cpl/dependency/hosted/__init__.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/hosted/__init__.py rename to src/dependency/cpl/dependency/hosted/__init__.py diff --git a/src/cpl-dependency/cpl/dependency/hosted/cronjob.py b/src/dependency/cpl/dependency/hosted/cronjob.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/hosted/cronjob.py rename to src/dependency/cpl/dependency/hosted/cronjob.py diff --git a/src/cpl-dependency/cpl/dependency/hosted/hosted_service.py b/src/dependency/cpl/dependency/hosted/hosted_service.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/hosted/hosted_service.py rename to src/dependency/cpl/dependency/hosted/hosted_service.py diff --git a/src/cpl-dependency/cpl/dependency/hosted/startup_task.py b/src/dependency/cpl/dependency/hosted/startup_task.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/hosted/startup_task.py rename to src/dependency/cpl/dependency/hosted/startup_task.py diff --git a/src/cpl-dependency/cpl/dependency/inject.py b/src/dependency/cpl/dependency/inject.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/inject.py rename to src/dependency/cpl/dependency/inject.py diff --git a/src/cpl-dependency/cpl/dependency/module/__init__.py b/src/dependency/cpl/dependency/module/__init__.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/module/__init__.py rename to src/dependency/cpl/dependency/module/__init__.py diff --git a/src/cpl-dependency/cpl/dependency/module/module.py b/src/dependency/cpl/dependency/module/module.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/module/module.py rename to src/dependency/cpl/dependency/module/module.py diff --git a/src/cpl-dependency/cpl/dependency/module/module_abc.py b/src/dependency/cpl/dependency/module/module_abc.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/module/module_abc.py rename to src/dependency/cpl/dependency/module/module_abc.py diff --git a/src/cpl-dependency/cpl/dependency/module/module_protocol.py b/src/dependency/cpl/dependency/module/module_protocol.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/module/module_protocol.py rename to src/dependency/cpl/dependency/module/module_protocol.py diff --git a/src/cpl-dependency/cpl/dependency/service_collection.py b/src/dependency/cpl/dependency/service_collection.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/service_collection.py rename to src/dependency/cpl/dependency/service_collection.py diff --git a/src/cpl-dependency/cpl/dependency/service_descriptor.py b/src/dependency/cpl/dependency/service_descriptor.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/service_descriptor.py rename to src/dependency/cpl/dependency/service_descriptor.py diff --git a/src/cpl-dependency/cpl/dependency/service_lifetime.py b/src/dependency/cpl/dependency/service_lifetime.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/service_lifetime.py rename to src/dependency/cpl/dependency/service_lifetime.py diff --git a/src/cpl-dependency/cpl/dependency/service_provider.py b/src/dependency/cpl/dependency/service_provider.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/service_provider.py rename to src/dependency/cpl/dependency/service_provider.py diff --git a/src/cpl-dependency/cpl/dependency/typing.py b/src/dependency/cpl/dependency/typing.py similarity index 100% rename from src/cpl-dependency/cpl/dependency/typing.py rename to src/dependency/cpl/dependency/typing.py diff --git a/src/cpl-dependency/pyproject.toml b/src/dependency/pyproject.toml similarity index 100% rename from src/cpl-dependency/pyproject.toml rename to src/dependency/pyproject.toml diff --git a/src/cpl-dependency/requirements.dev.txt b/src/dependency/requirements.dev.txt similarity index 100% rename from src/cpl-dependency/requirements.dev.txt rename to src/dependency/requirements.dev.txt diff --git a/src/cpl-dependency/requirements.txt b/src/dependency/requirements.txt similarity index 100% rename from src/cpl-dependency/requirements.txt rename to src/dependency/requirements.txt diff --git a/src/cpl-graphql/cpl/graphql/__init__.py b/src/graphql/cpl/graphql/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/__init__.py rename to src/graphql/cpl/graphql/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/_endpoints/__init__.py b/src/graphql/cpl/graphql/_endpoints/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/_endpoints/__init__.py rename to src/graphql/cpl/graphql/_endpoints/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/_endpoints/graphiql.py b/src/graphql/cpl/graphql/_endpoints/graphiql.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/_endpoints/graphiql.py rename to src/graphql/cpl/graphql/_endpoints/graphiql.py diff --git a/src/cpl-graphql/cpl/graphql/_endpoints/graphql.py b/src/graphql/cpl/graphql/_endpoints/graphql.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/_endpoints/graphql.py rename to src/graphql/cpl/graphql/_endpoints/graphql.py diff --git a/src/cpl-graphql/cpl/graphql/_endpoints/lazy_graphql_app.py b/src/graphql/cpl/graphql/_endpoints/lazy_graphql_app.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/_endpoints/lazy_graphql_app.py rename to src/graphql/cpl/graphql/_endpoints/lazy_graphql_app.py diff --git a/src/cpl-graphql/cpl/graphql/_endpoints/playground.py b/src/graphql/cpl/graphql/_endpoints/playground.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/_endpoints/playground.py rename to src/graphql/cpl/graphql/_endpoints/playground.py diff --git a/src/cpl-graphql/cpl/graphql/abc/__init__.py b/src/graphql/cpl/graphql/abc/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/abc/__init__.py rename to src/graphql/cpl/graphql/abc/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/abc/query_abc.py b/src/graphql/cpl/graphql/abc/query_abc.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/abc/query_abc.py rename to src/graphql/cpl/graphql/abc/query_abc.py diff --git a/src/cpl-graphql/cpl/graphql/abc/strawberry_protocol.py b/src/graphql/cpl/graphql/abc/strawberry_protocol.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/abc/strawberry_protocol.py rename to src/graphql/cpl/graphql/abc/strawberry_protocol.py diff --git a/src/cpl-graphql/cpl/graphql/application/__init__.py b/src/graphql/cpl/graphql/application/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/application/__init__.py rename to src/graphql/cpl/graphql/application/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/application/graphql_app.py b/src/graphql/cpl/graphql/application/graphql_app.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/application/graphql_app.py rename to src/graphql/cpl/graphql/application/graphql_app.py diff --git a/src/cpl-graphql/cpl/graphql/auth/__init__.py b/src/graphql/cpl/graphql/auth/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/__init__.py rename to src/graphql/cpl/graphql/auth/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/__init__.py b/src/graphql/cpl/graphql/auth/api_key/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/__init__.py rename to src/graphql/cpl/graphql/auth/api_key/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/api_key_filter.py b/src/graphql/cpl/graphql/auth/api_key/api_key_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/api_key_filter.py rename to src/graphql/cpl/graphql/auth/api_key/api_key_filter.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/api_key_graph_type.py b/src/graphql/cpl/graphql/auth/api_key/api_key_graph_type.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/api_key_graph_type.py rename to src/graphql/cpl/graphql/auth/api_key/api_key_graph_type.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/api_key_input.py b/src/graphql/cpl/graphql/auth/api_key/api_key_input.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/api_key_input.py rename to src/graphql/cpl/graphql/auth/api_key/api_key_input.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/api_key_mutation.py b/src/graphql/cpl/graphql/auth/api_key/api_key_mutation.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/api_key_mutation.py rename to src/graphql/cpl/graphql/auth/api_key/api_key_mutation.py diff --git a/src/cpl-graphql/cpl/graphql/auth/api_key/api_key_sort.py b/src/graphql/cpl/graphql/auth/api_key/api_key_sort.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/api_key/api_key_sort.py rename to src/graphql/cpl/graphql/auth/api_key/api_key_sort.py diff --git a/src/cpl-graphql/cpl/graphql/auth/graphql_auth_module.py b/src/graphql/cpl/graphql/auth/graphql_auth_module.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/graphql_auth_module.py rename to src/graphql/cpl/graphql/auth/graphql_auth_module.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/__init__.py b/src/graphql/cpl/graphql/auth/role/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/__init__.py rename to src/graphql/cpl/graphql/auth/role/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/role_filter.py b/src/graphql/cpl/graphql/auth/role/role_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/role_filter.py rename to src/graphql/cpl/graphql/auth/role/role_filter.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/role_graph_type.py b/src/graphql/cpl/graphql/auth/role/role_graph_type.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/role_graph_type.py rename to src/graphql/cpl/graphql/auth/role/role_graph_type.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/role_input.py b/src/graphql/cpl/graphql/auth/role/role_input.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/role_input.py rename to src/graphql/cpl/graphql/auth/role/role_input.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/role_mutation.py b/src/graphql/cpl/graphql/auth/role/role_mutation.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/role_mutation.py rename to src/graphql/cpl/graphql/auth/role/role_mutation.py diff --git a/src/cpl-graphql/cpl/graphql/auth/role/role_sort.py b/src/graphql/cpl/graphql/auth/role/role_sort.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/role/role_sort.py rename to src/graphql/cpl/graphql/auth/role/role_sort.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/__init__.py b/src/graphql/cpl/graphql/auth/user/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/__init__.py rename to src/graphql/cpl/graphql/auth/user/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/user_filter.py b/src/graphql/cpl/graphql/auth/user/user_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/user_filter.py rename to src/graphql/cpl/graphql/auth/user/user_filter.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/user_graph_type.py b/src/graphql/cpl/graphql/auth/user/user_graph_type.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/user_graph_type.py rename to src/graphql/cpl/graphql/auth/user/user_graph_type.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/user_input.py b/src/graphql/cpl/graphql/auth/user/user_input.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/user_input.py rename to src/graphql/cpl/graphql/auth/user/user_input.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/user_mutation.py b/src/graphql/cpl/graphql/auth/user/user_mutation.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/user_mutation.py rename to src/graphql/cpl/graphql/auth/user/user_mutation.py diff --git a/src/cpl-graphql/cpl/graphql/auth/user/user_sort.py b/src/graphql/cpl/graphql/auth/user/user_sort.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/auth/user/user_sort.py rename to src/graphql/cpl/graphql/auth/user/user_sort.py diff --git a/src/cpl-graphql/cpl/graphql/error.py b/src/graphql/cpl/graphql/error.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/error.py rename to src/graphql/cpl/graphql/error.py diff --git a/src/cpl-graphql/cpl/graphql/event_bus/__init__.py b/src/graphql/cpl/graphql/event_bus/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/event_bus/__init__.py rename to src/graphql/cpl/graphql/event_bus/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/event_bus/memory.py b/src/graphql/cpl/graphql/event_bus/memory.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/event_bus/memory.py rename to src/graphql/cpl/graphql/event_bus/memory.py diff --git a/src/cpl-graphql/cpl/graphql/graphql_module.py b/src/graphql/cpl/graphql/graphql_module.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/graphql_module.py rename to src/graphql/cpl/graphql/graphql_module.py diff --git a/src/cpl-graphql/cpl/graphql/query_context.py b/src/graphql/cpl/graphql/query_context.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/query_context.py rename to src/graphql/cpl/graphql/query_context.py diff --git a/src/cpl-graphql/cpl/graphql/schema/__init__.py b/src/graphql/cpl/graphql/schema/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/__init__.py rename to src/graphql/cpl/graphql/schema/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/schema/argument.py b/src/graphql/cpl/graphql/schema/argument.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/argument.py rename to src/graphql/cpl/graphql/schema/argument.py diff --git a/src/cpl-graphql/cpl/graphql/schema/collection.py b/src/graphql/cpl/graphql/schema/collection.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/collection.py rename to src/graphql/cpl/graphql/schema/collection.py diff --git a/src/cpl-graphql/cpl/graphql/schema/db_model_graph_type.py b/src/graphql/cpl/graphql/schema/db_model_graph_type.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/db_model_graph_type.py rename to src/graphql/cpl/graphql/schema/db_model_graph_type.py diff --git a/src/cpl-graphql/cpl/graphql/schema/field.py b/src/graphql/cpl/graphql/schema/field.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/field.py rename to src/graphql/cpl/graphql/schema/field.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/__init__.py b/src/graphql/cpl/graphql/schema/filter/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/__init__.py rename to src/graphql/cpl/graphql/schema/filter/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/bool_filter.py b/src/graphql/cpl/graphql/schema/filter/bool_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/bool_filter.py rename to src/graphql/cpl/graphql/schema/filter/bool_filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/date_filter.py b/src/graphql/cpl/graphql/schema/filter/date_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/date_filter.py rename to src/graphql/cpl/graphql/schema/filter/date_filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/db_model_filter.py b/src/graphql/cpl/graphql/schema/filter/db_model_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/db_model_filter.py rename to src/graphql/cpl/graphql/schema/filter/db_model_filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/filter.py b/src/graphql/cpl/graphql/schema/filter/filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/filter.py rename to src/graphql/cpl/graphql/schema/filter/filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/int_filter.py b/src/graphql/cpl/graphql/schema/filter/int_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/int_filter.py rename to src/graphql/cpl/graphql/schema/filter/int_filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/filter/string_filter.py b/src/graphql/cpl/graphql/schema/filter/string_filter.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/filter/string_filter.py rename to src/graphql/cpl/graphql/schema/filter/string_filter.py diff --git a/src/cpl-graphql/cpl/graphql/schema/graph_type.py b/src/graphql/cpl/graphql/schema/graph_type.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/graph_type.py rename to src/graphql/cpl/graphql/schema/graph_type.py diff --git a/src/cpl-graphql/cpl/graphql/schema/input.py b/src/graphql/cpl/graphql/schema/input.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/input.py rename to src/graphql/cpl/graphql/schema/input.py diff --git a/src/cpl-graphql/cpl/graphql/schema/mutation.py b/src/graphql/cpl/graphql/schema/mutation.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/mutation.py rename to src/graphql/cpl/graphql/schema/mutation.py diff --git a/src/cpl-graphql/cpl/graphql/schema/query.py b/src/graphql/cpl/graphql/schema/query.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/query.py rename to src/graphql/cpl/graphql/schema/query.py diff --git a/src/cpl-graphql/cpl/graphql/schema/root_mutation.py b/src/graphql/cpl/graphql/schema/root_mutation.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/root_mutation.py rename to src/graphql/cpl/graphql/schema/root_mutation.py diff --git a/src/cpl-graphql/cpl/graphql/schema/root_query.py b/src/graphql/cpl/graphql/schema/root_query.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/root_query.py rename to src/graphql/cpl/graphql/schema/root_query.py diff --git a/src/cpl-graphql/cpl/graphql/schema/root_subscription.py b/src/graphql/cpl/graphql/schema/root_subscription.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/root_subscription.py rename to src/graphql/cpl/graphql/schema/root_subscription.py diff --git a/src/cpl-graphql/cpl/graphql/schema/sort/__init__.py b/src/graphql/cpl/graphql/schema/sort/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/sort/__init__.py rename to src/graphql/cpl/graphql/schema/sort/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/schema/sort/db_model_sort.py b/src/graphql/cpl/graphql/schema/sort/db_model_sort.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/sort/db_model_sort.py rename to src/graphql/cpl/graphql/schema/sort/db_model_sort.py diff --git a/src/cpl-graphql/cpl/graphql/schema/sort/sort.py b/src/graphql/cpl/graphql/schema/sort/sort.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/sort/sort.py rename to src/graphql/cpl/graphql/schema/sort/sort.py diff --git a/src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py b/src/graphql/cpl/graphql/schema/sort/sort_order.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/sort/sort_order.py rename to src/graphql/cpl/graphql/schema/sort/sort_order.py diff --git a/src/cpl-graphql/cpl/graphql/schema/subscription.py b/src/graphql/cpl/graphql/schema/subscription.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/subscription.py rename to src/graphql/cpl/graphql/schema/subscription.py diff --git a/src/cpl-graphql/cpl/graphql/schema/subscription_field.py b/src/graphql/cpl/graphql/schema/subscription_field.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/schema/subscription_field.py rename to src/graphql/cpl/graphql/schema/subscription_field.py diff --git a/src/cpl-graphql/cpl/graphql/service/__init__.py b/src/graphql/cpl/graphql/service/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/service/__init__.py rename to src/graphql/cpl/graphql/service/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/service/graphql.py b/src/graphql/cpl/graphql/service/graphql.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/service/graphql.py rename to src/graphql/cpl/graphql/service/graphql.py diff --git a/src/cpl-graphql/cpl/graphql/service/schema.py b/src/graphql/cpl/graphql/service/schema.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/service/schema.py rename to src/graphql/cpl/graphql/service/schema.py diff --git a/src/cpl-graphql/cpl/graphql/typing.py b/src/graphql/cpl/graphql/typing.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/typing.py rename to src/graphql/cpl/graphql/typing.py diff --git a/src/cpl-graphql/cpl/graphql/utils/__init__.py b/src/graphql/cpl/graphql/utils/__init__.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/utils/__init__.py rename to src/graphql/cpl/graphql/utils/__init__.py diff --git a/src/cpl-graphql/cpl/graphql/utils/name_pipe.py b/src/graphql/cpl/graphql/utils/name_pipe.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/utils/name_pipe.py rename to src/graphql/cpl/graphql/utils/name_pipe.py diff --git a/src/cpl-graphql/cpl/graphql/utils/type_collector.py b/src/graphql/cpl/graphql/utils/type_collector.py similarity index 100% rename from src/cpl-graphql/cpl/graphql/utils/type_collector.py rename to src/graphql/cpl/graphql/utils/type_collector.py diff --git a/src/cpl-graphql/pyproject.toml b/src/graphql/pyproject.toml similarity index 100% rename from src/cpl-graphql/pyproject.toml rename to src/graphql/pyproject.toml diff --git a/src/cpl-graphql/requirements.dev.txt b/src/graphql/requirements.dev.txt similarity index 100% rename from src/cpl-graphql/requirements.dev.txt rename to src/graphql/requirements.dev.txt diff --git a/src/cpl-graphql/requirements.txt b/src/graphql/requirements.txt similarity index 100% rename from src/cpl-graphql/requirements.txt rename to src/graphql/requirements.txt diff --git a/src/cpl-mail/cpl/mail/__init__.py b/src/mail/cpl/mail/__init__.py similarity index 100% rename from src/cpl-mail/cpl/mail/__init__.py rename to src/mail/cpl/mail/__init__.py diff --git a/src/cpl-mail/cpl/mail/abc/__init__.py b/src/mail/cpl/mail/abc/__init__.py similarity index 100% rename from src/cpl-mail/cpl/mail/abc/__init__.py rename to src/mail/cpl/mail/abc/__init__.py diff --git a/src/cpl-mail/cpl/mail/abc/email_client_abc.py b/src/mail/cpl/mail/abc/email_client_abc.py similarity index 100% rename from src/cpl-mail/cpl/mail/abc/email_client_abc.py rename to src/mail/cpl/mail/abc/email_client_abc.py diff --git a/src/cpl-mail/cpl/mail/email_client.py b/src/mail/cpl/mail/email_client.py similarity index 100% rename from src/cpl-mail/cpl/mail/email_client.py rename to src/mail/cpl/mail/email_client.py diff --git a/src/cpl-mail/cpl/mail/email_client_settings.py b/src/mail/cpl/mail/email_client_settings.py similarity index 100% rename from src/cpl-mail/cpl/mail/email_client_settings.py rename to src/mail/cpl/mail/email_client_settings.py diff --git a/src/cpl-mail/cpl/mail/email_model.py b/src/mail/cpl/mail/email_model.py similarity index 100% rename from src/cpl-mail/cpl/mail/email_model.py rename to src/mail/cpl/mail/email_model.py diff --git a/src/cpl-mail/cpl/mail/logger.py b/src/mail/cpl/mail/logger.py similarity index 100% rename from src/cpl-mail/cpl/mail/logger.py rename to src/mail/cpl/mail/logger.py diff --git a/src/cpl-mail/cpl/mail/mail_module.py b/src/mail/cpl/mail/mail_module.py similarity index 100% rename from src/cpl-mail/cpl/mail/mail_module.py rename to src/mail/cpl/mail/mail_module.py diff --git a/src/cpl-mail/pyproject.toml b/src/mail/pyproject.toml similarity index 100% rename from src/cpl-mail/pyproject.toml rename to src/mail/pyproject.toml diff --git a/src/cpl-mail/requirements.dev.txt b/src/mail/requirements.dev.txt similarity index 100% rename from src/cpl-mail/requirements.dev.txt rename to src/mail/requirements.dev.txt diff --git a/src/cpl-mail/requirements.txt b/src/mail/requirements.txt similarity index 100% rename from src/cpl-mail/requirements.txt rename to src/mail/requirements.txt diff --git a/src/cpl-query/cpl/query/__init__.py b/src/query/cpl/query/__init__.py similarity index 100% rename from src/cpl-query/cpl/query/__init__.py rename to src/query/cpl/query/__init__.py diff --git a/src/cpl-query/cpl/query/array.py b/src/query/cpl/query/array.py similarity index 100% rename from src/cpl-query/cpl/query/array.py rename to src/query/cpl/query/array.py diff --git a/src/cpl-query/cpl/query/enumerable.py b/src/query/cpl/query/enumerable.py similarity index 100% rename from src/cpl-query/cpl/query/enumerable.py rename to src/query/cpl/query/enumerable.py diff --git a/src/cpl-query/cpl/query/immutable_list.py b/src/query/cpl/query/immutable_list.py similarity index 100% rename from src/cpl-query/cpl/query/immutable_list.py rename to src/query/cpl/query/immutable_list.py diff --git a/src/cpl-query/cpl/query/immutable_set.py b/src/query/cpl/query/immutable_set.py similarity index 100% rename from src/cpl-query/cpl/query/immutable_set.py rename to src/query/cpl/query/immutable_set.py diff --git a/src/cpl-query/cpl/query/list.py b/src/query/cpl/query/list.py similarity index 100% rename from src/cpl-query/cpl/query/list.py rename to src/query/cpl/query/list.py diff --git a/src/cpl-query/cpl/query/ordered_enumerable.py b/src/query/cpl/query/ordered_enumerable.py similarity index 100% rename from src/cpl-query/cpl/query/ordered_enumerable.py rename to src/query/cpl/query/ordered_enumerable.py diff --git a/src/cpl-query/cpl/query/protocol/__init__.py b/src/query/cpl/query/protocol/__init__.py similarity index 100% rename from src/cpl-query/cpl/query/protocol/__init__.py rename to src/query/cpl/query/protocol/__init__.py diff --git a/src/cpl-query/cpl/query/protocol/sequence.py b/src/query/cpl/query/protocol/sequence.py similarity index 100% rename from src/cpl-query/cpl/query/protocol/sequence.py rename to src/query/cpl/query/protocol/sequence.py diff --git a/src/cpl-query/cpl/query/set.py b/src/query/cpl/query/set.py similarity index 100% rename from src/cpl-query/cpl/query/set.py rename to src/query/cpl/query/set.py diff --git a/src/cpl-query/cpl/query/typing.py b/src/query/cpl/query/typing.py similarity index 100% rename from src/cpl-query/cpl/query/typing.py rename to src/query/cpl/query/typing.py diff --git a/src/cpl-query/pyproject.toml b/src/query/pyproject.toml similarity index 100% rename from src/cpl-query/pyproject.toml rename to src/query/pyproject.toml diff --git a/src/cpl-query/requirements.dev.txt b/src/query/requirements.dev.txt similarity index 100% rename from src/cpl-query/requirements.dev.txt rename to src/query/requirements.dev.txt diff --git a/src/cpl-query/requirements.txt b/src/query/requirements.txt similarity index 100% rename from src/cpl-query/requirements.txt rename to src/query/requirements.txt diff --git a/src/cpl-translation/cpl/translation/__init__.py b/src/translation/cpl/translation/__init__.py similarity index 100% rename from src/cpl-translation/cpl/translation/__init__.py rename to src/translation/cpl/translation/__init__.py diff --git a/src/cpl-translation/cpl/translation/translate_pipe.py b/src/translation/cpl/translation/translate_pipe.py similarity index 100% rename from src/cpl-translation/cpl/translation/translate_pipe.py rename to src/translation/cpl/translation/translate_pipe.py diff --git a/src/cpl-translation/cpl/translation/translation_module.py b/src/translation/cpl/translation/translation_module.py similarity index 100% rename from src/cpl-translation/cpl/translation/translation_module.py rename to src/translation/cpl/translation/translation_module.py diff --git a/src/cpl-translation/cpl/translation/translation_service.py b/src/translation/cpl/translation/translation_service.py similarity index 100% rename from src/cpl-translation/cpl/translation/translation_service.py rename to src/translation/cpl/translation/translation_service.py diff --git a/src/cpl-translation/cpl/translation/translation_service_abc.py b/src/translation/cpl/translation/translation_service_abc.py similarity index 100% rename from src/cpl-translation/cpl/translation/translation_service_abc.py rename to src/translation/cpl/translation/translation_service_abc.py diff --git a/src/cpl-translation/cpl/translation/translation_settings.py b/src/translation/cpl/translation/translation_settings.py similarity index 100% rename from src/cpl-translation/cpl/translation/translation_settings.py rename to src/translation/cpl/translation/translation_settings.py diff --git a/src/cpl-translation/pyproject.toml b/src/translation/pyproject.toml similarity index 100% rename from src/cpl-translation/pyproject.toml rename to src/translation/pyproject.toml diff --git a/src/cpl-translation/requirements.dev.txt b/src/translation/requirements.dev.txt similarity index 100% rename from src/cpl-translation/requirements.dev.txt rename to src/translation/requirements.dev.txt diff --git a/src/cpl-translation/requirements.txt b/src/translation/requirements.txt similarity index 100% rename from src/cpl-translation/requirements.txt rename to src/translation/requirements.txt From 104b7367780c4398c658b48cfdf15cdf696c1b53 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 09:39:46 +0200 Subject: [PATCH 06/22] Moved commands by context --- src/cli/cpl/cli/command/package/__init__.py | 0 src/cli/cpl/cli/command/{ => package}/install.py | 0 src/cli/cpl/cli/command/{ => package}/uninstall.py | 0 src/cli/cpl/cli/command/{ => package}/update.py | 0 src/cli/cpl/cli/command/structure/__init__.py | 0 src/cli/cpl/cli/command/{ => structure}/init.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/cli/cpl/cli/command/package/__init__.py rename src/cli/cpl/cli/command/{ => package}/install.py (100%) rename src/cli/cpl/cli/command/{ => package}/uninstall.py (100%) rename src/cli/cpl/cli/command/{ => package}/update.py (100%) create mode 100644 src/cli/cpl/cli/command/structure/__init__.py rename src/cli/cpl/cli/command/{ => structure}/init.py (100%) diff --git a/src/cli/cpl/cli/command/package/__init__.py b/src/cli/cpl/cli/command/package/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cli/cpl/cli/command/install.py b/src/cli/cpl/cli/command/package/install.py similarity index 100% rename from src/cli/cpl/cli/command/install.py rename to src/cli/cpl/cli/command/package/install.py diff --git a/src/cli/cpl/cli/command/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py similarity index 100% rename from src/cli/cpl/cli/command/uninstall.py rename to src/cli/cpl/cli/command/package/uninstall.py diff --git a/src/cli/cpl/cli/command/update.py b/src/cli/cpl/cli/command/package/update.py similarity index 100% rename from src/cli/cpl/cli/command/update.py rename to src/cli/cpl/cli/command/package/update.py diff --git a/src/cli/cpl/cli/command/structure/__init__.py b/src/cli/cpl/cli/command/structure/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cli/cpl/cli/command/init.py b/src/cli/cpl/cli/command/structure/init.py similarity index 100% rename from src/cli/cpl/cli/command/init.py rename to src/cli/cpl/cli/command/structure/init.py From 45dcb400daa509b0d6e971c7d85a3df39ed513bf Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 11:05:04 +0200 Subject: [PATCH 07/22] Added remove & add commands --- cpl.workspace.json | 4 +- src/cli/cpl.project.json | 6 +-- src/cli/cpl/cli/cli.py | 22 +++++++++ src/cli/cpl/cli/command/package/add.py | 51 +++++++++++++++++++ src/cli/cpl/cli/command/package/install.py | 5 +- src/cli/cpl/cli/command/package/remove.py | 52 ++++++++++++++++++++ src/cli/cpl/cli/command/package/uninstall.py | 5 +- src/cli/cpl/cli/main.py | 14 +++--- src/cli/cpl/cli/model/cpl_structure_model.py | 2 +- src/cli/cpl/cli/model/workspace.py | 5 ++ src/cli/cpl/cli/utils/structure.py | 1 + src/cli/run | 12 +++++ 12 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 src/cli/cpl/cli/command/package/add.py create mode 100644 src/cli/cpl/cli/command/package/remove.py create mode 100755 src/cli/run diff --git a/cpl.workspace.json b/cpl.workspace.json index 885653e9..b0e9f4da 100644 --- a/cpl.workspace.json +++ b/cpl.workspace.json @@ -1,8 +1,8 @@ { "name": "cpl", "projects": [ - "src/cpl-core/cpl.project.json", - "src/cpl-cli/cpl.project.json" + "src/cli/cpl.project.json", + "src/core/cpl.project.json" ], "defaultProject": "cpl-cli", "scripts": { diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index edaa4ca7..d7738c6a 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -11,11 +11,9 @@ "click": "~8.3.0" }, "devDependencies": { - "black": "~25.9" + "black": "~25.9.0" }, - "references": [ - "../cpl/cpl.project.json" - ], + "references": [], "main": "cpl/cli/main.py", "directory": "cpl/cli" } \ No newline at end of file diff --git a/src/cli/cpl/cli/cli.py b/src/cli/cpl/cli/cli.py index 66d633fc..47ff710b 100644 --- a/src/cli/cpl/cli/cli.py +++ b/src/cli/cpl/cli/cli.py @@ -1,5 +1,9 @@ +import traceback + import click +from cpl.core.console import Console + class AliasedGroup(click.Group): def command(self, *args, **kwargs): @@ -7,6 +11,7 @@ class AliasedGroup(click.Group): def decorator(f): cmd = super(AliasedGroup, self).command(*args, **kwargs)(f) + cmd.callback = self._handle_errors(cmd.callback) for alias in aliases: self.add_command(cmd, alias) @@ -28,6 +33,23 @@ class AliasedGroup(click.Group): with formatter.section("Commands"): formatter.write_dl(commands) + @staticmethod + def _handle_errors(f): + def wrapper(*args, **kwargs): + try: + res = f(*args, **kwargs) + Console.write_line() + return res + except Exception as e: + tb = None + if "verbose" in kwargs and kwargs["verbose"]: + tb = traceback.format_exc() + Console.error(str(e), tb) + Console.write_line() + exit(-1) + + return wrapper + @click.group(cls=AliasedGroup) def cli(): ... diff --git a/src/cli/cpl/cli/command/package/add.py b/src/cli/cpl/cli/command/package/add.py new file mode 100644 index 00000000..e49d8a82 --- /dev/null +++ b/src/cli/cpl/cli/command/package/add.py @@ -0,0 +1,51 @@ +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.core.configuration import Configuration +from cpl.core.console import Console + + +@cli.command("add", aliases=["a"]) +@click.argument("reference", type=click.STRING, required=True) +@click.argument("target", type=click.STRING, required=True) +@click.option("--verbose", is_flag=True, help="Enable verbose output") +def add(reference: str, target: str, verbose: bool): + workspace = None + + if Configuration.get("workspace_path") is not None: + workspace = Workspace.from_file(Configuration.get("workspace_path")) + + reference_project = _get_project_by_name_or_path(reference, workspace) + target_project = _get_project_by_name_or_path(target, workspace) + + if reference_project.name == target_project.name: + raise ValueError("Cannot add a project as a dependency to itself!") + + if reference_project.path in target_project.references: + raise ValueError(f"Project '{reference_project.name}' is already a reference of '{target_project.name}'") + target_project.references.append(reference_project.path) + target_project.save() + Console.write_line(f"Added '{reference_project.name}' to '{target_project.name}' project") + + +def _get_project_by_name_or_path(project: str, workspace=None) -> Project: + path = Path(project) + if path.exists() and path.is_dir(): + return Project.from_file(path / "cpl.project.json") + + if path.exists() and path.is_file(): + if not path.name.endswith("cpl.project.json"): + raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + + return Project.from_file(path) + + if workspace is not None: + for p in workspace.actual_projects: + if p.name == project: + return Project.from_file(Path(p.path)) + + raise ValueError(f"Project '{project}' not found.") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index ae5108dd..d793eb47 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -24,6 +24,7 @@ from cpl.core.console import Console def install(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): project = resolve_project(Path(project_path), project_name) if package is not None: + Console.write_line(dev) Console.write_line(f"Installing {package} to '{project.name}':") try: Pip.command("install", package, verbose=verbose, path=project_path) @@ -36,7 +37,9 @@ def install(package: str, project_path: str, project_name: str, dev: bool, verbo Console.error(f"Package '{package_name}' not found after installation.") return - project.dependencies[package_name] = Pip.apply_prefix(installed_version, Pip.get_package_full_version(package)) + deps = project.dependencies if not dev else project.dev_dependencies + deps[package_name] = Pip.apply_prefix(installed_version, Pip.get_package_full_version(package)) + project.save() Console.write_line(f"Added {package_name}~{installed_version} to project dependencies.") return diff --git a/src/cli/cpl/cli/command/package/remove.py b/src/cli/cpl/cli/command/package/remove.py new file mode 100644 index 00000000..c9417859 --- /dev/null +++ b/src/cli/cpl/cli/command/package/remove.py @@ -0,0 +1,52 @@ +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.core.configuration import Configuration +from cpl.core.console import Console + + +@cli.command("remove", aliases=["rm"]) +@click.argument("reference", type=click.STRING, required=True) +@click.argument("target", type=click.STRING, required=True) +@click.option("--verbose", is_flag=True, help="Enable verbose output") +def remove(reference: str, target: str, verbose: bool): + workspace = None + + if Configuration.get("workspace_path") is not None: + workspace = Workspace.from_file(Configuration.get("workspace_path")) + + reference_project = _get_project_by_name_or_path(reference, workspace) + target_project = _get_project_by_name_or_path(target, workspace) + + if reference_project.name == target_project.name: + raise ValueError("Cannot add a project as a dependency to itself!") + + if reference_project.path not in target_project.references: + raise ValueError(f"Project '{reference_project.name}' isn't a reference of '{target_project.name}'") + + target_project.references.remove(reference_project.path) + target_project.save() + Console.write_line(f"Removed '{reference_project.name}' from '{target_project.name}' project") + + +def _get_project_by_name_or_path(project: str, workspace=None) -> Project: + path = Path(project) + if path.exists() and path.is_dir(): + return Project.from_file(path / "cpl.project.json") + + if path.exists() and path.is_file(): + if not path.name.endswith("cpl.project.json"): + raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + + return Project.from_file(path) + + if workspace is not None: + for p in workspace.actual_projects: + if p.name == project: + return Project.from_file(Path(p.path)) + + raise ValueError(f"Project '{project}' not found.") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index 68e7a17e..04aee0b4 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -29,7 +29,7 @@ def uninstall(package: str, project_path: str, project_name: str, dev: bool, ver deps = project.dependencies if not dev else project.dev_dependencies try: - Pip.command("install", package, verbose=verbose, path=project_path) + Pip.command("uninstall -y", package, verbose=verbose, path=project_path) except subprocess.CalledProcessError as e: Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") return @@ -38,3 +38,6 @@ def uninstall(package: str, project_path: str, project_name: str, dev: bool, ver del deps[package] project.save() Console.write_line(f"Removed {package} from project dependencies.") + return + + Console.write_line(f"Package {package} was not found in project dependencies.") diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index 69a1e0c8..b18b1025 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -2,10 +2,12 @@ import os from pathlib import Path from cpl.cli.cli import cli -from cpl.cli.command.init import init -from cpl.cli.command.install import install -from cpl.cli.command.uninstall import uninstall -from cpl.cli.command.update import update +from cpl.cli.command.package.remove import remove +from cpl.cli.command.package.add import add +from cpl.cli.command.structure.init import init +from cpl.cli.command.package.install import install +from cpl.cli.command.package.uninstall import uninstall +from cpl.cli.command.package.update import update from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command @@ -54,8 +56,8 @@ def configure(): cli.add_command(install) cli.add_command(uninstall) cli.add_command(update) - # cli.add_command(add) - # cli.add_command(remove) + cli.add_command(add) + cli.add_command(remove) # run # cli.add_command(run) diff --git a/src/cli/cpl/cli/model/cpl_structure_model.py b/src/cli/cpl/cli/model/cpl_structure_model.py index c9aa02f4..867f30b6 100644 --- a/src/cli/cpl/cli/model/cpl_structure_model.py +++ b/src/cli/cpl/cli/model/cpl_structure_model.py @@ -32,7 +32,7 @@ class CPLStructureModel: kwargs: Dict[str, Any] = {} for name, param in list(sig.parameters.items())[1:]: if name == "path": - kwargs[name] = path + kwargs[name] = str(path) continue if name in data: diff --git a/src/cli/cpl/cli/model/workspace.py b/src/cli/cpl/cli/model/workspace.py index 842ec47c..77d1880f 100644 --- a/src/cli/cpl/cli/model/workspace.py +++ b/src/cli/cpl/cli/model/workspace.py @@ -1,6 +1,7 @@ from typing import Optional, List, Dict from cpl.cli.model.cpl_structure_model import CPLStructureModel +from cpl.cli.model.project import Project class Workspace(CPLStructureModel): @@ -45,6 +46,10 @@ class Workspace(CPLStructureModel): def projects(self, value: List[str]): self._projects = self._require_list_of_str(value, "projects") + @property + def actual_projects(self) -> List[Project]: + return [Project.from_file(p) for p in self._projects] + @property def default_project(self) -> Optional[str]: return self._default_project diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index 17266631..c08e71f1 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,4 +1,5 @@ import json +import os from pathlib import Path from cpl.cli.model.project import Project diff --git a/src/cli/run b/src/cli/run new file mode 100755 index 00000000..580c69e9 --- /dev/null +++ b/src/cli/run @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +cd ../ +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +export PYTHONPATH="$ROOT_DIR/core:$ROOT_DIR/cli:$PYTHONPATH" + +old_dir="$(pwd)" +cd ../ +python -m cpl.cli.main "$@" +cd "$old_dir" \ No newline at end of file From 6e0ae1f25e83f30b033c597067a0cedc1ca7cb57 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 11:43:23 +0200 Subject: [PATCH 08/22] Cleanup & fixes --- src/cli/cpl.project.json | 2 +- src/cli/cpl/cli/command/package/add.py | 37 +++----------------- src/cli/cpl/cli/command/package/install.py | 25 +++++-------- src/cli/cpl/cli/command/package/remove.py | 37 +++----------------- src/cli/cpl/cli/command/package/uninstall.py | 19 ++++------ src/cli/cpl/cli/command/package/update.py | 25 +++++-------- src/cli/cpl/cli/utils/pip.py | 19 +++++----- src/cli/cpl/cli/utils/structure.py | 33 +++++++++++++++-- 8 files changed, 74 insertions(+), 123 deletions(-) diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index d7738c6a..f097547a 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -11,7 +11,7 @@ "click": "~8.3.0" }, "devDependencies": { - "black": "~25.9.0" + "black": "~25.9" }, "references": [], "main": "cpl/cli/main.py", diff --git a/src/cli/cpl/cli/command/package/add.py b/src/cli/cpl/cli/command/package/add.py index e49d8a82..b06bd675 100644 --- a/src/cli/cpl/cli/command/package/add.py +++ b/src/cli/cpl/cli/command/package/add.py @@ -1,26 +1,16 @@ -from pathlib import Path - import click from cpl.cli.cli import cli -from cpl.cli.model.project import Project -from cpl.cli.model.workspace import Workspace -from cpl.core.configuration import Configuration +from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.core.console import Console @cli.command("add", aliases=["a"]) @click.argument("reference", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True) -@click.option("--verbose", is_flag=True, help="Enable verbose output") -def add(reference: str, target: str, verbose: bool): - workspace = None - - if Configuration.get("workspace_path") is not None: - workspace = Workspace.from_file(Configuration.get("workspace_path")) - - reference_project = _get_project_by_name_or_path(reference, workspace) - target_project = _get_project_by_name_or_path(target, workspace) +def add(reference: str, target: str): + reference_project = get_project_by_name_or_path(reference) + target_project = get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") @@ -30,22 +20,3 @@ def add(reference: str, target: str, verbose: bool): target_project.references.append(reference_project.path) target_project.save() Console.write_line(f"Added '{reference_project.name}' to '{target_project.name}' project") - - -def _get_project_by_name_or_path(project: str, workspace=None) -> Project: - path = Path(project) - if path.exists() and path.is_dir(): - return Project.from_file(path / "cpl.project.json") - - if path.exists() and path.is_file(): - if not path.name.endswith("cpl.project.json"): - raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") - - return Project.from_file(path) - - if workspace is not None: - for p in workspace.actual_projects: - if p.name == project: - return Project.from_file(Path(p.path)) - - raise ValueError(f"Project '{project}' not found.") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index d793eb47..f20e76fe 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -1,38 +1,31 @@ import subprocess -from pathlib import Path import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import resolve_project +from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.core.console import Console @cli.command("install", aliases=["i"]) @click.argument("package", type=click.STRING, required=False) -@click.option( - "--path", - "project_path", - type=click.Path(file_okay=False, exists=False), - default=".", - help="Path to project directory", -) -@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", is_flag=True, help="Enable verbose output") -def install(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): - project = resolve_project(Path(project_path), project_name) +def install(package: str, project: str, dev: bool, verbose: bool): + project = get_project_by_name_or_path(project or "./") + if package is not None: - Console.write_line(dev) Console.write_line(f"Installing {package} to '{project.name}':") try: - Pip.command("install", package, verbose=verbose, path=project_path) + Pip.command("install", package, verbose=verbose, path=project.path) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {package}: exit code {e.returncode}") + return package_name = Pip.get_package_without_version(package) - installed_version = Pip.get_package_version(package_name, path=project_path) + installed_version = Pip.get_package_version(package_name, path=project.path) if installed_version is None: Console.error(f"Package '{package_name}' not found after installation.") return @@ -58,6 +51,6 @@ def install(package: str, project_path: str, project_name: str, dev: bool, verbo dep = Pip.normalize_dep(name, version) Console.write_line(f" -> {dep}") try: - Pip.command("install", dep, verbose=verbose, path=project_path) + Pip.command("install", dep, verbose=verbose, path=project.path) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {dep}: exit code {e.returncode}") diff --git a/src/cli/cpl/cli/command/package/remove.py b/src/cli/cpl/cli/command/package/remove.py index c9417859..be337efb 100644 --- a/src/cli/cpl/cli/command/package/remove.py +++ b/src/cli/cpl/cli/command/package/remove.py @@ -1,26 +1,16 @@ -from pathlib import Path - import click from cpl.cli.cli import cli -from cpl.cli.model.project import Project -from cpl.cli.model.workspace import Workspace -from cpl.core.configuration import Configuration +from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.core.console import Console @cli.command("remove", aliases=["rm"]) @click.argument("reference", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True) -@click.option("--verbose", is_flag=True, help="Enable verbose output") -def remove(reference: str, target: str, verbose: bool): - workspace = None - - if Configuration.get("workspace_path") is not None: - workspace = Workspace.from_file(Configuration.get("workspace_path")) - - reference_project = _get_project_by_name_or_path(reference, workspace) - target_project = _get_project_by_name_or_path(target, workspace) +def remove(reference: str, target: str): + reference_project = get_project_by_name_or_path(reference) + target_project = get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") @@ -31,22 +21,3 @@ def remove(reference: str, target: str, verbose: bool): target_project.references.remove(reference_project.path) target_project.save() Console.write_line(f"Removed '{reference_project.name}' from '{target_project.name}' project") - - -def _get_project_by_name_or_path(project: str, workspace=None) -> Project: - path = Path(project) - if path.exists() and path.is_dir(): - return Project.from_file(path / "cpl.project.json") - - if path.exists() and path.is_file(): - if not path.name.endswith("cpl.project.json"): - raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") - - return Project.from_file(path) - - if workspace is not None: - for p in workspace.actual_projects: - if p.name == project: - return Project.from_file(Path(p.path)) - - raise ValueError(f"Project '{project}' not found.") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index 04aee0b4..ba70495a 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -1,35 +1,28 @@ import subprocess -from pathlib import Path import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import resolve_project +from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.core.console import Console @cli.command("uninstall", aliases=["ui"]) @click.argument("package", required=False) -@click.option( - "--path", - "project_path", - type=click.Path(file_okay=False, exists=False), - default=".", - help="Path to project directory", -) -@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", is_flag=True, help="Enable verbose output") -def uninstall(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): +def uninstall(package: str, project: str, dev: bool, verbose: bool): if package is None: package = Console.read("Package name to uninstall: ").strip() - project = resolve_project(Path(project_path), project_name) + project = get_project_by_name_or_path(project or "./") + deps = project.dependencies if not dev else project.dev_dependencies try: - Pip.command("uninstall -y", package, verbose=verbose, path=project_path) + Pip.command("uninstall -y", package, verbose=verbose, path=project.path) except subprocess.CalledProcessError as e: Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") return diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index a10206a0..ea116b84 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -1,28 +1,21 @@ import subprocess -from pathlib import Path import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import resolve_project +from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.core.console import Console @cli.command("update", aliases=["u"]) @click.argument("package", type=click.STRING, required=False) -@click.option( - "--path", - "project_path", - type=click.Path(file_okay=False, exists=False), - default=".", - help="Path to project directory", -) -@click.option("--name", "project_name", help="Project name (lookup via workspace)") +@click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", is_flag=True, help="Enable verbose output") -def update(package: str, project_path: str, project_name: str, dev: bool, verbose: bool): - project = resolve_project(Path(project_path), project_name) +def update(package: str, project: str, dev: bool, verbose: bool): + project = get_project_by_name_or_path(project or "./") + deps: dict = project.dependencies if dev: deps = project.dev_dependencies @@ -40,13 +33,13 @@ def update(package: str, project_path: str, project_name: str, dev: bool, verbos "install --upgrade", f"{Pip.normalize_dep(package, old_spec)}", verbose=verbose, - path=project_path, + path=project.path, ) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {package}: exit code {e.returncode}") return - installed_version = Pip.get_package_version(package, path=project_path) + installed_version = Pip.get_package_version(package, path=project.path) if installed_version is None: Console.error(f"Package '{package}' not found after update.") return @@ -66,12 +59,12 @@ def update(package: str, project_path: str, project_name: str, dev: bool, verbos dep = Pip.normalize_dep(name, version) Console.write_line(f" -> {dep}") try: - Pip.command("install --upgrade", dep, verbose=verbose, path=project_path) + Pip.command("install --upgrade", dep, verbose=verbose, path=project.path) except subprocess.CalledProcessError as e: Console.error(f"Failed to update {dep}: exit code {e.returncode}") return - installed_version = Pip.get_package_version(name, path=project_path) + installed_version = Pip.get_package_version(name, path=project.path) if installed_version is None: Console.error(f"Package '{name}' not found after update.") continue diff --git a/src/cli/cpl/cli/utils/pip.py b/src/cli/cpl/cli/utils/pip.py index 5c00eb92..70fbf343 100644 --- a/src/cli/cpl/cli/utils/pip.py +++ b/src/cli/cpl/cli/utils/pip.py @@ -1,3 +1,4 @@ +import os.path import re import subprocess from pathlib import Path @@ -97,19 +98,22 @@ class Pip: @staticmethod def command(command: str, *args, verbose: bool = False, path: str = None): + if path is not None and Path(path).is_file(): + path = os.path.dirname(path) + venv_path = ensure_venv(Path(path or "./")) pip = get_venv_pip(venv_path) if verbose: Console.write_line() + Console.write_line(f"Running: {pip} {command} {''.join(args)}") subprocess.run( - f"{pip} {command} {''.join(args)}", - shell=True, + [*pip.split(), command, *args], check=True, stdin=subprocess.DEVNULL if not verbose else None, stdout=subprocess.DEVNULL if not verbose else None, - stderr=subprocess.STDOUT if not verbose else None, + stderr=subprocess.DEVNULL if not verbose else None, ) @staticmethod @@ -119,8 +123,7 @@ class Pip: try: result = subprocess.run( - f"{pip} show {package}", - shell=True, + [*pip.split(), "show", package], check=True, capture_output=True, text=True, @@ -140,8 +143,7 @@ class Pip: pip = get_venv_pip(venv_path) try: result = subprocess.run( - f"{pip} list --format=freeze", - shell=True, + [*pip.split(), "list", "--format=freeze"], check=True, capture_output=True, text=True, @@ -163,8 +165,7 @@ class Pip: try: result = subprocess.run( - f"{pip} --version", - shell=True, + [*pip.split(), "--version"], check=True, capture_output=True, text=True, diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index c08e71f1..87f2e4cb 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,9 +1,8 @@ -import json -import os from pathlib import Path from cpl.cli.model.project import Project from cpl.cli.model.workspace import Workspace +from cpl.core.configuration import Configuration from cpl.core.console import Console @@ -29,3 +28,33 @@ def resolve_project(path: Path, name: str | None) -> Project: Console.error(f"Could not find project file '{path}'") exit(1) + +def get_project_by_name_or_path(project: str) -> Project: + if project is None: + raise ValueError("Project name or path must be provided.") + + workspace = None + if Configuration.get("workspace_path") is not None: + workspace = Workspace.from_file(Configuration.get("workspace_path")) + + path = Path(project) + if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): + return Project.from_file(path / "cpl.project.json") + + if path.exists() and path.is_file(): + if not path.name.endswith("cpl.project.json"): + raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + + return Project.from_file(path) + + if workspace is not None: + for p in workspace.actual_projects: + if p.name == project: + return Project.from_file(Path(p.path)) + + if workspace.default_project is not None: + for p in workspace.actual_projects: + if p.name == workspace.default_project: + return Project.from_file(Path(p.path)) + + raise ValueError(f"Project '{project}' not found.") \ No newline at end of file From 849dd7a733c586ab8ec7a5c8c8d2aa24d7b7f52f Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 14:13:28 +0200 Subject: [PATCH 09/22] [WIP] Started run command --- src/cli/cpl/cli/command/execute/__init__.py | 0 src/cli/cpl/cli/command/execute/run.py | 17 ++++++++++++ src/cli/cpl/cli/main.py | 5 ++-- src/cli/cpl/cli/utils/structure.py | 29 +-------------------- src/cli/cpl/cli/utils/venv.py | 12 ++++----- src/cli/run | 1 + 6 files changed, 28 insertions(+), 36 deletions(-) create mode 100644 src/cli/cpl/cli/command/execute/__init__.py create mode 100644 src/cli/cpl/cli/command/execute/run.py diff --git a/src/cli/cpl/cli/command/execute/__init__.py b/src/cli/cpl/cli/command/execute/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cli/cpl/cli/command/execute/run.py b/src/cli/cpl/cli/command/execute/run.py new file mode 100644 index 00000000..3f88babe --- /dev/null +++ b/src/cli/cpl/cli/command/execute/run.py @@ -0,0 +1,17 @@ +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.core.console import Console + + +@cli.command("run", aliases=["r"]) +@click.argument("args", nargs=-1) +@click.option("--project", "-p", type=str) +def run(project: str, args: list[str]): + project = get_project_by_name_or_path(project or "./") + if project.main is None: + Console.error(f"Project {project.name} has no executable") + return + + Console.write_line(project.main, args) diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index b18b1025..8cb00444 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -2,6 +2,7 @@ import os from pathlib import Path from cpl.cli.cli import cli +from cpl.cli.command.execute.run import run from cpl.cli.command.package.remove import remove from cpl.cli.command.package.add import add from cpl.cli.command.structure.init import init @@ -32,7 +33,7 @@ def _load_scripts(): if ws is None: continue - Configuration.set("workspace_path", os.path.abspath(p)) + Configuration.set("workspace", Workspace.from_file(p)) return ws.scripts return {} @@ -60,7 +61,7 @@ def configure(): cli.add_command(remove) # run - # cli.add_command(run) + cli.add_command(run) # cli.add_command(start) diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index 87f2e4cb..a5039850 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,41 +1,14 @@ from pathlib import Path from cpl.cli.model.project import Project -from cpl.cli.model.workspace import Workspace from cpl.core.configuration import Configuration -from cpl.core.console import Console -def resolve_project(path: Path, name: str | None) -> Project: - project_file = path / "cpl.project.json" - if project_file.exists(): - return Project.from_file(project_file) - - workspace_file = path / "cpl.workspace.json" - if workspace_file.exists(): - workspace = Workspace.from_file(workspace_file) - if name: - for p in workspace.projects: - project = Project.from_file(p) - if project.name == name: - return project - - elif workspace.default_project: - for p in workspace.projects: - project = Project.from_file(p) - if project.name == workspace.default_project: - return project - - Console.error(f"Could not find project file '{path}'") - exit(1) - def get_project_by_name_or_path(project: str) -> Project: if project is None: raise ValueError("Project name or path must be provided.") - workspace = None - if Configuration.get("workspace_path") is not None: - workspace = Workspace.from_file(Configuration.get("workspace_path")) + workspace = Configuration.get("workspace") path = Path(project) if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): diff --git a/src/cli/cpl/cli/utils/venv.py b/src/cli/cpl/cli/utils/venv.py index b34b84f7..c8806f33 100644 --- a/src/cli/cpl/cli/utils/venv.py +++ b/src/cli/cpl/cli/utils/venv.py @@ -9,12 +9,12 @@ from cpl.core.console import Console def ensure_venv(start_path: Path | None = None) -> Path: start_path = start_path or Path.cwd() - workspace_path = Configuration.get("workspace_path") + workspace = Configuration.get("workspace") - if workspace_path is not None: - workspace_path = Path(os.path.dirname(workspace_path)) + if workspace is not None: + workspace = Path(os.path.dirname(workspace.path)) - ws_venv = workspace_path / ".venv" + ws_venv = workspace / ".venv" if ws_venv.exists(): return ws_venv @@ -23,8 +23,8 @@ def ensure_venv(start_path: Path | None = None) -> Path: if venv_path.exists(): return venv_path - if workspace_path is not None: - venv_path = workspace_path / ".venv" + if workspace is not None: + venv_path = workspace / ".venv" else: venv_path = start_path / ".venv" diff --git a/src/cli/run b/src/cli/run index 580c69e9..2acff0b4 100755 --- a/src/cli/run +++ b/src/cli/run @@ -8,5 +8,6 @@ export PYTHONPATH="$ROOT_DIR/core:$ROOT_DIR/cli:$PYTHONPATH" old_dir="$(pwd)" cd ../ +echo "$@" python -m cpl.cli.main "$@" cd "$old_dir" \ No newline at end of file From faedf328cb7c055d4c80e7e293d43964901597a6 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 15:50:38 +0200 Subject: [PATCH 10/22] Added build command --- src/cli/cpl.project.json | 16 ++- src/cli/cpl/cli/__init__.py | 1 - src/cli/cpl/cli/command/package/add.py | 9 +- src/cli/cpl/cli/command/package/install.py | 2 +- src/cli/cpl/cli/command/package/remove.py | 7 +- src/cli/cpl/cli/command/package/uninstall.py | 2 +- src/cli/cpl/cli/command/package/update.py | 2 +- .../command/{execute => project}/__init__.py | 0 src/cli/cpl/cli/command/project/build.py | 31 ++++++ .../cli/command/{execute => project}/run.py | 0 src/cli/cpl/cli/main.py | 9 +- src/cli/cpl/cli/model/build.py | 18 +++ src/cli/cpl/cli/model/cpl_structure_model.py | 11 ++ .../cpl/cli/model/cpl_sub_structure_model.py | 104 ++++++++++++++++++ src/cli/cpl/cli/model/project.py | 91 +++++++++++++++ src/cli/cpl/cli/utils/structure.py | 4 + src/cli/run | 1 - src/core/cpl.project.json | 10 +- 18 files changed, 302 insertions(+), 16 deletions(-) rename src/cli/cpl/cli/command/{execute => project}/__init__.py (100%) create mode 100644 src/cli/cpl/cli/command/project/build.py rename src/cli/cpl/cli/command/{execute => project}/run.py (100%) create mode 100644 src/cli/cpl/cli/model/build.py create mode 100644 src/cli/cpl/cli/model/cpl_sub_structure_model.py diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index f097547a..886fbd05 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -13,7 +13,19 @@ "devDependencies": { "black": "~25.9" }, - "references": [], + "references": [ + "../core/cpl.project.json" + ], "main": "cpl/cli/main.py", - "directory": "cpl/cli" + "directory": "cpl/cli", + "build": { + "include": [ + "_templates/" + ], + "exclude": [ + "**/__pycache__", + "**/logs", + "**/tests" + ] + } } \ No newline at end of file diff --git a/src/cli/cpl/cli/__init__.py b/src/cli/cpl/cli/__init__.py index a6d8caf7..5becc17c 100644 --- a/src/cli/cpl/cli/__init__.py +++ b/src/cli/cpl/cli/__init__.py @@ -1,2 +1 @@ __version__ = "1.0.0" -__version__ = "1.0.0" diff --git a/src/cli/cpl/cli/command/package/add.py b/src/cli/cpl/cli/command/package/add.py index b06bd675..ee4afc5d 100644 --- a/src/cli/cpl/cli/command/package/add.py +++ b/src/cli/cpl/cli/command/package/add.py @@ -1,3 +1,5 @@ +from pathlib import Path + import click from cpl.cli.cli import cli @@ -8,7 +10,8 @@ from cpl.core.console import Console @cli.command("add", aliases=["a"]) @click.argument("reference", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True) -def add(reference: str, target: str): +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def add(reference: str, target: str, verbose: bool): reference_project = get_project_by_name_or_path(reference) target_project = get_project_by_name_or_path(target) @@ -17,6 +20,8 @@ def add(reference: str, target: str): if reference_project.path in target_project.references: raise ValueError(f"Project '{reference_project.name}' is already a reference of '{target_project.name}'") - target_project.references.append(reference_project.path) + + rel_path = Path(reference_project.path).relative_to(Path(target_project.path).parent, walk_up=True) + target_project.references.append(str(rel_path)) target_project.save() Console.write_line(f"Added '{reference_project.name}' to '{target_project.name}' project") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index f20e76fe..d2bb4aa2 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -12,7 +12,7 @@ from cpl.core.console import Console @click.argument("package", type=click.STRING, required=False) @click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") -@click.option("--verbose", is_flag=True, help="Enable verbose output") +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def install(package: str, project: str, dev: bool, verbose: bool): project = get_project_by_name_or_path(project or "./") diff --git a/src/cli/cpl/cli/command/package/remove.py b/src/cli/cpl/cli/command/package/remove.py index be337efb..230580fd 100644 --- a/src/cli/cpl/cli/command/package/remove.py +++ b/src/cli/cpl/cli/command/package/remove.py @@ -1,3 +1,5 @@ +from pathlib import Path + import click from cpl.cli.cli import cli @@ -15,9 +17,10 @@ def remove(reference: str, target: str): if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") - if reference_project.path not in target_project.references: + rel_path = str(Path(reference_project.path).relative_to(Path(target_project.path).parent, walk_up=True)) + if rel_path not in target_project.references: raise ValueError(f"Project '{reference_project.name}' isn't a reference of '{target_project.name}'") - target_project.references.remove(reference_project.path) + target_project.references.remove(rel_path) target_project.save() Console.write_line(f"Removed '{reference_project.name}' from '{target_project.name}' project") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index ba70495a..ef1b5b7e 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -12,7 +12,7 @@ from cpl.core.console import Console @click.argument("package", required=False) @click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") -@click.option("--verbose", is_flag=True, help="Enable verbose output") +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def uninstall(package: str, project: str, dev: bool, verbose: bool): if package is None: package = Console.read("Package name to uninstall: ").strip() diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index ea116b84..fed28888 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -12,7 +12,7 @@ from cpl.core.console import Console @click.argument("package", type=click.STRING, required=False) @click.argument("project", type=click.STRING, required=False) @click.option("--dev", is_flag=True, help="Include dev dependencies") -@click.option("--verbose", is_flag=True, help="Enable verbose output") +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def update(package: str, project: str, dev: bool, verbose: bool): project = get_project_by_name_or_path(project or "./") diff --git a/src/cli/cpl/cli/command/execute/__init__.py b/src/cli/cpl/cli/command/project/__init__.py similarity index 100% rename from src/cli/cpl/cli/command/execute/__init__.py rename to src/cli/cpl/cli/command/project/__init__.py diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py new file mode 100644 index 00000000..16d7718c --- /dev/null +++ b/src/cli/cpl/cli/command/project/build.py @@ -0,0 +1,31 @@ +import os.path +from pathlib import Path + +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.core.configuration import Configuration +from cpl.core.console import Console + + +@cli.command("build", aliases=["b"]) +@click.argument("project", type=click.STRING, required=False) +@click.option("--dist", "-d", type=str) +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def build(project: str, dist: str | None, verbose: bool): + project = get_project_by_name_or_path(project or "./") + dist_path = dist or Path(project.path).parent / "dist" + + if dist is None and Configuration.get("workspace") is not None: + dist_path = Path(Configuration.get("workspace").path).parent / "dist" + + dist_path = Path(dist_path).resolve().absolute() + + if verbose: + Console.write_line(f"Creating dist folder at {dist_path}...") + + os.makedirs(dist_path, exist_ok=True) + + project.do_build(dist_path, verbose) + Console.write_line("\nDone!") diff --git a/src/cli/cpl/cli/command/execute/run.py b/src/cli/cpl/cli/command/project/run.py similarity index 100% rename from src/cli/cpl/cli/command/execute/run.py rename to src/cli/cpl/cli/command/project/run.py diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index 8cb00444..be81881f 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -1,14 +1,14 @@ -import os from pathlib import Path from cpl.cli.cli import cli -from cpl.cli.command.execute.run import run -from cpl.cli.command.package.remove import remove from cpl.cli.command.package.add import add -from cpl.cli.command.structure.init import init from cpl.cli.command.package.install import install +from cpl.cli.command.package.remove import remove from cpl.cli.command.package.uninstall import uninstall from cpl.cli.command.package.update import update +from cpl.cli.command.project.build import build +from cpl.cli.command.project.run import run +from cpl.cli.command.structure.init import init from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command @@ -61,6 +61,7 @@ def configure(): cli.add_command(remove) # run + cli.add_command(build) cli.add_command(run) # cli.add_command(start) diff --git a/src/cli/cpl/cli/model/build.py b/src/cli/cpl/cli/model/build.py new file mode 100644 index 00000000..bfd87003 --- /dev/null +++ b/src/cli/cpl/cli/model/build.py @@ -0,0 +1,18 @@ +from cpl.cli.model.cpl_sub_structure_model import CPLSubStructureModel + + +class Build(CPLSubStructureModel): + + def __init__(self, include: list[str], exclude: list[str]): + CPLSubStructureModel.__init__(self) + + self._include = include + self._exclude = exclude + + @property + def include(self) -> list[str]: + return self._include + + @property + def exclude(self) -> list[str]: + return self._exclude diff --git a/src/cli/cpl/cli/model/cpl_structure_model.py b/src/cli/cpl/cli/model/cpl_structure_model.py index 867f30b6..ba32133a 100644 --- a/src/cli/cpl/cli/model/cpl_structure_model.py +++ b/src/cli/cpl/cli/model/cpl_structure_model.py @@ -1,8 +1,11 @@ import inspect import json +from inspect import isclass from pathlib import Path from typing import Any, Dict, List, Optional, Type, TypeVar +from cpl.cli.model.cpl_sub_structure_model import CPLSubStructureModel + T = TypeVar("T", bound="CPLStructureModel") @@ -35,6 +38,10 @@ class CPLStructureModel: kwargs[name] = str(path) continue + if isclass(param.annotation) and issubclass(param.annotation, CPLSubStructureModel): + kwargs[name] = param.annotation.from_json(data[name]) + continue + if name in data: kwargs[name] = data[name] continue @@ -63,6 +70,10 @@ class CPLStructureModel: if not key.startswith("_") or key == "_path": continue out_key = _self_or_cls_snake_to_camel(key[1:]) + + if isinstance(value, CPLSubStructureModel): + value = value.to_json() + result[out_key] = value return result diff --git a/src/cli/cpl/cli/model/cpl_sub_structure_model.py b/src/cli/cpl/cli/model/cpl_sub_structure_model.py new file mode 100644 index 00000000..0f6cf4f5 --- /dev/null +++ b/src/cli/cpl/cli/model/cpl_sub_structure_model.py @@ -0,0 +1,104 @@ +import inspect +import json +from pathlib import Path +from typing import Any, Dict, List, Optional, Type, TypeVar + +T = TypeVar("T", bound="CPLSubStructureModel") + + +class CPLSubStructureModel: + def __init__(self, path: Optional[str] = None): + self._path = path + + @classmethod + def from_json(cls: Type[T], data: Dict[str, Any]) -> T: + sig = inspect.signature(cls.__init__) + kwargs: Dict[str, Any] = {} + for name, param in list(sig.parameters.items())[1:]: + if name in data: + kwargs[name] = data[name] + continue + + priv = "_" + name + if priv in data: + kwargs[name] = data[priv] + continue + + camel = _self_or_cls_snake_to_camel(name) + if camel in data: + kwargs[name] = data[camel] + continue + + if param.default is not inspect._empty: + kwargs[name] = param.default + continue + + raise KeyError(f"Missing required field '{name}' for {cls.__name__}.") + + return cls(**kwargs) + + def to_json(self) -> Dict[str, Any]: + result: Dict[str, Any] = {} + for key, value in self.__dict__.items(): + if not key.startswith("_") or key == "_path": + continue + out_key = _self_or_cls_snake_to_camel(key[1:]) + result[out_key] = value + return result + + @staticmethod + def _require_str(value, field: str, allow_empty: bool = True) -> str: + if not isinstance(value, str): + raise TypeError(f"{field} must be of type str") + if not allow_empty and not value.strip(): + raise ValueError(f"{field} must not be empty") + return value + + @staticmethod + def _require_optional_non_empty_str(value, field: str) -> Optional[str]: + if value is None: + return None + if not isinstance(value, str): + raise TypeError(f"{field} must be str or None") + s = value.strip() + if not s: + raise ValueError(f"{field} must not be empty when set") + return s + + @staticmethod + def _require_list_of_str(value, field: str) -> List[str]: + if not isinstance(value, list): + raise TypeError(f"{field} must be a list") + out: List[str] = [] + for i, v in enumerate(value): + if not isinstance(v, str): + raise TypeError(f"{field}[{i}] must be of type str") + s = v.strip() + if s: + out.append(s) + + seen = set() + uniq: List[str] = [] + for s in out: + if s not in seen: + seen.add(s) + uniq.append(s) + return uniq + + @staticmethod + def _require_dict_str_str(value, field: str) -> Dict[str, str]: + if not isinstance(value, dict): + raise TypeError(f"{field} must be a dict") + out: Dict[str, str] = {} + for k, v in value.items(): + if not isinstance(k, str) or not k.strip(): + raise TypeError(f"Keys in {field} must be non-empty strings") + if not isinstance(v, str) or not v.strip(): + raise TypeError(f"Values in {field} must be non-empty strings") + out[k.strip()] = v.strip() + return out + + +def _self_or_cls_snake_to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p[:1].upper() + p[1:] for p in parts[1:]) diff --git a/src/cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py index 1944a48f..37fa4e5d 100644 --- a/src/cli/cpl/cli/model/project.py +++ b/src/cli/cpl/cli/model/project.py @@ -1,8 +1,14 @@ +import fnmatch +import os import re +import shutil +from pathlib import Path from typing import Optional, List, Dict from urllib.parse import urlparse +from cpl.cli.model.build import Build from cpl.cli.model.cpl_structure_model import CPLStructureModel +from cpl.core.console import Console class Project(CPLStructureModel): @@ -44,6 +50,7 @@ class Project(CPLStructureModel): references: List[str], main: Optional[str], directory: str, + build: Build, ): CPLStructureModel.__init__(self, path) @@ -60,6 +67,7 @@ class Project(CPLStructureModel): self._references = references self._main = main self._directory = directory + self._build = build @property def name(self) -> str: @@ -176,3 +184,86 @@ class Project(CPLStructureModel): @directory.setter def directory(self, value: str): self._directory = self._require_str(value, "directory", allow_empty=False).strip() + + @property + def build(self) -> Build: + return self._build + + def _collect_files(self, rel_dir: Path) -> List[Path]: + files: List[Path] = [] + exclude_patterns = [p.strip() for p in self._build.exclude or []] + + for root, dirnames, filenames in os.walk(rel_dir, topdown=True): + root_path = Path(root) + rel_root = root_path.relative_to(rel_dir).as_posix() + + dirnames[:] = [ + d + for d in dirnames + if not any( + fnmatch.fnmatch(f"{rel_root}/{d}" if rel_root else d, pattern) or fnmatch.fnmatch(d, pattern) + for pattern in exclude_patterns + ) + ] + + for filename in filenames: + rel_path = f"{rel_root}/{filename}" if rel_root else filename + if any( + fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(filename, pattern) + for pattern in exclude_patterns + ): + continue + + files.append(root_path / filename) + + return files + + def build_references(self, dist: Path, verbose: bool = False): + references = [] + old_dir = os.getcwd() + os.chdir(Path(self.path).parent) + for ref in self.references: + os.chdir(Path(ref).parent) + references.append(Project.from_file(ref)) + + for p in references: + os.chdir(Path(p.path).parent) + p.do_build(dist, verbose) + + os.chdir(old_dir) + + def do_build(self, dist: Path, verbose: bool = False): + self.build_references(dist, verbose) + + Console.write_line(f"Building project {self.name}...") + if isinstance(dist, str): + dist = Path(dist) + + dist_path = dist / self.name + rel_dir = Path(self.path).parent / Path(self.directory) + + if verbose: + Console.write_line(f" Collecting project '{self.name}' files...") + + files = self._collect_files(rel_dir) + + if len(files) == 0: + return + + if verbose: + Console.write_line(f" Cleaning dist folder at {dist_path.absolute()}...") + + shutil.rmtree(str(dist_path), ignore_errors=True) + + for file in files: + rel_path = file.relative_to(rel_dir) + dest_file_path = dist_path / rel_path + + if not dest_file_path.parent.exists(): + os.makedirs(dest_file_path.parent, exist_ok=True) + + shutil.copy(file, dest_file_path) + + if verbose: + Console.write_line(f" Copied {len(files)} files from {rel_dir.absolute()} to {dist_path.absolute()}") + Console.write_line(" Done!") diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index a5039850..0044bd24 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -25,6 +25,10 @@ def get_project_by_name_or_path(project: str) -> Project: if p.name == project: return Project.from_file(Path(p.path)) + + if not path.is_dir() and not path.is_file(): + raise ValueError(f"Unknown project {project}") + if workspace.default_project is not None: for p in workspace.actual_projects: if p.name == workspace.default_project: diff --git a/src/cli/run b/src/cli/run index 2acff0b4..580c69e9 100755 --- a/src/cli/run +++ b/src/cli/run @@ -8,6 +8,5 @@ export PYTHONPATH="$ROOT_DIR/core:$ROOT_DIR/cli:$PYTHONPATH" old_dir="$(pwd)" cd ../ -echo "$@" python -m cpl.cli.main "$@" cd "$old_dir" \ No newline at end of file diff --git a/src/core/cpl.project.json b/src/core/cpl.project.json index 7a3bd3b2..5cebf59d 100644 --- a/src/core/cpl.project.json +++ b/src/core/cpl.project.json @@ -20,5 +20,13 @@ }, "references": [], "main": null, - "directory": "cpl/core" + "directory": "cpl/core", + "build": { + "include": [], + "exclude": [ + "**/__pycache__", + "**/logs", + "**/tests" + ] + } } \ No newline at end of file From 3c26c73b4129906eb81ef52108f4a6dc08b0be9a Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 16:31:04 +0200 Subject: [PATCH 11/22] Added run command --- src/cli/cpl/cli/command/project/run.py | 38 ++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/cli/cpl/cli/command/project/run.py b/src/cli/cpl/cli/command/project/run.py index 3f88babe..5bbd5aba 100644 --- a/src/cli/cpl/cli/command/project/run.py +++ b/src/cli/cpl/cli/command/project/run.py @@ -1,17 +1,51 @@ +import os +import subprocess +from pathlib import Path + import click from cpl.cli.cli import cli from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.venv import get_venv_python, ensure_venv +from cpl.core.configuration import Configuration from cpl.core.console import Console @cli.command("run", aliases=["r"]) @click.argument("args", nargs=-1) @click.option("--project", "-p", type=str) -def run(project: str, args: list[str]): +@click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def run(project: str, args: list[str], dev: bool, verbose: bool): project = get_project_by_name_or_path(project or "./") if project.main is None: Console.error(f"Project {project.name} has no executable") return - Console.write_line(project.main, args) + path = str(Path(project.path).parent.resolve().absolute()) + executable = project.main + if not dev: + dist_path = Path(project.path).parent / "dist" + + if Configuration.get("workspace") is not None: + dist_path = Path(Configuration.get("workspace").path).parent / "dist" + + dist_path = Path(dist_path).resolve().absolute() + if verbose: + Console.write_line(f"Creating dist folder at {dist_path}...") + + os.makedirs(dist_path, exist_ok=True) + project.do_build(dist_path, verbose) + path = dist_path / project.name + main = project.main.replace(project.directory, "").lstrip("/\\") + + executable = path / main + + python = str(get_venv_python(ensure_venv()).absolute()) + Console.write_line(f"\nStarting project {project.name}...") + if verbose: + Console.write_line(f" with args {args}...") + + Console.write_line("\n\n") # add some space before output + + subprocess.run([python, executable, *args], cwd=path) From 883fa2d691b46adb63be5b56700adf7b7077a634 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Oct 2025 18:25:32 +0200 Subject: [PATCH 12/22] Added build command --- src/cli/cpl.project.json | 3 ++- src/cli/cpl/cli/command/project/build.py | 30 +++++++++++++++++++++++- src/cli/cpl/cli/utils/structure.py | 23 +++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index 886fbd05..91a1ef44 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -17,8 +17,9 @@ "../core/cpl.project.json" ], "main": "cpl/cli/main.py", - "directory": "cpl/cli", + "directory": "cpl", "build": { + "build": "python -m build", "include": [ "_templates/" ], diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py index 16d7718c..f8c51fa1 100644 --- a/src/cli/cpl/cli/command/project/build.py +++ b/src/cli/cpl/cli/command/project/build.py @@ -1,10 +1,12 @@ import os.path +import subprocess from pathlib import Path import click from cpl.cli.cli import cli from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.venv import ensure_venv, get_venv_python from cpl.core.configuration import Configuration from cpl.core.console import Console @@ -12,9 +14,11 @@ from cpl.core.console import Console @cli.command("build", aliases=["b"]) @click.argument("project", type=click.STRING, required=False) @click.option("--dist", "-d", type=str) +@click.option("--skip-py-build", "-spb", is_flag=True, help="Skip toml generation and python build") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") -def build(project: str, dist: str | None, verbose: bool): +def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: bool = None): project = get_project_by_name_or_path(project or "./") + venv = ensure_venv().absolute() dist_path = dist or Path(project.path).parent / "dist" if dist is None and Configuration.get("workspace") is not None: @@ -28,4 +32,28 @@ def build(project: str, dist: str | None, verbose: bool): os.makedirs(dist_path, exist_ok=True) project.do_build(dist_path, verbose) + + if skip_py_build: + Console.write_line("\nDone!") + return + + from cpl.cli.utils.structure import create_pyproject_toml + + create_pyproject_toml(project, dist_path / project.name) + python = str(get_venv_python(venv)) + + subprocess.run( + [ + python, + "-m", + "build", + "--outdir", + str(dist_path / project.name), + str(dist_path / project.name), + ], + check=True, + stdin=subprocess.DEVNULL if not verbose else None, + stdout=subprocess.DEVNULL if not verbose else None, + stderr=subprocess.DEVNULL if not verbose else None, + ) Console.write_line("\nDone!") diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index 0044bd24..a2c50dbe 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,3 +1,4 @@ +import textwrap from pathlib import Path from cpl.cli.model.project import Project @@ -34,4 +35,24 @@ def get_project_by_name_or_path(project: str) -> Project: if p.name == workspace.default_project: return Project.from_file(Path(p.path)) - raise ValueError(f"Project '{project}' not found.") \ No newline at end of file + raise ValueError(f"Project '{project}' not found.") + +def create_pyproject_toml(project: Project, path: Path): + pyproject_path = path / "pyproject.toml" + if pyproject_path.exists(): + return + + content = textwrap.dedent(f""" + [build-system] + requires = ["setuptools>=70.1.0", "wheel", "build"] + build-backend = "setuptools.build_meta" + [project] + name = "{project.name}" + version = "{project.version or '0.1.0'}" + description = "{project.description or ''}" + authors = [{{name="{project.author or ''}"}}] + license = "{project.license or ''}" + dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] + """).lstrip() + + pyproject_path.write_text(content) From 96479236476a4eb336d7378b2fa403a9fbcd882b Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 12 Oct 2025 18:19:58 +0200 Subject: [PATCH 13/22] Improved build --- src/cli/cpl/cli/command/project/build.py | 33 ++++++++------ src/cli/cpl/cli/model/project.py | 56 ++++++++++++++---------- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py index f8c51fa1..91225ec7 100644 --- a/src/cli/cpl/cli/command/project/build.py +++ b/src/cli/cpl/cli/command/project/build.py @@ -42,18 +42,23 @@ def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: b create_pyproject_toml(project, dist_path / project.name) python = str(get_venv_python(venv)) - subprocess.run( - [ - python, - "-m", - "build", - "--outdir", - str(dist_path / project.name), - str(dist_path / project.name), - ], - check=True, - stdin=subprocess.DEVNULL if not verbose else None, - stdout=subprocess.DEVNULL if not verbose else None, - stderr=subprocess.DEVNULL if not verbose else None, + Console.spinner( + "Building python package...", + lambda: ( + subprocess.run( + [ + python, + "-m", + "build", + "--outdir", + str(dist_path / project.name), + str(dist_path / project.name), + ], + check=True, + stdin=subprocess.DEVNULL if not verbose else None, + stdout=subprocess.DEVNULL if not verbose else None, + stderr=subprocess.DEVNULL if not verbose else None, + ) + ), ) - Console.write_line("\nDone!") + Console.write_line(" Done!") diff --git a/src/cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py index 37fa4e5d..cb78ba12 100644 --- a/src/cli/cpl/cli/model/project.py +++ b/src/cli/cpl/cli/model/project.py @@ -3,7 +3,7 @@ import os import re import shutil from pathlib import Path -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Self from urllib.parse import urlparse from cpl.cli.model.build import Build @@ -228,42 +228,50 @@ class Project(CPLStructureModel): for p in references: os.chdir(Path(p.path).parent) - p.do_build(dist, verbose) + p.do_build(dist, verbose, self) os.chdir(old_dir) - def do_build(self, dist: Path, verbose: bool = False): - self.build_references(dist, verbose) - - Console.write_line(f"Building project {self.name}...") + def do_build(self, dist: Path, verbose: bool = False, parent: Self = None): if isinstance(dist, str): dist = Path(dist) - dist_path = dist / self.name - rel_dir = Path(self.path).parent / Path(self.directory) + dist_project = self if parent is None else parent + dist_path = (dist / dist_project.name / self.directory).resolve().absolute() + + if parent is None: + if verbose: + Console.write_line(f" Cleaning dist folder at {dist_path}...") + shutil.rmtree(str(dist_path), ignore_errors=True) if verbose: - Console.write_line(f" Collecting project '{self.name}' files...") + Console.write_line(f" Building references for project {self.name}...") - files = self._collect_files(rel_dir) + self.build_references(dist, verbose) - if len(files) == 0: - return + # Console.write_line(f"Building project {self.name}...") + def _build(): + if verbose: + Console.write_line(f" Collecting project '{self.name}' files...") - if verbose: - Console.write_line(f" Cleaning dist folder at {dist_path.absolute()}...") + rel_dir = (Path(self.path).parent / Path(self.directory)).absolute() + files = self._collect_files(rel_dir) + if len(files) == 0: + if verbose: + Console.write_line(f" No files found in {rel_dir}, skipping copy.") + return - shutil.rmtree(str(dist_path), ignore_errors=True) + for file in files: + rel_path = file.relative_to(rel_dir) + dest_file_path = dist_path / rel_path - for file in files: - rel_path = file.relative_to(rel_dir) - dest_file_path = dist_path / rel_path + if not dest_file_path.parent.exists(): + os.makedirs(dest_file_path.parent, exist_ok=True) - if not dest_file_path.parent.exists(): - os.makedirs(dest_file_path.parent, exist_ok=True) + shutil.copy(file, dest_file_path) - shutil.copy(file, dest_file_path) + if verbose: + Console.write_line(f" Copied {len(files)} files from {rel_dir} to {dist_path}") + Console.write_line(" Done!") - if verbose: - Console.write_line(f" Copied {len(files)} files from {rel_dir.absolute()} to {dist_path.absolute()}") - Console.write_line(" Done!") + Console.spinner(f"Building project {self.name}...", lambda: _build()) From a02c1014387df6a9b5ab902cf15586ca0809b981 Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 12 Oct 2025 18:20:07 +0200 Subject: [PATCH 14/22] Improved spinner --- src/cli/cpl/cli/command/project/build.py | 43 ++++++++++++------- src/core/cpl/core/console/_spinner.py | 54 ++++++++++++------------ src/core/cpl/core/console/console.py | 5 ++- 3 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py index 91225ec7..6a37c2fc 100644 --- a/src/cli/cpl/cli/command/project/build.py +++ b/src/cli/cpl/cli/command/project/build.py @@ -5,6 +5,7 @@ from pathlib import Path import click from cpl.cli.cli import cli +from cpl.cli.model.project import Project from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.cli.utils.venv import ensure_venv, get_venv_python from cpl.core.configuration import Configuration @@ -42,23 +43,33 @@ def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: b create_pyproject_toml(project, dist_path / project.name) python = str(get_venv_python(venv)) - Console.spinner( + result = Console.spinner( "Building python package...", - lambda: ( - subprocess.run( - [ - python, - "-m", - "build", - "--outdir", - str(dist_path / project.name), - str(dist_path / project.name), - ], - check=True, - stdin=subprocess.DEVNULL if not verbose else None, - stdout=subprocess.DEVNULL if not verbose else None, - stderr=subprocess.DEVNULL if not verbose else None, - ) + lambda: subprocess.run( + [ + python, + "-m", + "build", + "--outdir", + str(dist_path / project.name), + str(dist_path / project.name), + ], + check=True, + stdin=subprocess.DEVNULL if not verbose else None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ), ) + + if result is None: + raise RuntimeError("Build process did not run") + + if verbose and result.stdout is not None: + Console.write_line(result.stdout.decode()) + + if result.returncode != 0 and result.stderr is not None: + if result.stderr is not None: + Console.error(str(result.stderr.decode())) + raise RuntimeError(f"Build process failed with exit code {result.returncode}") + Console.write_line(" Done!") diff --git a/src/core/cpl/core/console/_spinner.py b/src/core/cpl/core/console/_spinner.py index a9f28ab2..1dcdf301 100644 --- a/src/core/cpl/core/console/_spinner.py +++ b/src/core/cpl/core/console/_spinner.py @@ -1,7 +1,5 @@ -import os import shutil import sys -import multiprocessing import time from multiprocessing import Process @@ -21,18 +19,28 @@ class Spinner(Process): Foreground color of the spinner background_color: :class:`cpl.core.console.background_color.BackgroundColorEnum` Background color of the spinner + done_char: :class:`str` """ - def __init__(self, msg_len: int, foreground_color: ForegroundColorEnum, background_color: BackgroundColorEnum): + def __init__( + self, + foreground_color: ForegroundColorEnum, + background_color: BackgroundColorEnum, + done_char: str = None, + msg_len: int = None, + ): Process.__init__(self) - self._msg_len = msg_len self._foreground_color = foreground_color self._background_color = background_color self._is_spinning = True self._exit = False + assert done_char is None or len(done_char) == 1, "done_char must be a single character" + self._done_char = done_char or "✓" + self._msg_len = msg_len + @staticmethod def _spinner(): r"""Selects active spinner char""" @@ -53,43 +61,33 @@ class Spinner(Process): def run(self) -> None: r"""Entry point of process, shows the spinner""" - columns = 0 - if sys.platform == "win32": - columns = os.get_terminal_size().columns - else: - size = shutil.get_terminal_size(fallback=(80, 24)) - columns = max(1, size.columns) - - end_msg = "done" - - padding = columns - self._msg_len - len(end_msg) - if padding > 0: - print(f'{"" : >{padding}}', end="") - else: - print("", end="") + size = shutil.get_terminal_size(fallback=(80, 24)) + columns = max(1, size.columns) spinner = self._spinner() + color_args = self._get_color_args() + + if self._msg_len is not None: + columns = min(columns, self._msg_len + 2) + while self._is_spinning: - print(colored(f"{next(spinner): >{len(end_msg)}}", *self._get_color_args()), end="") + frame = next(spinner) + sys.stdout.write(f"\033[{columns}G") + print(colored(frame, *color_args), end="", flush=True) time.sleep(0.1) - back = "" - for i in range(0, len(end_msg)): - back += "\b" - - print(back, end="") + sys.stdout.write(f"\033[{columns}G") sys.stdout.flush() - if not self._exit: - print(colored(end_msg, *self._get_color_args()), end="") - def stop(self): r"""Stops the spinner""" self._is_spinning = False - super().terminate() time.sleep(0.1) + print("\b" + colored(self._done_char, *self._get_color_args()), end="", flush=True) + super().terminate() def exit(self): r"""Stops the spinner""" self._is_spinning = False self._exit = True time.sleep(0.1) + super().terminate() diff --git a/src/core/cpl/core/console/console.py b/src/core/cpl/core/console/console.py index 86e6fa9c..d45e3158 100644 --- a/src/core/cpl/core/console/console.py +++ b/src/core/cpl/core/console/console.py @@ -435,6 +435,8 @@ class Console: message: str, call: Callable, *args, + done_char: str = None, + full_width: bool = False, text_foreground_color: Union[str, ForegroundColorEnum] = None, spinner_foreground_color: Union[str, ForegroundColorEnum] = None, text_background_color: Union[str, BackgroundColorEnum] = None, @@ -484,7 +486,8 @@ class Console: cls.set_hold_back(True) spinner = None if not cls._disabled: - spinner = Spinner(len(message), spinner_foreground_color, spinner_background_color) + msg_len = None if full_width else len(message) + 1 + spinner = Spinner(spinner_foreground_color, spinner_background_color, done_char=done_char, msg_len=msg_len) spinner.start() return_value = None From f1604f147732b2670ac119117f2903cf35783d6e Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 12 Oct 2025 20:22:40 +0200 Subject: [PATCH 15/22] Removed ref for dev --- src/cli/cpl.project.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index 91a1ef44..57786277 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -13,9 +13,7 @@ "devDependencies": { "black": "~25.9" }, - "references": [ - "../core/cpl.project.json" - ], + "references": [], "main": "cpl/cli/main.py", "directory": "cpl", "build": { From 33728cdec34f3e1709bcc9a94e82b1f0d3deb41f Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 12 Oct 2025 20:43:44 +0200 Subject: [PATCH 16/22] Added internal extra url --- src/cli/cpl.project.json | 1 - src/cli/cpl/cli/command/package/install.py | 14 ++++++++++++-- src/cli/cpl/cli/command/package/uninstall.py | 7 ++++++- src/cli/cpl/cli/command/package/update.py | 2 +- src/cli/cpl/cli/utils/pip.py | 2 +- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index 57786277..76a94d70 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -17,7 +17,6 @@ "main": "cpl/cli/main.py", "directory": "cpl", "build": { - "build": "python -m build", "include": [ "_templates/" ], diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index d2bb4aa2..fd1bb93f 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -19,7 +19,12 @@ def install(package: str, project: str, dev: bool, verbose: bool): if package is not None: Console.write_line(f"Installing {package} to '{project.name}':") try: - Pip.command("install", package, verbose=verbose, path=project.path) + Pip.command( + "install --extra-index-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/", + package, + verbose=verbose, + path=project.path, + ) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {package}: exit code {e.returncode}") return @@ -51,6 +56,11 @@ def install(package: str, project: str, dev: bool, verbose: bool): dep = Pip.normalize_dep(name, version) Console.write_line(f" -> {dep}") try: - Pip.command("install", dep, verbose=verbose, path=project.path) + Pip.command( + "install --extra-index-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/", + dep, + verbose=verbose, + path=project.path, + ) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {dep}: exit code {e.returncode}") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index ef1b5b7e..f856b41a 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -22,7 +22,12 @@ def uninstall(package: str, project: str, dev: bool, verbose: bool): deps = project.dependencies if not dev else project.dev_dependencies try: - Pip.command("uninstall -y", package, verbose=verbose, path=project.path) + Pip.command( + "uninstall -y", + package, + verbose=verbose, + path=project.path, + ) except subprocess.CalledProcessError as e: Console.error(f"Failed to uninstall {package}: exit code {e.returncode}") return diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index fed28888..ea602dc1 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -30,7 +30,7 @@ def update(package: str, project: str, dev: bool, verbose: bool): Console.write_line(f"Updating {package} to '{project.name}':") try: Pip.command( - "install --upgrade", + "install --upgrade --extra-index-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/", f"{Pip.normalize_dep(package, old_spec)}", verbose=verbose, path=project.path, diff --git a/src/cli/cpl/cli/utils/pip.py b/src/cli/cpl/cli/utils/pip.py index 70fbf343..8d9c0798 100644 --- a/src/cli/cpl/cli/utils/pip.py +++ b/src/cli/cpl/cli/utils/pip.py @@ -109,7 +109,7 @@ class Pip: Console.write_line(f"Running: {pip} {command} {''.join(args)}") subprocess.run( - [*pip.split(), command, *args], + [*pip.split(), *command.split(), *args], check=True, stdin=subprocess.DEVNULL if not verbose else None, stdout=subprocess.DEVNULL if not verbose else None, From 8d0bc13cc0d4a28e527df5d55c2b0acfbf5f5ff9 Mon Sep 17 00:00:00 2001 From: edraft Date: Mon, 13 Oct 2025 06:21:03 +0200 Subject: [PATCH 17/22] Added live dev server --- src/cli/cpl/cli/command/project/run.py | 2 +- src/cli/cpl/cli/command/project/start.py | 25 +++++ src/cli/cpl/cli/main.py | 4 +- src/cli/cpl/cli/model/project.py | 10 +- src/cli/cpl/cli/utils/live_server/__init__.py | 0 .../utils/live_server/file_event_handler.py | 51 +++++++++ .../cpl/cli/utils/live_server/live_server.py | 103 ++++++++++++++++++ src/cli/cpl/cli/utils/structure.py | 8 +- src/cli/cpl/cli/utils/venv.py | 2 +- src/cli/requirements.txt | 3 +- 10 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 src/cli/cpl/cli/command/project/start.py create mode 100644 src/cli/cpl/cli/utils/live_server/__init__.py create mode 100644 src/cli/cpl/cli/utils/live_server/file_event_handler.py create mode 100644 src/cli/cpl/cli/utils/live_server/live_server.py diff --git a/src/cli/cpl/cli/command/project/run.py b/src/cli/cpl/cli/command/project/run.py index 5bbd5aba..8b724a81 100644 --- a/src/cli/cpl/cli/command/project/run.py +++ b/src/cli/cpl/cli/command/project/run.py @@ -46,6 +46,6 @@ def run(project: str, args: list[str], dev: bool, verbose: bool): if verbose: Console.write_line(f" with args {args}...") - Console.write_line("\n\n") # add some space before output + Console.write_line("\n\n") subprocess.run([python, executable, *args], cwd=path) diff --git a/src/cli/cpl/cli/command/project/start.py b/src/cli/cpl/cli/command/project/start.py new file mode 100644 index 00000000..a380dcba --- /dev/null +++ b/src/cli/cpl/cli/command/project/start.py @@ -0,0 +1,25 @@ +import click + +from cpl.cli.cli import cli +from cpl.cli.utils.live_server.live_server import LiveServer +from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.core.console import Console + + +@cli.command("start", aliases=["s"]) +@click.argument("args", nargs=-1) +@click.option("--project", "-p", type=str) +@click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def start(project: str, args: list[str], dev: bool, verbose: bool): + project = get_project_by_name_or_path(project or "./") + if project.main is None: + Console.error(f"Project {project.name} has no executable") + return + + LiveServer( + project, + args, + dev, + verbose, + ).start() diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index be81881f..959c7bd0 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -1,3 +1,4 @@ +import sys from pathlib import Path from cpl.cli.cli import cli @@ -8,6 +9,7 @@ from cpl.cli.command.package.uninstall import uninstall from cpl.cli.command.package.update import update from cpl.cli.command.project.build import build from cpl.cli.command.project.run import run +from cpl.cli.command.project.start import start from cpl.cli.command.structure.init import init from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace @@ -63,7 +65,7 @@ def configure(): # run cli.add_command(build) cli.add_command(run) - # cli.add_command(start) + cli.add_command(start) def main(): diff --git a/src/cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py index cb78ba12..326eee68 100644 --- a/src/cli/cpl/cli/model/project.py +++ b/src/cli/cpl/cli/model/project.py @@ -232,7 +232,7 @@ class Project(CPLStructureModel): os.chdir(old_dir) - def do_build(self, dist: Path, verbose: bool = False, parent: Self = None): + def do_build(self, dist: Path, verbose: bool = False, parent: Self = None, silent: bool = False): if isinstance(dist, str): dist = Path(dist) @@ -249,7 +249,6 @@ class Project(CPLStructureModel): self.build_references(dist, verbose) - # Console.write_line(f"Building project {self.name}...") def _build(): if verbose: Console.write_line(f" Collecting project '{self.name}' files...") @@ -272,6 +271,11 @@ class Project(CPLStructureModel): if verbose: Console.write_line(f" Copied {len(files)} files from {rel_dir} to {dist_path}") - Console.write_line(" Done!") + if not silent: + Console.write_line(" Done!") + + if silent: + _build() + return Console.spinner(f"Building project {self.name}...", lambda: _build()) diff --git a/src/cli/cpl/cli/utils/live_server/__init__.py b/src/cli/cpl/cli/utils/live_server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cli/cpl/cli/utils/live_server/file_event_handler.py b/src/cli/cpl/cli/utils/live_server/file_event_handler.py new file mode 100644 index 00000000..009a0b7b --- /dev/null +++ b/src/cli/cpl/cli/utils/live_server/file_event_handler.py @@ -0,0 +1,51 @@ +import re +import time + +from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileClosedEvent + + +class FileEventHandler(FileSystemEventHandler): + + _ignore_patterns = [ + r".*~$", + r".*\.swp$", + r".*\.swx$", + r".*\.tmp$", + r".*__pycache__.*", + r".*\.pytest_cache.*", + r".*/\.idea/.*", + r".*/\.vscode/.*", + r".*\.DS_Store$", + r"#.*#$", + ] + _watch_extensions = (".py", ".json", ".yaml", ".yml", ".toml") + + def __init__(self, on_save, debounce_seconds: float = 0.5): + super().__init__() + self._on_save = on_save + self._debounce = debounce_seconds + self._last_triggered = 0 + self._last_file = None + + def _should_ignore(self, path: str) -> bool: + for pattern in self._ignore_patterns: + if re.match(pattern, path): + return True + return not path.endswith(self._watch_extensions) + + def _debounced_trigger(self, path: str): + now = time.time() + if path != self._last_file or now - self._last_triggered > self._debounce: + self._last_triggered = now + self._last_file = path + self._on_save(path) + + def on_modified(self, event): + if not event.is_directory and isinstance(event, FileModifiedEvent): + if not self._should_ignore(event.src_path): + self._debounced_trigger(event.src_path) + + def on_closed(self, event): + if not event.is_directory and isinstance(event, FileClosedEvent): + if not self._should_ignore(event.src_path): + self._debounced_trigger(event.src_path) diff --git a/src/cli/cpl/cli/utils/live_server/live_server.py b/src/cli/cpl/cli/utils/live_server/live_server.py new file mode 100644 index 00000000..acb98172 --- /dev/null +++ b/src/cli/cpl/cli/utils/live_server/live_server.py @@ -0,0 +1,103 @@ +import os +import subprocess +import time +from pathlib import Path + +from watchdog.observers import Observer + +from cpl.cli.model.project import Project +from cpl.cli.utils.live_server.file_event_handler import FileEventHandler +from cpl.cli.utils.venv import get_venv_python, ensure_venv +from cpl.core.configuration import Configuration +from cpl.core.console import Console + + +class LiveServer: + def __init__(self, project: Project, args: list[str], dev: bool = False, verbose: bool = False): + path = str(Path(project.path).parent.resolve().absolute()) + executable = (Path(project.path).parent / Path(project.directory) / project.main).resolve().absolute() + + self._dist_path = None + if not dev: + self._dist_path = Path(project.path).parent / "dist" + + if Configuration.get("workspace") is not None: + self._dist_path = Path(Configuration.get("workspace").path).parent / "dist" + + self._dist_path = Path(self._dist_path).resolve().absolute() + if verbose: + Console.write_line(f"Creating dist folder at {self._dist_path}...") + + os.makedirs(self._dist_path, exist_ok=True) + project.do_build(self._dist_path, verbose) + path = self._dist_path / project.name / project.directory + main = project.main.replace(project.directory, "").lstrip("/\\") + + executable = (path / main).resolve().absolute() + + if not os.path.isfile(executable): + Console.error(f"Executable {executable} not found.") + return + + self._project = project + self._sources = (Path(project.path).parent / Path(project.directory)).resolve().absolute() + self._executable = executable + self._working_directory = Path(path) + self._args = args + self._is_dev = dev + self._verbose = verbose + + self._process = None + self._observer = None + self._python = str(get_venv_python(ensure_venv(Path("./"))).absolute()) + + def _run_executable(self): + if self._process: + self._process.terminate() + self._process.wait() + + self._process = subprocess.Popen( + [self._python, str(self._executable), *self._args], + cwd=self._working_directory, + ) + + def _on_change(self, changed_file: str): + if self._verbose: + Console.write_line(f"Change detected: {changed_file}") + + if self._is_dev and self._process: + self._process.terminate() + self._process.wait() + + Console.write_line("Restart\n\n") + time.sleep(0.5) # debounce to avoid copy temp files + if not self._is_dev: + self._project.do_build(self._dist_path, verbose=self._verbose, silent=True) + + self._run_executable() + + def start(self): + handler = FileEventHandler(self._on_change) + observer = Observer() + observer.schedule(handler, str(self._sources), recursive=True) + observer.start() + self._observer = observer + + Console.write_line("** CPL live development server is running **\n") + Console.write_line(f"Watching {self._sources} ... (Ctrl+C to stop)") + Console.write_line(f"Starting {self._executable} ...\n\n") + self._run_executable() + + try: + while True: + time.sleep(1) + except KeyboardInterrupt as e: + time.sleep(1) + Console.write_line("Stopping...") + finally: + if self._process: + self._process.terminate() + self._process.wait() + observer.stop() + observer.join() + Console.close() diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index a2c50dbe..c8952527 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -26,7 +26,6 @@ def get_project_by_name_or_path(project: str) -> Project: if p.name == project: return Project.from_file(Path(p.path)) - if not path.is_dir() and not path.is_file(): raise ValueError(f"Unknown project {project}") @@ -37,12 +36,14 @@ def get_project_by_name_or_path(project: str) -> Project: raise ValueError(f"Project '{project}' not found.") + def create_pyproject_toml(project: Project, path: Path): pyproject_path = path / "pyproject.toml" if pyproject_path.exists(): return - content = textwrap.dedent(f""" + content = textwrap.dedent( + f""" [build-system] requires = ["setuptools>=70.1.0", "wheel", "build"] build-backend = "setuptools.build_meta" @@ -53,6 +54,7 @@ def create_pyproject_toml(project: Project, path: Path): authors = [{{name="{project.author or ''}"}}] license = "{project.license or ''}" dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] - """).lstrip() + """ + ).lstrip() pyproject_path.write_text(content) diff --git a/src/cli/cpl/cli/utils/venv.py b/src/cli/cpl/cli/utils/venv.py index c8806f33..580f0639 100644 --- a/src/cli/cpl/cli/utils/venv.py +++ b/src/cli/cpl/cli/utils/venv.py @@ -28,7 +28,7 @@ def ensure_venv(start_path: Path | None = None) -> Path: else: venv_path = start_path / ".venv" - Console.write_line(f"Creating virtual environment at {venv_path}...") + Console.write_line(f"Creating virtual environment at {venv_path.absolute()}...") venv.EnvBuilder(with_pip=True).create(venv_path) return venv_path diff --git a/src/cli/requirements.txt b/src/cli/requirements.txt index 838c3e83..06b0dafe 100644 --- a/src/cli/requirements.txt +++ b/src/cli/requirements.txt @@ -1,2 +1,3 @@ cpl-core -click==8.3.0 \ No newline at end of file +click==8.3.0 +watchdog==6.0.0 \ No newline at end of file From 98ed458d7c9e5a5bcd07aa57bf39ac0dc1279ce3 Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 16 Oct 2025 12:46:11 +0200 Subject: [PATCH 18/22] Added cpl generate --- src/cli/cpl.project.json | 2 +- .../cpl/cli/.cpl/generate/abc.py.schematic | 9 ++ .../cpl/cli/.cpl/generate/app.py.schematic | 16 +++ .../cpl/cli/.cpl/generate/config.py.schematic | 10 ++ .../generate/data_access_object.py.schematic | 9 ++ .../cli/.cpl/generate/db_model.py.schematic | 23 ++++ .../.cpl/generate/db_model_join.py.schematic | 29 +++++ .../cpl/cli/.cpl/generate/enum.py.schematic | 5 + .../cpl/cli/.cpl/generate/logger.py.schematic | 7 + .../cpl/cli/.cpl/generate/module.py.schematic | 17 +++ .../.cpl/generate/multiprocess.py.schematic | 9 ++ .../cpl/cli/.cpl/generate/pipe.py.schematic | 11 ++ .../cpl/cli/.cpl/generate/thread.py.schematic | 9 ++ .../cli/.cpl/generate/web_app.py.schematic | 18 +++ src/cli/cpl/cli/command/structure/generate.py | 122 ++++++++++++++++++ src/cli/cpl/cli/main.py | 2 + src/cli/cpl/cli/model/workspace.py | 10 ++ src/cli/cpl/cli/utils/NameUtils.py | 13 ++ src/cli/cpl/cli/utils/template_collector.py | 41 ++++++ .../cpl/dependency/module/__init__.py | 1 + 20 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 src/cli/cpl/cli/.cpl/generate/abc.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/app.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/config.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/db_model.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/enum.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/logger.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/module.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/pipe.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/thread.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/web_app.py.schematic create mode 100644 src/cli/cpl/cli/command/structure/generate.py create mode 100644 src/cli/cpl/cli/utils/NameUtils.py create mode 100644 src/cli/cpl/cli/utils/template_collector.py diff --git a/src/cli/cpl.project.json b/src/cli/cpl.project.json index 76a94d70..852a8a28 100644 --- a/src/cli/cpl.project.json +++ b/src/cli/cpl.project.json @@ -15,7 +15,7 @@ }, "references": [], "main": "cpl/cli/main.py", - "directory": "cpl", + "directory": "cpl/cli", "build": { "include": [ "_templates/" diff --git a/src/cli/cpl/cli/.cpl/generate/abc.py.schematic b/src/cli/cpl/cli/.cpl/generate/abc.py.schematic new file mode 100644 index 00000000..4443bc8f --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/abc.py.schematic @@ -0,0 +1,9 @@ +from abc import ABC + + +class ABC(ABC): + + def __init__(self): + ABC.__init__(self) + + print(" initialized") diff --git a/src/cli/cpl/cli/.cpl/generate/app.py.schematic b/src/cli/cpl/cli/.cpl/generate/app.py.schematic new file mode 100644 index 00000000..6d332b1e --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/app.py.schematic @@ -0,0 +1,16 @@ +from cpl.application.abc import ApplicationABC +from cpl.core.environment import Environment +from cpl.core.log import LoggerABC +from cpl.dependency import ServiceProvider +from cpl.dependency.typing import Modules + + +class (ApplicationABC): + def __init__(self, services: ServiceProvider, modules: Modules): + ApplicationABC.__init__(self, services, modules) + + self._logger = services.get_service(LoggerABC) + + async def main(self): + self._logger.debug(f"Host: {Environment.get_host_name()}") + self._logger.debug(f"Environment: {Environment.get_environment()}") diff --git a/src/cli/cpl/cli/.cpl/generate/config.py.schematic b/src/cli/cpl/cli/.cpl/generate/config.py.schematic new file mode 100644 index 00000000..a3420af4 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/config.py.schematic @@ -0,0 +1,10 @@ +from cpl.core.configuration import ConfigurationModelABC + + +class Config(ConfigurationModelABC): + + def __init__( + self, + src: dict = None, + ): + ConfigurationModelABC.__init__(self, src) diff --git a/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic b/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic new file mode 100644 index 00000000..4dd329d4 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/data_access_object.py.schematic @@ -0,0 +1,9 @@ +from cpl.database.abc import DbModelDaoABC + + +class Dao(DbModelDaoABC[]): + + def __init__(self): + DbModelDaoABC.__init__(self, , "") + + self.attribute(.name, str) diff --git a/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic b/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic new file mode 100644 index 00000000..1d4ae23b --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/db_model.py.schematic @@ -0,0 +1,23 @@ +from datetime import datetime +from typing import Self + +from cpl.core.typing import SerialId +from cpl.database.abc import DbModelABC + + +class (DbModelABC[Self]): + def __init__( + self, + id: SerialId, + name: str, + deleted: bool = False, + editor_id: SerialId | None = None, + created: datetime | None = None, + updated: datetime | None = None, + ): + DbModelABC.__init__(self, id, deleted, editor_id, created, updated) + self._name = name + + @property + def name(self) -> str: + return self._name diff --git a/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic b/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic new file mode 100644 index 00000000..d800a952 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/db_model_join.py.schematic @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Self + +from cpl.core.typing import SerialId +from cpl.database.abc import DbJoinModelABC + + +class Join(DbJoinModelABC[Self]): + def __init__( + self, + id: SerialId, + source_id: SerialId, + reference_id: SerialId, + deleted: bool = False, + editor_id: SerialId | None = None, + created: datetime | None = None, + updated: datetime | None = None, + ): + DbJoinModelABC.__init__(self, source_id, reference_id, id, deleted, editor_id, created, updated) + self._source_id = source_id + self._reference_id = reference_id + + @property + def source_id(self) -> int: + return self._source_id + + @property + def reference(self) -> int: + return self._reference_id diff --git a/src/cli/cpl/cli/.cpl/generate/enum.py.schematic b/src/cli/cpl/cli/.cpl/generate/enum.py.schematic new file mode 100644 index 00000000..ed2e4e94 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/enum.py.schematic @@ -0,0 +1,5 @@ +from enum import Enum + + +class Enum(Enum): + KEY = "value" diff --git a/src/cli/cpl/cli/.cpl/generate/logger.py.schematic b/src/cli/cpl/cli/.cpl/generate/logger.py.schematic new file mode 100644 index 00000000..aa9b144f --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/logger.py.schematic @@ -0,0 +1,7 @@ +from cpl.core.log.wrapped_logger import WrappedLogger + + +class Logger(WrappedLogger): + + def __init__(self): + WrappedLogger.__init__(self, "") diff --git a/src/cli/cpl/cli/.cpl/generate/module.py.schematic b/src/cli/cpl/cli/.cpl/generate/module.py.schematic new file mode 100644 index 00000000..a4901060 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/module.py.schematic @@ -0,0 +1,17 @@ +from cpl.dependency import ServiceCollection, ServiceProvider +from cpl.dependency.module import Module + + +class Module(Module): + dependencies = [] + configuration = [] + singleton = [] + scoped = [] + transient = [] + hosted = [] + + @staticmethod + def register(collection: ServiceCollection): ... + + @staticmethod + def configure(provider: ServiceProvider): ... diff --git a/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic b/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic new file mode 100644 index 00000000..be7fa608 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/multiprocess.py.schematic @@ -0,0 +1,9 @@ +import multiprocessing + + +class (multiprocessing.Process): + + def __init__(self): + multiprocessing.Process.__init__(self) + + def run(self): ... \ No newline at end of file diff --git a/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic b/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic new file mode 100644 index 00000000..65a30bd0 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/pipe.py.schematic @@ -0,0 +1,11 @@ +from cpl.core.pipes import PipeABC +from cpl.core.typing import T + + +class Pipe(PipeABC): + + @staticmethod + def to_str(value: T, *args) -> str: ... + + @staticmethod + def from_str(value: str, *args) -> T: ... diff --git a/src/cli/cpl/cli/.cpl/generate/thread.py.schematic b/src/cli/cpl/cli/.cpl/generate/thread.py.schematic new file mode 100644 index 00000000..438681e6 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/thread.py.schematic @@ -0,0 +1,9 @@ +import threading + + +class (threading.Thread): + + def __init__(self): + threading.Thread.__init__(self) + + def run(self): ... \ No newline at end of file diff --git a/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic b/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic new file mode 100644 index 00000000..5f63cc0b --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/web_app.py.schematic @@ -0,0 +1,18 @@ +from cpl.api.application import WebApp +from cpl.core.environment import Environment +from cpl.core.log import LoggerABC +from cpl.dependency import ServiceProvider +from cpl.dependency.typing import Modules + + +class (WebApp): + def __init__(self, services: ServiceProvider, modules: Modules): + WebApp.__init__(self, services, modules) + + self._logger = services.get_service(LoggerABC) + + async def main(self): + self._logger.debug(f"Host: {Environment.get_host_name()}") + self._logger.debug(f"Environment: {Environment.get_environment()}") + + await super().main() diff --git a/src/cli/cpl/cli/command/structure/generate.py b/src/cli/cpl/cli/command/structure/generate.py new file mode 100644 index 00000000..7a113000 --- /dev/null +++ b/src/cli/cpl/cli/command/structure/generate.py @@ -0,0 +1,122 @@ +import os +import re +from pathlib import Path + +import click + +from cpl.cli import cli as clim +from cpl.cli.cli import cli +from cpl.cli.model.project import Project +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.template_collector import TemplateCollector +from cpl.core.configuration import Configuration +from cpl.core.console import Console +from cpl.core.utils import String + + +@cli.command("generate", aliases=["g"]) +@click.argument("schematic", type=click.STRING, required=True) +@click.argument("name", type=click.STRING, required=True) +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def generate(schematic: str, name: str, verbose: bool) -> None: + TemplateCollector.collect_templates(Path(clim.__file__).parent, verbose) + + workspace: Workspace = Configuration.get("workspace") + if workspace is not None: + if verbose: + Console.write_line("Workspace found, collecting templates...") + TemplateCollector.collect_templates(Path(workspace.path).parent, verbose) + + project = None + try: + project = get_project_by_name_or_path("./") + if verbose: + Console.write_line("project found, collecting templates...") + + TemplateCollector.collect_templates(Path(project.path).parent, verbose) + except ValueError: + if verbose: + Console.write_line("Local project not found") + + templates = TemplateCollector.get_templates() + schematics = {} + for template_name, template_content in templates.items(): + t_name = template_name.split(".")[0] + + if t_name in schematics: + raise ValueError(f"Duplicate schematic name found: {t_name}") + + schematics[t_name] = template_name + + for i in range(len(t_name)): + char = t_name[i] + if char in schematics: + continue + + schematics[char] = template_name + break + + if schematic not in schematics: + raise ValueError( + f"Schematic '{schematic}' not found. Available schematics: {', '.join([x.split(".")[0] for x in templates.keys()])}" + ) + + path, name = _get_name_and_path_from_name(name, project) + + os.makedirs(path, exist_ok=True) + + Console.write_line(f"Generating {str(path / name)} ...") + with open(path / f"{name}.py", "w") as f: + f.write( + _render_template(schematics[schematic].split(".")[0], templates[schematics[schematic]], name, str(path)) + ) + + +def _get_name_and_path_from_name(in_name: str, project: Project = None) -> tuple[Path, str]: + path = "" + name = "" + + in_name_parts = in_name.split("/") + if len(in_name_parts) == 1: + name = in_name_parts[0] + else: + path = "/".join(in_name_parts[:-1]) + name = in_name_parts[-1] + + workspace: Workspace = Configuration.get("workspace") + if workspace is None and project is not None: + return (Path(project.path).parent / project.directory / path).resolve().absolute(), name + elif workspace is None and project is None: + return Path(path).resolve().absolute(), name + + selected_project = project + project_name = path.split("/")[0] + project_by_name = workspace.get_project_by_name(project_name) + if project_by_name is not None: + selected_project = project_by_name + path = "/".join(path.split("/")[1:]) + + if selected_project is None: + selected_project = workspace.get_project_by_name(workspace.default_project) + + return (Path(selected_project.path).parent / selected_project.directory / path).resolve().absolute(), name + + +def _render_template(schematic, template_str: str, name: str, path: str) -> str: + context = { + "schematic": schematic, + "Name": String.to_pascal_case(name), + "name": String.to_snake_case(name), + "NAME": String.to_snake_case(name).upper(), + "camelName": String.to_camel_case(name), + "multi_Name": f"{String.to_pascal_case(name)}s", + "multi_name": f"{String.to_snake_case(name)}s", + "multi_NAME": f"{String.to_snake_case(name).upper()}s", + "multi_camelName": f"{String.to_camel_case(name)}s", + "path": path.replace("\\", "/"), + } + + for key, value in context.items(): + template_str = template_str.replace(f"<{key}>", value) + return template_str diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index 959c7bd0..e85d6a5d 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -10,6 +10,7 @@ from cpl.cli.command.package.update import update from cpl.cli.command.project.build import build from cpl.cli.command.project.run import run from cpl.cli.command.project.start import start +from cpl.cli.command.structure.generate import generate from cpl.cli.command.structure.init import init from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace @@ -54,6 +55,7 @@ def configure(): # structure cli.add_command(init) # cli.add_command(new) + cli.add_command(generate) # packaging cli.add_command(install) diff --git a/src/cli/cpl/cli/model/workspace.py b/src/cli/cpl/cli/model/workspace.py index 77d1880f..b79c6a2e 100644 --- a/src/cli/cpl/cli/model/workspace.py +++ b/src/cli/cpl/cli/model/workspace.py @@ -50,6 +50,10 @@ class Workspace(CPLStructureModel): def actual_projects(self) -> List[Project]: return [Project.from_file(p) for p in self._projects] + @property + def project_names(self) -> List[str]: + return [Project.from_file(p).name for p in self._projects if "name" in p] + @property def default_project(self) -> Optional[str]: return self._default_project @@ -65,3 +69,9 @@ class Workspace(CPLStructureModel): @scripts.setter def scripts(self, value: Dict[str, str]): self._scripts = self._require_dict_str_str(value, "scripts") + + def get_project_by_name(self, name: str) -> Optional[Project]: + for project in self.actual_projects: + if project.name == name: + return project + return None diff --git a/src/cli/cpl/cli/utils/NameUtils.py b/src/cli/cpl/cli/utils/NameUtils.py new file mode 100644 index 00000000..3d41459f --- /dev/null +++ b/src/cli/cpl/cli/utils/NameUtils.py @@ -0,0 +1,13 @@ +class NameUtils: + @staticmethod + def classify(name: str) -> str: # UserService + return "".join(w.capitalize() for w in name.replace("-", "_").split("_")) + + @staticmethod + def dasherize(name: str) -> str: # user-service + return name.replace("_", "-").lower() + + @staticmethod + def camelize(name: str) -> str: # userService + parts = name.split("-") + return parts[0] + "".join(w.capitalize() for w in parts[1:]) diff --git a/src/cli/cpl/cli/utils/template_collector.py b/src/cli/cpl/cli/utils/template_collector.py new file mode 100644 index 00000000..6de54115 --- /dev/null +++ b/src/cli/cpl/cli/utils/template_collector.py @@ -0,0 +1,41 @@ +import os +from pathlib import Path + +from cpl.core.console import Console + + +class TemplateCollector: + _templates = {} + _collected_paths = [] + + @classmethod + def get_templates(cls) -> dict[str, str]: + return cls._templates + + @classmethod + def collect_templates(cls, directory: Path, verbose=False): + if not directory.exists() or not directory.is_dir(): + raise FileNotFoundError(f"Directory '{directory}' does not exist") + + if not str(directory).endswith(".cpl/generate"): + directory = directory / ".cpl" / "generate" + + directory = directory.resolve().absolute() + + if directory in cls._collected_paths: + return + + cls._collected_paths.append(directory) + if not directory.exists() or not directory.is_dir(): + if verbose: + Console.write_line(f"No templates found in {directory}") + return + + templates = {} + for root, _, files in os.walk(directory): + for file in files: + if file.endswith(".schematic"): + with open(os.path.join(root, file), "r") as f: + templates[os.path.relpath(os.path.join(root, file), directory)] = f.read() + + cls._templates.update(templates) diff --git a/src/dependency/cpl/dependency/module/__init__.py b/src/dependency/cpl/dependency/module/__init__.py index e69de29b..8f210ac9 100644 --- a/src/dependency/cpl/dependency/module/__init__.py +++ b/src/dependency/cpl/dependency/module/__init__.py @@ -0,0 +1 @@ +from .module import Module From 8ebac05dd8d8e538eb6e04891b102370b2e86eda Mon Sep 17 00:00:00 2001 From: edraft Date: Thu, 16 Oct 2025 18:19:36 +0200 Subject: [PATCH 19/22] Cleanup --- src/cli/cpl/cli/command/structure/generate.py | 26 +++---------------- .../cli/utils/{NameUtils.py => name-utils.py} | 0 src/cli/cpl/cli/utils/template_renderer.py | 23 ++++++++++++++++ 3 files changed, 27 insertions(+), 22 deletions(-) rename src/cli/cpl/cli/utils/{NameUtils.py => name-utils.py} (100%) create mode 100644 src/cli/cpl/cli/utils/template_renderer.py diff --git a/src/cli/cpl/cli/command/structure/generate.py b/src/cli/cpl/cli/command/structure/generate.py index 7a113000..150c75f1 100644 --- a/src/cli/cpl/cli/command/structure/generate.py +++ b/src/cli/cpl/cli/command/structure/generate.py @@ -1,5 +1,4 @@ import os -import re from pathlib import Path import click @@ -10,9 +9,9 @@ from cpl.cli.model.project import Project from cpl.cli.model.workspace import Workspace from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.cli.utils.template_collector import TemplateCollector +from cpl.cli.utils.template_renderer import TemplateRenderer from cpl.core.configuration import Configuration from cpl.core.console import Console -from cpl.core.utils import String @cli.command("generate", aliases=["g"]) @@ -69,7 +68,9 @@ def generate(schematic: str, name: str, verbose: bool) -> None: Console.write_line(f"Generating {str(path / name)} ...") with open(path / f"{name}.py", "w") as f: f.write( - _render_template(schematics[schematic].split(".")[0], templates[schematics[schematic]], name, str(path)) + TemplateRenderer.render_template( + schematics[schematic].split(".")[0], templates[schematics[schematic]], name, str(path) + ) ) @@ -101,22 +102,3 @@ def _get_name_and_path_from_name(in_name: str, project: Project = None) -> tuple selected_project = workspace.get_project_by_name(workspace.default_project) return (Path(selected_project.path).parent / selected_project.directory / path).resolve().absolute(), name - - -def _render_template(schematic, template_str: str, name: str, path: str) -> str: - context = { - "schematic": schematic, - "Name": String.to_pascal_case(name), - "name": String.to_snake_case(name), - "NAME": String.to_snake_case(name).upper(), - "camelName": String.to_camel_case(name), - "multi_Name": f"{String.to_pascal_case(name)}s", - "multi_name": f"{String.to_snake_case(name)}s", - "multi_NAME": f"{String.to_snake_case(name).upper()}s", - "multi_camelName": f"{String.to_camel_case(name)}s", - "path": path.replace("\\", "/"), - } - - for key, value in context.items(): - template_str = template_str.replace(f"<{key}>", value) - return template_str diff --git a/src/cli/cpl/cli/utils/NameUtils.py b/src/cli/cpl/cli/utils/name-utils.py similarity index 100% rename from src/cli/cpl/cli/utils/NameUtils.py rename to src/cli/cpl/cli/utils/name-utils.py diff --git a/src/cli/cpl/cli/utils/template_renderer.py b/src/cli/cpl/cli/utils/template_renderer.py new file mode 100644 index 00000000..a6111f20 --- /dev/null +++ b/src/cli/cpl/cli/utils/template_renderer.py @@ -0,0 +1,23 @@ +from cpl.core.utils import String + + +class TemplateRenderer: + + @staticmethod + def render_template(schematic, template_str: str, name: str, path: str) -> str: + context = { + "schematic": schematic, + "Name": String.to_pascal_case(name), + "name": String.to_snake_case(name), + "NAME": String.to_snake_case(name).upper(), + "camelName": String.to_camel_case(name), + "multi_Name": f"{String.to_pascal_case(name)}s", + "multi_name": f"{String.to_snake_case(name)}s", + "multi_NAME": f"{String.to_snake_case(name).upper()}s", + "multi_camelName": f"{String.to_camel_case(name)}s", + "path": path.replace("\\", "/"), + } + + for key, value in context.items(): + template_str = template_str.replace(f"<{key}>", value) + return template_str \ No newline at end of file From 76b44ca5173a91ee81e7de0109aee9c57b6e1a39 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 18 Oct 2025 17:48:49 +0200 Subject: [PATCH 20/22] Added cpl new --- cpl.workspace.json | 3 +- .../cpl/application/application_builder.py | 2 +- src/application/cpl/application/host.py | 30 ++- src/cli/cpl/cli/.cpl/new/console/main.py | 9 + src/cli/cpl/cli/.cpl/new/graphql/main.py | 46 +++++ src/cli/cpl/cli/.cpl/new/library/class.py | 3 + src/cli/cpl/cli/.cpl/new/service/main.py | 13 ++ .../cli/.cpl/new/service/my_hosted_service.py | 13 ++ src/cli/cpl/cli/.cpl/new/web/main.py | 37 ++++ src/cli/cpl/cli/command/package/add.py | 6 +- src/cli/cpl/cli/command/package/install.py | 4 +- src/cli/cpl/cli/command/package/remove.py | 6 +- src/cli/cpl/cli/command/package/uninstall.py | 4 +- src/cli/cpl/cli/command/package/update.py | 4 +- src/cli/cpl/cli/command/project/build.py | 10 +- src/cli/cpl/cli/command/project/run.py | 10 +- src/cli/cpl/cli/command/project/start.py | 12 +- src/cli/cpl/cli/command/structure/generate.py | 4 +- src/cli/cpl/cli/command/structure/init.py | 61 +----- src/cli/cpl/cli/command/structure/new.py | 64 +++++++ src/cli/cpl/cli/main.py | 5 +- src/cli/cpl/cli/model/build.py | 4 + src/cli/cpl/cli/model/cpl_structure_model.py | 4 + src/cli/cpl/cli/model/project.py | 4 +- src/cli/cpl/cli/utils/structure.py | 177 ++++++++++++++---- src/cli/cpl/cli/utils/template_renderer.py | 2 +- src/cli/run | 7 +- src/core/cpl/core/__init__.py | 1 - .../cpl/core/configuration/configuration.py | 1 - src/core/cpl/core/console/console.py | 6 +- src/core/cpl/core/property.py | 3 + .../cpl/graphql/application/__init__.py | 2 +- 32 files changed, 427 insertions(+), 130 deletions(-) create mode 100644 src/cli/cpl/cli/.cpl/new/console/main.py create mode 100644 src/cli/cpl/cli/.cpl/new/graphql/main.py create mode 100644 src/cli/cpl/cli/.cpl/new/library/class.py create mode 100644 src/cli/cpl/cli/.cpl/new/service/main.py create mode 100644 src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py create mode 100644 src/cli/cpl/cli/.cpl/new/web/main.py create mode 100644 src/cli/cpl/cli/command/structure/new.py create mode 100644 src/core/cpl/core/property.py diff --git a/cpl.workspace.json b/cpl.workspace.json index b0e9f4da..c45ad1ab 100644 --- a/cpl.workspace.json +++ b/cpl.workspace.json @@ -2,7 +2,8 @@ "name": "cpl", "projects": [ "src/cli/cpl.project.json", - "src/core/cpl.project.json" + "src/core/cpl.project.json", + "test/cpl.project.json" ], "defaultProject": "cpl-cli", "scripts": { diff --git a/src/application/cpl/application/application_builder.py b/src/application/cpl/application/application_builder.py index 3d3d1529..97b58154 100644 --- a/src/application/cpl/application/application_builder.py +++ b/src/application/cpl/application/application_builder.py @@ -14,7 +14,7 @@ TApp = TypeVar("TApp", bound=ApplicationABC) class ApplicationBuilder(Generic[TApp]): - def __init__(self, app: Type[ApplicationABC]): + def __init__(self, app: Type[TApp]): assert app is not None, "app must not be None" assert issubclass(app, ApplicationABC), "app must be an subclass of ApplicationABC or its subclass" diff --git a/src/application/cpl/application/host.py b/src/application/cpl/application/host.py index f1309beb..0aeac147 100644 --- a/src/application/cpl/application/host.py +++ b/src/application/cpl/application/host.py @@ -1,7 +1,9 @@ import asyncio from typing import Callable -from cpl.dependency import get_provider +from cpl.core.property import classproperty +from cpl.dependency.context import get_provider, use_root_provider +from cpl.dependency.service_collection import ServiceCollection from cpl.dependency.hosted.startup_task import StartupTask @@ -9,6 +11,24 @@ class Host: _loop: asyncio.AbstractEventLoop | None = None _tasks: dict = {} + _service_collection: ServiceCollection | None = None + + @classproperty + def services(cls) -> ServiceCollection: + if cls._service_collection is None: + cls._service_collection = ServiceCollection() + + return cls._service_collection + + @classmethod + def get_provider(cls): + provider = get_provider() + if provider is None: + provider = cls.services.build() + use_root_provider(provider) + + return provider + @classmethod def get_loop(cls) -> asyncio.AbstractEventLoop: if cls._loop is None: @@ -18,7 +38,7 @@ class Host: @classmethod def run_start_tasks(cls): - provider = get_provider() + provider = cls.get_provider() tasks = provider.get_services(StartupTask) loop = cls.get_loop() @@ -30,7 +50,7 @@ class Host: @classmethod def run_hosted_services(cls): - provider = get_provider() + provider = cls.get_provider() services = provider.get_hosted_services() loop = cls.get_loop() @@ -49,6 +69,10 @@ class Host: cls._tasks.clear() + @classmethod + async def wait_for_all(cls): + await asyncio.gather(*cls._tasks.values()) + @classmethod def run_app(cls, func: Callable, *args, **kwargs): cls.run_start_tasks() diff --git a/src/cli/cpl/cli/.cpl/new/console/main.py b/src/cli/cpl/cli/.cpl/new/console/main.py new file mode 100644 index 00000000..0b4aff83 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/console/main.py @@ -0,0 +1,9 @@ +from cpl.core.console import Console + + +def main(): + Console.write_line("Hello, World!") + + +if __name__ == "__main__": + main() diff --git a/src/cli/cpl/cli/.cpl/new/graphql/main.py b/src/cli/cpl/cli/.cpl/new/graphql/main.py new file mode 100644 index 00000000..4c1e3a6e --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/graphql/main.py @@ -0,0 +1,46 @@ +from cpl.api import ApiModule +from cpl.application import ApplicationBuilder +from cpl.core.configuration import Configuration +from cpl.graphql.application import GraphQLApp +from starlette.responses import JSONResponse + + +def main(): + builder = ApplicationBuilder[GraphQLApp](GraphQLApp) + + Configuration.add_json_file(f"appsettings.json", optional=True) + + ( + builder.services.add_logging() + # uncomment to add preferred database module + # .add_module(MySQLModule) + # .add_module(PostgresModule) + .add_module(ApiModule) + ) + + app = builder.build() + app.with_logging() + + app.with_authentication() + app.with_authorization() + + app.with_route( + path="/ping", + fn=lambda r: JSONResponse("pong"), + method="GET", + ) + + schema = app.with_graphql() + schema.query.string_field("ping", resolver=lambda: "pong") + + app.with_auth_root_queries(True) + app.with_auth_root_mutations(True) + + app.with_playground() + app.with_graphiql() + + app.run() + + +if __name__ == "__main__": + main() diff --git a/src/cli/cpl/cli/.cpl/new/library/class.py b/src/cli/cpl/cli/.cpl/new/library/class.py new file mode 100644 index 00000000..84d43c42 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/library/class.py @@ -0,0 +1,3 @@ +class Class1: + + def __init__(self): ... diff --git a/src/cli/cpl/cli/.cpl/new/service/main.py b/src/cli/cpl/cli/.cpl/new/service/main.py new file mode 100644 index 00000000..41829500 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/service/main.py @@ -0,0 +1,13 @@ +from cpl.application import Host +from my_hosted_service import MyHostedService + + +async def main(): + Host.services.add_hosted_service(MyHostedService) + Host.run_start_tasks() + Host.run_hosted_services() + await Host.wait_for_all() + + +if __name__ == "__main__": + Host.run(main) diff --git a/src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py b/src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py new file mode 100644 index 00000000..016b8c61 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/service/my_hosted_service.py @@ -0,0 +1,13 @@ +from cpl.core.console import Console +from cpl.dependency.hosted import HostedService + + +class MyHostedService(HostedService): + def __init__(self): + HostedService.__init__(self) + + async def start(self): + Console.write_line("Hello, World!") + + async def stop(self): + Console.write_line("Goodbye, World!") diff --git a/src/cli/cpl/cli/.cpl/new/web/main.py b/src/cli/cpl/cli/.cpl/new/web/main.py new file mode 100644 index 00000000..4d23fa9f --- /dev/null +++ b/src/cli/cpl/cli/.cpl/new/web/main.py @@ -0,0 +1,37 @@ +from starlette.responses import JSONResponse + +from cpl.api import ApiModule +from cpl.api.application import WebApp +from cpl.application import ApplicationBuilder +from cpl.core.configuration import Configuration + + +def main(): + builder = ApplicationBuilder[WebApp](WebApp) + + Configuration.add_json_file(f"appsettings.json", optional=True) + + ( + builder.services.add_logging() + # uncomment to add preferred database module + # .add_module(MySQLModule) + # .add_module(PostgresModule) + .add_module(ApiModule) + ) + + app = builder.build() + app.with_logging() + + app.with_authentication() + app.with_authorization() + + app.with_route( + path="/ping", + fn=lambda r: JSONResponse("pong"), + method="GET", + ) + app.run() + + +if __name__ == "__main__": + main() diff --git a/src/cli/cpl/cli/command/package/add.py b/src/cli/cpl/cli/command/package/add.py index ee4afc5d..c5c21e63 100644 --- a/src/cli/cpl/cli/command/package/add.py +++ b/src/cli/cpl/cli/command/package/add.py @@ -3,7 +3,7 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -12,8 +12,8 @@ from cpl.core.console import Console @click.argument("target", type=click.STRING, required=True) @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def add(reference: str, target: str, verbose: bool): - reference_project = get_project_by_name_or_path(reference) - target_project = get_project_by_name_or_path(target) + reference_project = Structure.get_project_by_name_or_path(reference) + target_project = Structure.get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index fd1bb93f..b06d68f8 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -14,7 +14,7 @@ from cpl.core.console import Console @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def install(package: str, project: str, dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") if package is not None: Console.write_line(f"Installing {package} to '{project.name}':") diff --git a/src/cli/cpl/cli/command/package/remove.py b/src/cli/cpl/cli/command/package/remove.py index 230580fd..c1e8e3d6 100644 --- a/src/cli/cpl/cli/command/package/remove.py +++ b/src/cli/cpl/cli/command/package/remove.py @@ -3,7 +3,7 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -11,8 +11,8 @@ from cpl.core.console import Console @click.argument("reference", type=click.STRING, required=True) @click.argument("target", type=click.STRING, required=True) def remove(reference: str, target: str): - reference_project = get_project_by_name_or_path(reference) - target_project = get_project_by_name_or_path(target) + reference_project = Structure.get_project_by_name_or_path(reference) + target_project = Structure.get_project_by_name_or_path(target) if reference_project.name == target_project.name: raise ValueError("Cannot add a project as a dependency to itself!") diff --git a/src/cli/cpl/cli/command/package/uninstall.py b/src/cli/cpl/cli/command/package/uninstall.py index f856b41a..9718c608 100644 --- a/src/cli/cpl/cli/command/package/uninstall.py +++ b/src/cli/cpl/cli/command/package/uninstall.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -17,7 +17,7 @@ def uninstall(package: str, project: str, dev: bool, verbose: bool): if package is None: package = Console.read("Package name to uninstall: ").strip() - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") deps = project.dependencies if not dev else project.dev_dependencies diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index ea602dc1..e37ed395 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -4,7 +4,7 @@ import click from cpl.cli.cli import cli from cpl.cli.utils.pip import Pip -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -14,7 +14,7 @@ from cpl.core.console import Console @click.option("--dev", is_flag=True, help="Include dev dependencies") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def update(package: str, project: str, dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project = Structure.get_project_by_name_or_path(project or "./") deps: dict = project.dependencies if dev: diff --git a/src/cli/cpl/cli/command/project/build.py b/src/cli/cpl/cli/command/project/build.py index 6a37c2fc..931e7266 100644 --- a/src/cli/cpl/cli/command/project/build.py +++ b/src/cli/cpl/cli/command/project/build.py @@ -5,8 +5,6 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.model.project import Project -from cpl.cli.utils.structure import get_project_by_name_or_path from cpl.cli.utils.venv import ensure_venv, get_venv_python from cpl.core.configuration import Configuration from cpl.core.console import Console @@ -18,7 +16,9 @@ from cpl.core.console import Console @click.option("--skip-py-build", "-spb", is_flag=True, help="Skip toml generation and python build") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: bool = None): - project = get_project_by_name_or_path(project or "./") + from cpl.cli.utils.structure import Structure + + project = Structure.get_project_by_name_or_path(project or "./") venv = ensure_venv().absolute() dist_path = dist or Path(project.path).parent / "dist" @@ -38,9 +38,7 @@ def build(project: str, dist: str = None, skip_py_build: bool = None, verbose: b Console.write_line("\nDone!") return - from cpl.cli.utils.structure import create_pyproject_toml - - create_pyproject_toml(project, dist_path / project.name) + Structure.create_pyproject_toml(project, dist_path / project.name) python = str(get_venv_python(venv)) result = Console.spinner( diff --git a/src/cli/cpl/cli/command/project/run.py b/src/cli/cpl/cli/command/project/run.py index 8b724a81..232cc340 100644 --- a/src/cli/cpl/cli/command/project/run.py +++ b/src/cli/cpl/cli/command/project/run.py @@ -5,19 +5,23 @@ from pathlib import Path import click from cpl.cli.cli import cli -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.cli.utils.venv import get_venv_python, ensure_venv from cpl.core.configuration import Configuration from cpl.core.console import Console @cli.command("run", aliases=["r"]) +@click.argument("project", type=str, required=False, default=None) @click.argument("args", nargs=-1) -@click.option("--project", "-p", type=str) @click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def run(project: str, args: list[str], dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project_path = Path("./") + if project is not None: + project_path = (Path("./") / project).resolve().absolute() + + project = Structure.get_project_by_name_or_path(str(project_path)) if project.main is None: Console.error(f"Project {project.name} has no executable") return diff --git a/src/cli/cpl/cli/command/project/start.py b/src/cli/cpl/cli/command/project/start.py index a380dcba..28bc4797 100644 --- a/src/cli/cpl/cli/command/project/start.py +++ b/src/cli/cpl/cli/command/project/start.py @@ -1,18 +1,24 @@ +from pathlib import Path + import click from cpl.cli.cli import cli from cpl.cli.utils.live_server.live_server import LiveServer -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @cli.command("start", aliases=["s"]) +@click.argument("project", type=str, required=False, default=None) @click.argument("args", nargs=-1) -@click.option("--project", "-p", type=str) @click.option("--dev", "-d", is_flag=True, help="Use sources instead of build output") @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def start(project: str, args: list[str], dev: bool, verbose: bool): - project = get_project_by_name_or_path(project or "./") + project_path = Path("./") + if project is not None: + project_path = (Path("./") / project).resolve().absolute() + + project = Structure.get_project_by_name_or_path(str(project_path)) if project.main is None: Console.error(f"Project {project.name} has no executable") return diff --git a/src/cli/cpl/cli/command/structure/generate.py b/src/cli/cpl/cli/command/structure/generate.py index 150c75f1..67e74096 100644 --- a/src/cli/cpl/cli/command/structure/generate.py +++ b/src/cli/cpl/cli/command/structure/generate.py @@ -7,7 +7,7 @@ from cpl.cli import cli as clim from cpl.cli.cli import cli from cpl.cli.model.project import Project from cpl.cli.model.workspace import Workspace -from cpl.cli.utils.structure import get_project_by_name_or_path +from cpl.cli.utils.structure import Structure from cpl.cli.utils.template_collector import TemplateCollector from cpl.cli.utils.template_renderer import TemplateRenderer from cpl.core.configuration import Configuration @@ -29,7 +29,7 @@ def generate(schematic: str, name: str, verbose: bool) -> None: project = None try: - project = get_project_by_name_or_path("./") + project = Structure.get_project_by_name_or_path("./") if verbose: Console.write_line("project found, collecting templates...") diff --git a/src/cli/cpl/cli/command/structure/init.py b/src/cli/cpl/cli/command/structure/init.py index ff4bc7cd..a8323fa4 100644 --- a/src/cli/cpl/cli/command/structure/init.py +++ b/src/cli/cpl/cli/command/structure/init.py @@ -3,9 +3,8 @@ from pathlib import Path import click from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT -from cpl.cli.model.project import Project -from cpl.cli.model.workspace import Workspace from cpl.cli.utils.prompt import SmartChoice +from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -25,62 +24,10 @@ def init(target: str, name: str): target = [pt for pt in PROJECT_TYPES if pt.startswith(target)][0] if target in ["workspace", "ws"]: - _init_workspace(name or click.prompt("Workspace name", default="my-workspace")) + Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace")) elif target in PROJECT_TYPES: - _init_project(name or click.prompt("Project name", default=f"my-{target}"), target) + workspace = Structure.find_workspace_in_path(Path(name).parent) + Structure.init_project("./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace) else: Console.error(f"Unknown target '{target}'") raise SystemExit(1) - - -def _init_workspace(name: str): - path = Path("cpl.workspace.json") - - if path.exists(): - Console.write_line("workspace.json already exists.") - return - - data = { - "name": name, - "projects": [], - "defaultProject": None, - "scripts": {}, - } - workspace = Workspace.new("./", name) - workspace.save() - - Console.write_line(f"Created workspace '{name}'") - - -def _init_project(name: str, project_type: str): - project = Project.new("./", name, project_type) - - path = Path("cpl.project.json") - if path.exists(): - Console.write_line("cpl.project.json already exists.") - return - - if not Path("src").exists(): - project.directory = click.prompt( - "Project directory", type=click.Path(exists=True, file_okay=False), default="src" - ) - - if project_type in ["console", "web", "service"]: - project.main = click.prompt( - "Main executable", type=click.Path(exists=True, dir_okay=False), default="src/main.py" - ) - - project.save() - - workspace_file = Path("../cpl.workspace.json") - if workspace_file.exists(): - workspace = Workspace.from_file(workspace_file) - - rel_path = str(Path.cwd().relative_to(workspace_file.parent)) - if rel_path not in workspace.projects: - workspace.projects.append(rel_path) - workspace.save() - - Console.write_line(f"Registered '{name}' in workspace.json") - - Console.write_line(f"Created {project_type} project '{name}'") diff --git a/src/cli/cpl/cli/command/structure/new.py b/src/cli/cpl/cli/command/structure/new.py new file mode 100644 index 00000000..be3a74ae --- /dev/null +++ b/src/cli/cpl/cli/command/structure/new.py @@ -0,0 +1,64 @@ +import os +from pathlib import Path + +import click +from cpl.cli import cli as clim +from cpl.cli.cli import cli +from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.structure import Structure +from cpl.core.console import Console + + +@cli.command("new", aliases=["n"]) +@click.argument("type", type=click.STRING, required=True) +@click.argument("name", type=click.STRING, required=True) +@click.option("--name", "in_name", type=click.STRING, help="Name of the workspace or project to create.") +@click.option( + "--project", + "-p", + nargs=2, + metavar=" ", + help="Optional: when creating a workspace, also create a project with the given name and type.", +) +@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") +def new(type: str, name: str, in_name: str | None, project: list[str] | None, verbose: bool) -> None: + workspace_created = False + path = Path(name).parent + project_name = in_name or Path(name).stem + + if type in ["workspace", "ws"]: + Structure.init_workspace(name, project_name) + workspace = Workspace.from_file(Path(name) / "cpl.workspace.json") + workspace_created = True + + if len(project) == 2: + type = project[0] + if type not in PROJECT_TYPES + PROJECT_TYPES_SHORT: + raise ValueError(f"Unknown project type '{type}'") + + path = Path(workspace.path).parent / Path(project[1]).parent + project_name = Path(project[1]).stem + + workspace = Structure.find_workspace_in_path(path) + if workspace is None: + Console.error("No workspace found. Please run 'cpl init workspace' first.") + raise SystemExit(1) + + if project_name in workspace.project_names: + Console.error(f"Project '{project_name}' already exists in the workspace") + raise SystemExit(1) + + if verbose: + Console.write_line(f"Creating project '{path/project_name}'...") + + project_types = os.listdir(Path(clim.__file__).parent / ".cpl" / "new") + project_types.extend(set(x[0] for x in PROJECT_TYPES)) + + if type not in project_types: + raise ValueError(f"Unsupported project type '{type}'") + + Structure.create_project(path, type, project_name, workspace, verbose) + if workspace_created: + workspace.default_project = project_name + workspace.save() diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index e85d6a5d..4afb0ca8 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -12,6 +12,7 @@ from cpl.cli.command.project.run import run from cpl.cli.command.project.start import start from cpl.cli.command.structure.generate import generate from cpl.cli.command.structure.init import init +from cpl.cli.command.structure.new import new from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command @@ -36,7 +37,7 @@ def _load_scripts(): if ws is None: continue - Configuration.set("workspace", Workspace.from_file(p)) + Configuration.set("workspace", ws) return ws.scripts return {} @@ -54,7 +55,7 @@ def configure(): # structure cli.add_command(init) - # cli.add_command(new) + cli.add_command(new) cli.add_command(generate) # packaging diff --git a/src/cli/cpl/cli/model/build.py b/src/cli/cpl/cli/model/build.py index bfd87003..b9939e29 100644 --- a/src/cli/cpl/cli/model/build.py +++ b/src/cli/cpl/cli/model/build.py @@ -3,6 +3,10 @@ from cpl.cli.model.cpl_sub_structure_model import CPLSubStructureModel class Build(CPLSubStructureModel): + @staticmethod + def new(include: list[str], exclude: list[str]) -> "Build": + return Build(include, exclude) + def __init__(self, include: list[str], exclude: list[str]): CPLSubStructureModel.__init__(self) diff --git a/src/cli/cpl/cli/model/cpl_structure_model.py b/src/cli/cpl/cli/model/cpl_structure_model.py index ba32133a..3d9434c1 100644 --- a/src/cli/cpl/cli/model/cpl_structure_model.py +++ b/src/cli/cpl/cli/model/cpl_structure_model.py @@ -1,5 +1,6 @@ import inspect import json +import os from inspect import isclass from pathlib import Path from typing import Any, Dict, List, Optional, Type, TypeVar @@ -81,6 +82,9 @@ class CPLStructureModel: if not self._path: raise ValueError("Cannot save model without a path.") + if not Path(self._path).exists(): + os.makedirs(Path(self._path).parent, exist_ok=True) + with open(self._path, "w", encoding="utf-8") as f: json.dump(self.to_json(), f, indent=2) diff --git a/src/cli/cpl/cli/model/project.py b/src/cli/cpl/cli/model/project.py index 326eee68..d89fda8e 100644 --- a/src/cli/cpl/cli/model/project.py +++ b/src/cli/cpl/cli/model/project.py @@ -31,7 +31,8 @@ class Project(CPLStructureModel): {}, [], None, - "src", + "./", + Build.new([], []), ) def __init__( @@ -192,6 +193,7 @@ class Project(CPLStructureModel): def _collect_files(self, rel_dir: Path) -> List[Path]: files: List[Path] = [] exclude_patterns = [p.strip() for p in self._build.exclude or []] + exclude_patterns.append("cpl.*.json") for root, dirnames, filenames in os.walk(rel_dir, topdown=True): root_path = Path(root) diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index c8952527..e9740916 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -1,27 +1,75 @@ +import os +import shutil import textwrap from pathlib import Path +import click + +import cpl.cli as cli from cpl.cli.model.project import Project -from cpl.core.configuration import Configuration +from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.template_renderer import TemplateRenderer +from cpl.core.console import Console -def get_project_by_name_or_path(project: str) -> Project: - if project is None: - raise ValueError("Project name or path must be provided.") +class Structure: + @staticmethod + def find_workspace_in_path(path: Path) -> Workspace | None: + current_path = path.resolve() - workspace = Configuration.get("workspace") + Console.write_line(*([current_path] + list(current_path.parents))) + for parent in [current_path] + list(current_path.parents): + workspace_file = parent / "cpl.workspace.json" + Console.write_line(workspace_file) + if workspace_file.exists() and workspace_file.is_file(): + ws = Workspace.from_file(workspace_file) + Console.error(ws.name) + return ws - path = Path(project) - if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): - return Project.from_file(path / "cpl.project.json") + return None - if path.exists() and path.is_file(): - if not path.name.endswith("cpl.project.json"): - raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + @staticmethod + def create_pyproject_toml(project: Project, path: Path): + pyproject_path = path / "pyproject.toml" + if pyproject_path.exists(): + return - return Project.from_file(path) + content = textwrap.dedent( + f""" + [build-system] + requires = ["setuptools>=70.1.0", "wheel", "build"] + build-backend = "setuptools.build_meta" + [project] + name = "{project.name}" + version = "{project.version or '0.1.0'}" + description = "{project.description or ''}" + authors = [{{name="{project.author or ''}"}}] + license = "{project.license or ''}" + dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] + """ + ).lstrip() + + pyproject_path.write_text(content) + + @staticmethod + def get_project_by_name_or_path(project: str) -> Project: + if project is None: + raise ValueError("Project name or path must be provided.") + + path = Path(project) + if path.exists() and path.is_dir() and (path / "cpl.project.json").exists(): + return Project.from_file(path / "cpl.project.json") + + if path.exists() and path.is_file(): + if not path.name.endswith("cpl.project.json"): + raise ValueError(f"File '{path}' is not a valid cpl.project.json file.") + + return Project.from_file(path) + + workspace = Structure.find_workspace_in_path(path.parent) + if workspace is None: + raise RuntimeError("No workspace found. Please run 'cpl init workspace' first.") - if workspace is not None: for p in workspace.actual_projects: if p.name == project: return Project.from_file(Path(p.path)) @@ -34,27 +82,90 @@ def get_project_by_name_or_path(project: str) -> Project: if p.name == workspace.default_project: return Project.from_file(Path(p.path)) - raise ValueError(f"Project '{project}' not found.") + raise ValueError(f"Project '{project}' not found.") + @staticmethod + def init_workspace(path: Path | str, name: str): + path = Path(path) / Path("cpl.workspace.json") -def create_pyproject_toml(project: Project, path: Path): - pyproject_path = path / "pyproject.toml" - if pyproject_path.exists(): - return + if path.exists(): + raise ValueError("workspace.json already exists.") - content = textwrap.dedent( - f""" - [build-system] - requires = ["setuptools>=70.1.0", "wheel", "build"] - build-backend = "setuptools.build_meta" - [project] - name = "{project.name}" - version = "{project.version or '0.1.0'}" - description = "{project.description or ''}" - authors = [{{name="{project.author or ''}"}}] - license = "{project.license or ''}" - dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}] - """ - ).lstrip() + workspace = Workspace.new(str(path), name) + workspace.save() - pyproject_path.write_text(content) + Console.write_line(f"Created workspace '{name}'") + + @staticmethod + def init_project(rel_path: str, name: str, project_type: str, workspace: Workspace | None, verbose=False): + if not Path(rel_path).exists(): + rel_path = click.prompt("Project directory", type=click.Path(exists=True, file_okay=False), default="src") + + path = Path(rel_path) / Path("cpl.project.json") + if path.exists(): + Console.write_line("cpl.project.json already exists.") + return + + project = Project.new(str(path), name, project_type) + + executable_path = Path(project.path).parent / "main.py" + executable_file = ( + str(executable_path.relative_to(Path(project.path).parent)) if executable_path.exists() else None + ) + + if project_type in ["console", "web", "service"]: + project.main = executable_file or click.prompt( + "Main executable", type=click.Path(exists=True, dir_okay=False), default="src/main.py" + ) + + project.save() + + if workspace is not None: + rel_path = str(path.resolve().absolute().relative_to(Path(workspace.path).parent)) + if rel_path not in workspace.projects: + workspace.projects.append(rel_path) + workspace.save() + + if verbose: + Console.write_line(f"Registered '{name}' in workspace.json") + + Console.write_line(f"Created {project_type} project '{name}'") + + @staticmethod + def create_project(path: Path, project_type: str, name: str, workspace: Workspace | None, verbose=False): + if not str(path).endswith(name): + path = path / name + + if not path.exists(): + os.makedirs(path, exist_ok=True) + + src_dir = Path(cli.__file__).parent / ".cpl" / "new" / project_type + + Console.write_line() + for root, dirs, files in os.walk(src_dir): + rel_root = Path(root).relative_to(src_dir) + target_root = path / rel_root + target_root.mkdir(parents=True, exist_ok=True) + + for filename in files: + src_file = Path(root) / filename + tgt_file = target_root / filename + + Console.set_foreground_color("green") + Console.write_line(f"Create {str(tgt_file).replace(".schematic", "")}") + Console.set_foreground_color() + + if filename.endswith(".schematic"): + with open(src_file, "r") as src: + with open(str(tgt_file).replace(".schematic", ""), "w") as tgt: + tgt.write( + TemplateRenderer.render_template( + str(src_file).split(".")[0], src.read(), name, str(path) + ) + ) + continue + + shutil.copy(src_file, tgt_file) + + Console.write_line() + Structure.init_project(str(path), name, project_type, workspace) diff --git a/src/cli/cpl/cli/utils/template_renderer.py b/src/cli/cpl/cli/utils/template_renderer.py index a6111f20..a3a5afc2 100644 --- a/src/cli/cpl/cli/utils/template_renderer.py +++ b/src/cli/cpl/cli/utils/template_renderer.py @@ -20,4 +20,4 @@ class TemplateRenderer: for key, value in context.items(): template_str = template_str.replace(f"<{key}>", value) - return template_str \ No newline at end of file + return template_str diff --git a/src/cli/run b/src/cli/run index 580c69e9..cc22a471 100755 --- a/src/cli/run +++ b/src/cli/run @@ -4,7 +4,12 @@ set -e cd ../ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -export PYTHONPATH="$ROOT_DIR/core:$ROOT_DIR/cli:$PYTHONPATH" +py_list="$ROOT_DIR" +for d in "$ROOT_DIR"/*; do + [ -d "$d" ] || continue + py_list="$py_list:$d" +done +export PYTHONPATH="${py_list}${PYTHONPATH:+:$PYTHONPATH}" old_dir="$(pwd)" cd ../ diff --git a/src/core/cpl/core/__init__.py b/src/core/cpl/core/__init__.py index a6d8caf7..5becc17c 100644 --- a/src/core/cpl/core/__init__.py +++ b/src/core/cpl/core/__init__.py @@ -1,2 +1 @@ __version__ = "1.0.0" -__version__ = "1.0.0" diff --git a/src/core/cpl/core/configuration/configuration.py b/src/core/cpl/core/configuration/configuration.py index 5963d6db..e4ca5053 100644 --- a/src/core/cpl/core/configuration/configuration.py +++ b/src/core/cpl/core/configuration/configuration.py @@ -133,5 +133,4 @@ class Configuration: if isclass(key) and issubclass(key, ConfigurationModelABC) and result == default: result = key() cls.set(key, result) - return result diff --git a/src/core/cpl/core/console/console.py b/src/core/cpl/core/console/console.py index d45e3158..4a4d8620 100644 --- a/src/core/cpl/core/console/console.py +++ b/src/core/cpl/core/console/console.py @@ -72,13 +72,17 @@ class Console: cls._background_color = color @classmethod - def set_foreground_color(cls, color: Union[ForegroundColorEnum, str]): + def set_foreground_color(cls, color: Union[ForegroundColorEnum, str] = None): r"""Sets the foreground color Parameter: color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`] Foreground color of the console """ + if color is None: + cls._foreground_color = ForegroundColorEnum.default + return + if type(color) is str: cls._foreground_color = ForegroundColorEnum[color] else: diff --git a/src/core/cpl/core/property.py b/src/core/cpl/core/property.py new file mode 100644 index 00000000..a5b78634 --- /dev/null +++ b/src/core/cpl/core/property.py @@ -0,0 +1,3 @@ +class classproperty(property): + def __get__(self, obj, cls): + return self.fget(cls) diff --git a/src/graphql/cpl/graphql/application/__init__.py b/src/graphql/cpl/graphql/application/__init__.py index 96b2346c..cd74b311 100644 --- a/src/graphql/cpl/graphql/application/__init__.py +++ b/src/graphql/cpl/graphql/application/__init__.py @@ -1 +1 @@ -from .graphql_app import WebApp +from .graphql_app import GraphQLApp From 6d3e435da601082af090d3948a8f624f6e85a1c0 Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 19 Oct 2025 14:39:05 +0200 Subject: [PATCH 21/22] cpl new use venv & install deps on creation --- example/general/src/hosted_service.py | 4 +- .../cli/.cpl/generate/cron_job.py.schematic | 9 ++++ .../.cpl/generate/hosted_service.py.schematic | 13 +++++ src/cli/cpl/cli/command/package/install.py | 7 ++- src/cli/cpl/cli/command/package/update.py | 4 +- src/cli/cpl/cli/command/structure/init.py | 16 ++++-- src/cli/cpl/cli/command/structure/new.py | 28 ++++++---- src/cli/cpl/cli/const.py | 4 +- src/cli/cpl/cli/main.py | 8 ++- src/cli/cpl/cli/model/workspace.py | 2 +- src/cli/cpl/cli/utils/pip.py | 8 +-- src/cli/cpl/cli/utils/prompt.py | 8 +++ src/cli/cpl/cli/utils/structure.py | 51 +++++++++++++++---- src/cli/cpl/cli/utils/venv.py | 6 ++- src/cli/pyproject.toml | 3 ++ .../cpl/core/service}/__init__.py | 0 .../cpl/core/service}/cronjob.py | 0 .../cpl/core/service}/hosted_service.py | 0 .../cpl/core/service}/startup_task.py | 0 19 files changed, 130 insertions(+), 41 deletions(-) create mode 100644 src/cli/cpl/cli/.cpl/generate/cron_job.py.schematic create mode 100644 src/cli/cpl/cli/.cpl/generate/hosted_service.py.schematic rename src/{dependency/cpl/dependency/hosted => core/cpl/core/service}/__init__.py (100%) rename src/{dependency/cpl/dependency/hosted => core/cpl/core/service}/cronjob.py (100%) rename src/{dependency/cpl/dependency/hosted => core/cpl/core/service}/hosted_service.py (100%) rename src/{dependency/cpl/dependency/hosted => core/cpl/core/service}/startup_task.py (100%) diff --git a/example/general/src/hosted_service.py b/example/general/src/hosted_service.py index f2fbf762..15d91753 100644 --- a/example/general/src/hosted_service.py +++ b/example/general/src/hosted_service.py @@ -3,8 +3,8 @@ from datetime import datetime from cpl.core.console import Console from cpl.core.time.cron import Cron -from cpl.dependency.hosted.cronjob import CronjobABC -from cpl.dependency.hosted.hosted_service import HostedService +from cpl.core.service.cronjob import CronjobABC +from cpl.core.service.hosted_service import HostedService class Hosted(HostedService): diff --git a/src/cli/cpl/cli/.cpl/generate/cron_job.py.schematic b/src/cli/cpl/cli/.cpl/generate/cron_job.py.schematic new file mode 100644 index 00000000..1919b5e1 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/cron_job.py.schematic @@ -0,0 +1,9 @@ +from cpl.core.console import Console +from cpl.core.service import CronjobABC + +class CronJob(CronjobABC): + def __init__(self): + CronjobABC.__init__(self, Cron("*/1 * * * *")) + + async def loop(self): + Console.write_line(f"[{datetime.now()}] Hello, World!") diff --git a/src/cli/cpl/cli/.cpl/generate/hosted_service.py.schematic b/src/cli/cpl/cli/.cpl/generate/hosted_service.py.schematic new file mode 100644 index 00000000..4bd8f6f5 --- /dev/null +++ b/src/cli/cpl/cli/.cpl/generate/hosted_service.py.schematic @@ -0,0 +1,13 @@ +from cpl.core.console import Console +from cpl.core.service import HostedService + + +class (HostedService): + def __init__(self): + HostedService.__init__(self) + + async def start(self): + Console.write_line("Hello, World!") + + async def stop(self): + Console.write_line("Goodbye, World!") diff --git a/src/cli/cpl/cli/command/package/install.py b/src/cli/cpl/cli/command/package/install.py index b06d68f8..c1c2a195 100644 --- a/src/cli/cpl/cli/command/package/install.py +++ b/src/cli/cpl/cli/command/package/install.py @@ -1,8 +1,11 @@ +import os import subprocess +from pathlib import Path import click from cpl.cli.cli import cli +from cpl.cli.const import PIP_URL from cpl.cli.utils.pip import Pip from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -20,10 +23,10 @@ def install(package: str, project: str, dev: bool, verbose: bool): Console.write_line(f"Installing {package} to '{project.name}':") try: Pip.command( - "install --extra-index-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/", + f"install --extra-index-url {PIP_URL}", package, verbose=verbose, - path=project.path, + path=Path(project.path).parent, ) except subprocess.CalledProcessError as e: Console.error(f"Failed to install {package}: exit code {e.returncode}") diff --git a/src/cli/cpl/cli/command/package/update.py b/src/cli/cpl/cli/command/package/update.py index e37ed395..6268bdba 100644 --- a/src/cli/cpl/cli/command/package/update.py +++ b/src/cli/cpl/cli/command/package/update.py @@ -3,6 +3,7 @@ import subprocess import click from cpl.cli.cli import cli +from cpl.cli.const import PIP_URL from cpl.cli.utils.pip import Pip from cpl.cli.utils.structure import Structure from cpl.core.console import Console @@ -30,8 +31,7 @@ def update(package: str, project: str, dev: bool, verbose: bool): Console.write_line(f"Updating {package} to '{project.name}':") try: Pip.command( - "install --upgrade --extra-index-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/", - f"{Pip.normalize_dep(package, old_spec)}", + f"install --upgrade --extra-index-url {PIP_URL}" f"{Pip.normalize_dep(package, old_spec)}", verbose=verbose, path=project.path, ) diff --git a/src/cli/cpl/cli/command/structure/init.py b/src/cli/cpl/cli/command/structure/init.py index a8323fa4..ee7c4d7f 100644 --- a/src/cli/cpl/cli/command/structure/init.py +++ b/src/cli/cpl/cli/command/structure/init.py @@ -3,8 +3,9 @@ from pathlib import Path import click from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT -from cpl.cli.utils.prompt import SmartChoice +from cpl.cli.utils.prompt import ProjectType from cpl.cli.utils.structure import Structure +from cpl.cli.utils.venv import ensure_venv from cpl.core.console import Console @@ -12,11 +13,14 @@ from cpl.core.console import Console @click.argument("target", required=False) @click.argument("name", required=False) def init(target: str, name: str): + workspace = None + project = None + if target is None: Console.write_line("CPL Init Wizard") target = click.prompt( "What do you want to initialize?", - type=SmartChoice(["workspace"] + PROJECT_TYPES, {"workspace": "ws"}), + type=ProjectType, show_choices=True, ) @@ -24,10 +28,14 @@ def init(target: str, name: str): target = [pt for pt in PROJECT_TYPES if pt.startswith(target)][0] if target in ["workspace", "ws"]: - Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace")) + workspace = Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace")) elif target in PROJECT_TYPES: workspace = Structure.find_workspace_in_path(Path(name).parent) - Structure.init_project("./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace) + project = Structure.init_project( + "./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace + ) else: Console.error(f"Unknown target '{target}'") raise SystemExit(1) + + ensure_venv(Path((workspace or project).path).parent) diff --git a/src/cli/cpl/cli/command/structure/new.py b/src/cli/cpl/cli/command/structure/new.py index be3a74ae..7476d8c0 100644 --- a/src/cli/cpl/cli/command/structure/new.py +++ b/src/cli/cpl/cli/command/structure/new.py @@ -2,16 +2,19 @@ import os from pathlib import Path import click + from cpl.cli import cli as clim from cpl.cli.cli import cli from cpl.cli.const import PROJECT_TYPES, PROJECT_TYPES_SHORT from cpl.cli.model.workspace import Workspace +from cpl.cli.utils.prompt import ProjectType from cpl.cli.utils.structure import Structure +from cpl.cli.utils.venv import ensure_venv from cpl.core.console import Console @cli.command("new", aliases=["n"]) -@click.argument("type", type=click.STRING, required=True) +@click.argument("type", type=ProjectType, required=True) @click.argument("name", type=click.STRING, required=True) @click.option("--name", "in_name", type=click.STRING, help="Name of the workspace or project to create.") @click.option( @@ -23,24 +26,25 @@ from cpl.core.console import Console ) @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output") def new(type: str, name: str, in_name: str | None, project: list[str] | None, verbose: bool) -> None: - workspace_created = False path = Path(name).parent project_name = in_name or Path(name).stem if type in ["workspace", "ws"]: Structure.init_workspace(name, project_name) workspace = Workspace.from_file(Path(name) / "cpl.workspace.json") - workspace_created = True + ensure_venv(Path(name)) - if len(project) == 2: - type = project[0] - if type not in PROJECT_TYPES + PROJECT_TYPES_SHORT: - raise ValueError(f"Unknown project type '{type}'") + if project is None or len(project) != 2: + return - path = Path(workspace.path).parent / Path(project[1]).parent - project_name = Path(project[1]).stem + type = project[0] + if type not in PROJECT_TYPES + PROJECT_TYPES_SHORT: + raise ValueError(f"Unknown project type '{type}'") - workspace = Structure.find_workspace_in_path(path) + path = Path(workspace.path).parent / Path(project[1]).parent + project_name = Path(project[1]).stem + + workspace = Structure.find_workspace_in_path(path, with_parents=False) if workspace is None: Console.error("No workspace found. Please run 'cpl init workspace' first.") raise SystemExit(1) @@ -59,6 +63,8 @@ def new(type: str, name: str, in_name: str | None, project: list[str] | None, ve raise ValueError(f"Unsupported project type '{type}'") Structure.create_project(path, type, project_name, workspace, verbose) - if workspace_created: + + ensure_venv(Path((workspace or project).path).parent) + if workspace.default_project is None: workspace.default_project = project_name workspace.save() diff --git a/src/cli/cpl/cli/const.py b/src/cli/cpl/cli/const.py index a5810f03..acb63ff7 100644 --- a/src/cli/cpl/cli/const.py +++ b/src/cli/cpl/cli/const.py @@ -1,2 +1,4 @@ -PROJECT_TYPES = ["console", "web", "library", "service"] +PROJECT_TYPES = ["console", "web", "graphql", "library", "service"] PROJECT_TYPES_SHORT = [x[0] for x in PROJECT_TYPES] + +PIP_URL = "https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/" diff --git a/src/cli/cpl/cli/main.py b/src/cli/cpl/cli/main.py index 4afb0ca8..baae0e58 100644 --- a/src/cli/cpl/cli/main.py +++ b/src/cli/cpl/cli/main.py @@ -1,4 +1,3 @@ -import sys from pathlib import Path from cpl.cli.cli import cli @@ -17,6 +16,7 @@ from cpl.cli.command.version import version from cpl.cli.model.workspace import Workspace from cpl.cli.utils.custom_command import script_command from cpl.core.configuration import Configuration +from cpl.core.console import Console def _load_workspace(path: str) -> Workspace | None: @@ -74,12 +74,16 @@ def configure(): def main(): prepare() configure() - cli() + try: + cli() + finally: + Console.write_line() if __name__ == "__main__": main() + # (( # ( `) # ; / , diff --git a/src/cli/cpl/cli/model/workspace.py b/src/cli/cpl/cli/model/workspace.py index b79c6a2e..a80c64d7 100644 --- a/src/cli/cpl/cli/model/workspace.py +++ b/src/cli/cpl/cli/model/workspace.py @@ -52,7 +52,7 @@ class Workspace(CPLStructureModel): @property def project_names(self) -> List[str]: - return [Project.from_file(p).name for p in self._projects if "name" in p] + return [Project.from_file(p).name for p in self._projects] @property def default_project(self) -> Optional[str]: diff --git a/src/cli/cpl/cli/utils/pip.py b/src/cli/cpl/cli/utils/pip.py index 8d9c0798..3b187134 100644 --- a/src/cli/cpl/cli/utils/pip.py +++ b/src/cli/cpl/cli/utils/pip.py @@ -97,13 +97,12 @@ class Pip: return spec.replace(package_name, "").strip() or None @staticmethod - def command(command: str, *args, verbose: bool = False, path: str = None): - if path is not None and Path(path).is_file(): + def command(command: str, *args, verbose: bool = False, path: Path = None): + if path is not None and path.is_file(): path = os.path.dirname(path) - venv_path = ensure_venv(Path(path or "./")) + venv_path = ensure_venv(Path(os.getcwd()) / Path(path or "./")) pip = get_venv_pip(venv_path) - if verbose: Console.write_line() Console.write_line(f"Running: {pip} {command} {''.join(args)}") @@ -111,6 +110,7 @@ class Pip: subprocess.run( [*pip.split(), *command.split(), *args], check=True, + cwd=path, stdin=subprocess.DEVNULL if not verbose else None, stdout=subprocess.DEVNULL if not verbose else None, stderr=subprocess.DEVNULL if not verbose else None, diff --git a/src/cli/cpl/cli/utils/prompt.py b/src/cli/cpl/cli/utils/prompt.py index e90e7708..d491f613 100644 --- a/src/cli/cpl/cli/utils/prompt.py +++ b/src/cli/cpl/cli/utils/prompt.py @@ -1,5 +1,7 @@ import click +from cpl.cli.const import PROJECT_TYPES + class SmartChoice(click.Choice): @@ -18,3 +20,9 @@ class SmartChoice(click.Choice): if val_lower in self._aliases.values(): value = [k for k, v in self._aliases.items() if v == val_lower][0] return super().convert(value, param, ctx) + + def get_metavar(self, param, ctx): + return "|".join([f"({a}){option}" for option, a in self._aliases.items()]) + + +ProjectType = SmartChoice(["workspace"] + PROJECT_TYPES, {"workspace": "ws"}) diff --git a/src/cli/cpl/cli/utils/structure.py b/src/cli/cpl/cli/utils/structure.py index e9740916..1a2116da 100644 --- a/src/cli/cpl/cli/utils/structure.py +++ b/src/cli/cpl/cli/utils/structure.py @@ -5,7 +5,7 @@ from pathlib import Path import click -import cpl.cli as cli +from cpl import cli from cpl.cli.model.project import Project from cpl.cli.model.workspace import Workspace from cpl.cli.utils.template_renderer import TemplateRenderer @@ -13,17 +13,35 @@ from cpl.core.console import Console class Structure: - @staticmethod - def find_workspace_in_path(path: Path) -> Workspace | None: - current_path = path.resolve() + _dependency_map = { + "console": [ + "cpl-core", + ], + "web": [ + "cpl-api", + ], + "graphql": [ + "cpl-graphql", + ], + "library": [ + "cpl-core", + ], + "service": [ + "cpl-core", + ], + } - Console.write_line(*([current_path] + list(current_path.parents))) - for parent in [current_path] + list(current_path.parents): + @staticmethod + def find_workspace_in_path(path: Path, with_parents=False) -> Workspace | None: + current_path = path.resolve() + paths = [current_path] + if with_parents: + paths.extend(current_path.parents) + + for parent in paths: workspace_file = parent / "cpl.workspace.json" - Console.write_line(workspace_file) if workspace_file.exists() and workspace_file.is_file(): ws = Workspace.from_file(workspace_file) - Console.error(ws.name) return ws return None @@ -95,6 +113,7 @@ class Structure: workspace.save() Console.write_line(f"Created workspace '{name}'") + return workspace @staticmethod def init_project(rel_path: str, name: str, project_type: str, workspace: Workspace | None, verbose=False): @@ -103,8 +122,8 @@ class Structure: path = Path(rel_path) / Path("cpl.project.json") if path.exists(): - Console.write_line("cpl.project.json already exists.") - return + Console.error("cpl.project.json already exists.") + raise SystemExit(1) project = Project.new(str(path), name, project_type) @@ -120,6 +139,17 @@ class Structure: project.save() + from cpl.cli.command.package.install import install + + old_cwd = os.getcwd() + os.chdir(Path(workspace.path).parent) + install.callback(f"cpl-cli>={cli.__version__}", project.name, dev=True, verbose=verbose) + if project_type in Structure._dependency_map: + for package in Structure._dependency_map[project_type]: + install.callback(package, project.name, dev=False, verbose=verbose) + + os.chdir(old_cwd) + if workspace is not None: rel_path = str(path.resolve().absolute().relative_to(Path(workspace.path).parent)) if rel_path not in workspace.projects: @@ -130,6 +160,7 @@ class Structure: Console.write_line(f"Registered '{name}' in workspace.json") Console.write_line(f"Created {project_type} project '{name}'") + return project @staticmethod def create_project(path: Path, project_type: str, name: str, workspace: Workspace | None, verbose=False): diff --git a/src/cli/cpl/cli/utils/venv.py b/src/cli/cpl/cli/utils/venv.py index 580f0639..497e53bf 100644 --- a/src/cli/cpl/cli/utils/venv.py +++ b/src/cli/cpl/cli/utils/venv.py @@ -9,7 +9,9 @@ from cpl.core.console import Console def ensure_venv(start_path: Path | None = None) -> Path: start_path = start_path or Path.cwd() - workspace = Configuration.get("workspace") + from cpl.cli.utils.structure import Structure + + workspace = Structure.find_workspace_in_path(start_path) if workspace is not None: workspace = Path(os.path.dirname(workspace.path)) @@ -28,7 +30,7 @@ def ensure_venv(start_path: Path | None = None) -> Path: else: venv_path = start_path / ".venv" - Console.write_line(f"Creating virtual environment at {venv_path.absolute()}...") + Console.write_line(f"Creating virtual environment at {venv_path.resolve().absolute()}...") venv.EnvBuilder(with_pip=True).create(venv_path) return venv_path diff --git a/src/cli/pyproject.toml b/src/cli/pyproject.toml index afbbe062..92523473 100644 --- a/src/cli/pyproject.toml +++ b/src/cli/pyproject.toml @@ -16,6 +16,9 @@ keywords = ["cpl", "cli", "backend", "shared", "library"] dynamic = ["dependencies", "optional-dependencies"] +[project.scripts] +cpl = "cpl.cli.main:main" + [project.urls] Homepage = "https://www.sh-edraft.de" diff --git a/src/dependency/cpl/dependency/hosted/__init__.py b/src/core/cpl/core/service/__init__.py similarity index 100% rename from src/dependency/cpl/dependency/hosted/__init__.py rename to src/core/cpl/core/service/__init__.py diff --git a/src/dependency/cpl/dependency/hosted/cronjob.py b/src/core/cpl/core/service/cronjob.py similarity index 100% rename from src/dependency/cpl/dependency/hosted/cronjob.py rename to src/core/cpl/core/service/cronjob.py diff --git a/src/dependency/cpl/dependency/hosted/hosted_service.py b/src/core/cpl/core/service/hosted_service.py similarity index 100% rename from src/dependency/cpl/dependency/hosted/hosted_service.py rename to src/core/cpl/core/service/hosted_service.py diff --git a/src/dependency/cpl/dependency/hosted/startup_task.py b/src/core/cpl/core/service/startup_task.py similarity index 100% rename from src/dependency/cpl/dependency/hosted/startup_task.py rename to src/core/cpl/core/service/startup_task.py From ba9edfa3fcf4862c78c2a0a428d868fa73079a62 Mon Sep 17 00:00:00 2001 From: edraft Date: Sun, 19 Oct 2025 14:40:24 +0200 Subject: [PATCH 22/22] Added cli dev build --- .gitea/workflows/build-dev.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitea/workflows/build-dev.yaml b/.gitea/workflows/build-dev.yaml index 3d988346..e0438905 100644 --- a/.gitea/workflows/build-dev.yaml +++ b/.gitea/workflows/build-dev.yaml @@ -33,6 +33,13 @@ jobs: working_directory: src/cpl-auth secrets: inherit + cli: + uses: ./.gitea/workflows/package.yaml + needs: [ prepare, core ] + with: + working_directory: src/cpl-cli + secrets: inherit + core: uses: ./.gitea/workflows/package.yaml needs: [prepare]