cli #199

Merged
edraft merged 22 commits from cli into dev 2025-10-19 14:40:46 +02:00
32 changed files with 427 additions and 130 deletions
Showing only changes of commit 76b44ca517 - Show all commits

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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()

View File

@@ -0,0 +1,9 @@
from cpl.core.console import Console
def main():
Console.write_line("Hello, World!")
if __name__ == "__main__":
main()

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
class Class1:
def __init__(self): ...

View File

@@ -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)

View File

@@ -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!")

View File

@@ -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()

View File

@@ -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!")

View File

@@ -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}':")

View File

@@ -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!")

View File

@@ -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

View File

@@ -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:

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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...")

View File

@@ -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}'")

View File

@@ -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="<type> <name>",
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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,43 +1,35 @@
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.")
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 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:
return Project.from_file(Path(p.path))
raise ValueError(f"Project '{project}' not found.")
def create_pyproject_toml(project: Project, path: Path):
@staticmethod
def create_pyproject_toml(project: Project, path: Path):
pyproject_path = path / "pyproject.toml"
if pyproject_path.exists():
return
@@ -58,3 +50,122 @@ def create_pyproject_toml(project: Project, path: Path):
).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.")
for p in workspace.actual_projects:
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:
return Project.from_file(Path(p.path))
raise ValueError(f"Project '{project}' not found.")
@staticmethod
def init_workspace(path: Path | str, name: str):
path = Path(path) / Path("cpl.workspace.json")
if path.exists():
raise ValueError("workspace.json already exists.")
workspace = Workspace.new(str(path), name)
workspace.save()
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)

View File

@@ -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 ../

View File

@@ -1,2 +1 @@
__version__ = "1.0.0"
__version__ = "1.0.0"

View File

@@ -133,5 +133,4 @@ class Configuration:
if isclass(key) and issubclass(key, ConfigurationModelABC) and result == default:
result = key()
cls.set(key, result)
return result

View File

@@ -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:

View File

@@ -0,0 +1,3 @@
class classproperty(property):
def __get__(self, obj, cls):
return self.fget(cls)

View File

@@ -1 +1 @@
from .graphql_app import WebApp
from .graphql_app import GraphQLApp