nautica 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautica-3.0.0/PKG-INFO +112 -0
- nautica-3.0.0/README.md +94 -0
- nautica-3.0.0/napi/__init__.py +1 -0
- nautica-3.0.0/napi/http.py +91 -0
- nautica-3.0.0/nautica/__init__.py +12 -0
- nautica-3.0.0/nautica/__main__.py +9 -0
- nautica-3.0.0/nautica/cli/Create.py +49 -0
- nautica-3.0.0/nautica/cli/Install.py +35 -0
- nautica-3.0.0/nautica/cli/Run.py +25 -0
- nautica-3.0.0/nautica/cli/__init__.py +17 -0
- nautica-3.0.0/nautica/ext/Static.py +268 -0
- nautica-3.0.0/nautica/ext/StatusCodes.py +157 -0
- nautica-3.0.0/nautica/ext/Util.py +103 -0
- nautica-3.0.0/nautica/ext/__init__.py +0 -0
- nautica-3.0.0/nautica/manager/__init__.py +9 -0
- nautica-3.0.0/nautica/manager/config/__init__.py +72 -0
- nautica-3.0.0/nautica/manager/config/builder.py +26 -0
- nautica-3.0.0/nautica/manager/config/helper.py +89 -0
- nautica-3.0.0/nautica/manager/logger/__init__.py +193 -0
- nautica-3.0.0/nautica/manager/logger/levels.py +30 -0
- nautica-3.0.0/nautica/manager/logger/tableutil.py +45 -0
- nautica-3.0.0/nautica/manager/memory/__init__.py +52 -0
- nautica-3.0.0/nautica/models/Http.py +216 -0
- nautica-3.0.0/nautica/models/Requirements.py +117 -0
- nautica-3.0.0/nautica/models/Service.py +62 -0
- nautica-3.0.0/nautica/models/Shell.py +92 -0
- nautica-3.0.0/nautica/services/__init__.py +132 -0
- nautica-3.0.0/nautica/services/builtins/__init__.py +36 -0
- nautica-3.0.0/nautica/services/builtins/http/__init__.py +36 -0
- nautica-3.0.0/nautica/services/builtins/http/middleware.py +167 -0
- nautica-3.0.0/nautica/services/builtins/http/requirements.py +138 -0
- nautica-3.0.0/nautica/services/builtins/http/router.py +61 -0
- nautica-3.0.0/nautica/services/builtins/http/server.py +97 -0
- nautica-3.0.0/nautica/services/builtins/shell/__init__.py +169 -0
- nautica-3.0.0/nautica/services/builtins/shell/commands/basic.py +106 -0
- nautica-3.0.0/nautica/services/builtins/shell/decorator.py +18 -0
- nautica-3.0.0/nautica/services/builtins/shell/gui/__init__.py +55 -0
- nautica-3.0.0/nautica/services/builtins/shell/gui/autocomplete.py +103 -0
- nautica-3.0.0/nautica/services/builtins/shell/gui/css.py +132 -0
- nautica-3.0.0/nautica/services/builtins/shell/gui/pages/home.py +191 -0
- nautica-3.0.0/nautica/services/builtins/shell/gui/themes.py +53 -0
- nautica-3.0.0/nautica.egg-info/PKG-INFO +112 -0
- nautica-3.0.0/nautica.egg-info/SOURCES.txt +47 -0
- nautica-3.0.0/nautica.egg-info/dependency_links.txt +1 -0
- nautica-3.0.0/nautica.egg-info/entry_points.txt +2 -0
- nautica-3.0.0/nautica.egg-info/requires.txt +8 -0
- nautica-3.0.0/nautica.egg-info/top_level.txt +2 -0
- nautica-3.0.0/pyproject.toml +35 -0
- nautica-3.0.0/setup.cfg +4 -0
nautica-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nautica
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: A service management framework
|
|
5
|
+
Author-email: Xellu <xellu@catboys.cc>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/xellu/nautica-api
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: starlette
|
|
11
|
+
Requires-Dist: uvicorn
|
|
12
|
+
Requires-Dist: python-multipart
|
|
13
|
+
Requires-Dist: colorama
|
|
14
|
+
Requires-Dist: tomlkit
|
|
15
|
+
Requires-Dist: click
|
|
16
|
+
Requires-Dist: psutil
|
|
17
|
+
Requires-Dist: textual
|
|
18
|
+
|
|
19
|
+
# Nautica V3
|
|
20
|
+
|
|
21
|
+
Nautica V3 _(also referred to as Nautica API, Nautica3, N3)_ is a modular application framework built around a service registry. It provides you with a structured way to manage components of your applications, such as HTTP or Socket servers, background workers, etc.
|
|
22
|
+
|
|
23
|
+
> N3 is not just an HTTP framework — the HTTP server is one of many services. It's designed for applications that need to coordinate multiple components at once.
|
|
24
|
+
|
|
25
|
+
## What's Included
|
|
26
|
+
- **Service Registry** with dependency resolver
|
|
27
|
+
- **Lifecycle hooks** for install, start and shutdown
|
|
28
|
+
- TOML **Config system** with key management
|
|
29
|
+
- **Logger** with file output and memory
|
|
30
|
+
- **Shell** for interacting with services
|
|
31
|
+
- **TUI** for live logs, thread and worker inspection _(optional)_
|
|
32
|
+
- **Plugin system** for extending projects without modifying core code
|
|
33
|
+
- **HTTP API** built on Starlette + Uvicorn
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
I made N3 because I was solving the same problems in my projects, those being: configs, logging, figuring out startup orders, not to mention the validation boilerplate on every route. Because of this I made Nautica V2, which solved many of these issues, and V3 to improve on the idea.
|
|
38
|
+
|
|
39
|
+
## How HTTP API Compares
|
|
40
|
+
|
|
41
|
+
### Benchmark Performance
|
|
42
|
+
|
|
43
|
+
| Library | Requests/Second | Avg Latency | Overall |
|
|
44
|
+
| --- | --- | --- | --- |
|
|
45
|
+
| Nautica3 | `2271` | `4.4ms` | - |
|
|
46
|
+
| FastAPI | `2259` | `4.4ms` | 0.5% slower |
|
|
47
|
+
| Flask | `75` | `132.6ms` | 96% slower |
|
|
48
|
+
|
|
49
|
+
*Ran with 10 workers, for 10 seconds. Nautica3 matches FastAPI despite running additional middleware, requirement parsing, and logging on every request.*
|
|
50
|
+
|
|
51
|
+
### Code Complexity
|
|
52
|
+
|
|
53
|
+
Similarly to SvelteKit, Nautica defines the route names for you. This helps to reduce boilerplate (see Flask and FastAPI examples), improve readability, and helps with naming conventions
|
|
54
|
+
|
|
55
|
+
#### Nautica3
|
|
56
|
+
```py
|
|
57
|
+
# file: src/http/api/v1/auth.py
|
|
58
|
+
from napi.http import HTTP, Context, Reply
|
|
59
|
+
from somewhere import username, password
|
|
60
|
+
|
|
61
|
+
@HTTP.POST()
|
|
62
|
+
@HTTP.Require(body = {"username": str, "password": str})
|
|
63
|
+
def login(ctx: Context):
|
|
64
|
+
if ctx.body["username"] == username and ctx.body["password"] == password:
|
|
65
|
+
return Reply(ok=True)
|
|
66
|
+
return Reply(ok=False, error="Invalid credentials"), 401
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Flask
|
|
70
|
+
```py
|
|
71
|
+
# file: main.py
|
|
72
|
+
from flask import Flask, request
|
|
73
|
+
from flask.blueprints import Blueprint
|
|
74
|
+
import json
|
|
75
|
+
from somewhere import username, password
|
|
76
|
+
|
|
77
|
+
app = Flask(__name__)
|
|
78
|
+
v1auth = Blueprint("v1auth", __name__, url_prefix="/api/v1/auth")
|
|
79
|
+
|
|
80
|
+
@v1auth.post("/login")
|
|
81
|
+
def login():
|
|
82
|
+
data = request.get_json(silent=True) or {}
|
|
83
|
+
if data.get("username") == username and data.get("password") == password:
|
|
84
|
+
return json.dumps({"ok": True})
|
|
85
|
+
return json.dumps({"ok": False, "error": "Invalid credentials"}), 401
|
|
86
|
+
|
|
87
|
+
app.register_blueprint(v1auth)
|
|
88
|
+
app.run(port=8101)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### FastAPI
|
|
92
|
+
```py
|
|
93
|
+
# file: main.py
|
|
94
|
+
from fastapi import FastAPI, HTTPException
|
|
95
|
+
from pydantic import BaseModel
|
|
96
|
+
import uvicorn
|
|
97
|
+
from somewhere import username, password
|
|
98
|
+
|
|
99
|
+
app = FastAPI()
|
|
100
|
+
|
|
101
|
+
class LoginRequest(BaseModel):
|
|
102
|
+
username: str
|
|
103
|
+
password: str
|
|
104
|
+
|
|
105
|
+
@app.post("/api/v1/auth/login")
|
|
106
|
+
def login(body: LoginRequest):
|
|
107
|
+
if body.username == username and body.password == password:
|
|
108
|
+
return {"ok": True}
|
|
109
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
110
|
+
|
|
111
|
+
uvicorn.run(app, port=8101)
|
|
112
|
+
```
|
nautica-3.0.0/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Nautica V3
|
|
2
|
+
|
|
3
|
+
Nautica V3 _(also referred to as Nautica API, Nautica3, N3)_ is a modular application framework built around a service registry. It provides you with a structured way to manage components of your applications, such as HTTP or Socket servers, background workers, etc.
|
|
4
|
+
|
|
5
|
+
> N3 is not just an HTTP framework — the HTTP server is one of many services. It's designed for applications that need to coordinate multiple components at once.
|
|
6
|
+
|
|
7
|
+
## What's Included
|
|
8
|
+
- **Service Registry** with dependency resolver
|
|
9
|
+
- **Lifecycle hooks** for install, start and shutdown
|
|
10
|
+
- TOML **Config system** with key management
|
|
11
|
+
- **Logger** with file output and memory
|
|
12
|
+
- **Shell** for interacting with services
|
|
13
|
+
- **TUI** for live logs, thread and worker inspection _(optional)_
|
|
14
|
+
- **Plugin system** for extending projects without modifying core code
|
|
15
|
+
- **HTTP API** built on Starlette + Uvicorn
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
I made N3 because I was solving the same problems in my projects, those being: configs, logging, figuring out startup orders, not to mention the validation boilerplate on every route. Because of this I made Nautica V2, which solved many of these issues, and V3 to improve on the idea.
|
|
20
|
+
|
|
21
|
+
## How HTTP API Compares
|
|
22
|
+
|
|
23
|
+
### Benchmark Performance
|
|
24
|
+
|
|
25
|
+
| Library | Requests/Second | Avg Latency | Overall |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| Nautica3 | `2271` | `4.4ms` | - |
|
|
28
|
+
| FastAPI | `2259` | `4.4ms` | 0.5% slower |
|
|
29
|
+
| Flask | `75` | `132.6ms` | 96% slower |
|
|
30
|
+
|
|
31
|
+
*Ran with 10 workers, for 10 seconds. Nautica3 matches FastAPI despite running additional middleware, requirement parsing, and logging on every request.*
|
|
32
|
+
|
|
33
|
+
### Code Complexity
|
|
34
|
+
|
|
35
|
+
Similarly to SvelteKit, Nautica defines the route names for you. This helps to reduce boilerplate (see Flask and FastAPI examples), improve readability, and helps with naming conventions
|
|
36
|
+
|
|
37
|
+
#### Nautica3
|
|
38
|
+
```py
|
|
39
|
+
# file: src/http/api/v1/auth.py
|
|
40
|
+
from napi.http import HTTP, Context, Reply
|
|
41
|
+
from somewhere import username, password
|
|
42
|
+
|
|
43
|
+
@HTTP.POST()
|
|
44
|
+
@HTTP.Require(body = {"username": str, "password": str})
|
|
45
|
+
def login(ctx: Context):
|
|
46
|
+
if ctx.body["username"] == username and ctx.body["password"] == password:
|
|
47
|
+
return Reply(ok=True)
|
|
48
|
+
return Reply(ok=False, error="Invalid credentials"), 401
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Flask
|
|
52
|
+
```py
|
|
53
|
+
# file: main.py
|
|
54
|
+
from flask import Flask, request
|
|
55
|
+
from flask.blueprints import Blueprint
|
|
56
|
+
import json
|
|
57
|
+
from somewhere import username, password
|
|
58
|
+
|
|
59
|
+
app = Flask(__name__)
|
|
60
|
+
v1auth = Blueprint("v1auth", __name__, url_prefix="/api/v1/auth")
|
|
61
|
+
|
|
62
|
+
@v1auth.post("/login")
|
|
63
|
+
def login():
|
|
64
|
+
data = request.get_json(silent=True) or {}
|
|
65
|
+
if data.get("username") == username and data.get("password") == password:
|
|
66
|
+
return json.dumps({"ok": True})
|
|
67
|
+
return json.dumps({"ok": False, "error": "Invalid credentials"}), 401
|
|
68
|
+
|
|
69
|
+
app.register_blueprint(v1auth)
|
|
70
|
+
app.run(port=8101)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### FastAPI
|
|
74
|
+
```py
|
|
75
|
+
# file: main.py
|
|
76
|
+
from fastapi import FastAPI, HTTPException
|
|
77
|
+
from pydantic import BaseModel
|
|
78
|
+
import uvicorn
|
|
79
|
+
from somewhere import username, password
|
|
80
|
+
|
|
81
|
+
app = FastAPI()
|
|
82
|
+
|
|
83
|
+
class LoginRequest(BaseModel):
|
|
84
|
+
username: str
|
|
85
|
+
password: str
|
|
86
|
+
|
|
87
|
+
@app.post("/api/v1/auth/login")
|
|
88
|
+
def login(body: LoginRequest):
|
|
89
|
+
if body.username == username and body.password == password:
|
|
90
|
+
return {"ok": True}
|
|
91
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
92
|
+
|
|
93
|
+
uvicorn.run(app, port=8101)
|
|
94
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import http
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
from nautica.manager import Logger
|
|
3
|
+
|
|
4
|
+
from nautica.services.builtins.http.middleware import Middleware
|
|
5
|
+
from nautica.models.Http import RouteRequirements, RequestContext as Context, Reply, ErrorReply as Error
|
|
6
|
+
from nautica.models import Requirements
|
|
7
|
+
from nautica.ext import StatusCodes
|
|
8
|
+
|
|
9
|
+
from starlette.responses import JSONResponse, PlainTextResponse, FileResponse, HTMLResponse, RedirectResponse, StreamingResponse
|
|
10
|
+
|
|
11
|
+
Require = Requirements
|
|
12
|
+
|
|
13
|
+
class RouteManager:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.temp = []
|
|
16
|
+
|
|
17
|
+
def _create(self, r):
|
|
18
|
+
self.temp.append(r)
|
|
19
|
+
Logger.debug(f"Registered route for {r.func.__name__}, {r.method=}, {r.name=}")
|
|
20
|
+
|
|
21
|
+
def GET(self, name: str | None = None):
|
|
22
|
+
return Middleware(self, "get", name).decorator
|
|
23
|
+
|
|
24
|
+
def POST(self, name: str | None = None):
|
|
25
|
+
return Middleware(self, "post", name).decorator
|
|
26
|
+
|
|
27
|
+
def HEAD(self, name: str | None = None):
|
|
28
|
+
return Middleware(self, "head", name).decorator
|
|
29
|
+
|
|
30
|
+
def PUT(self, name: str | None = None):
|
|
31
|
+
return Middleware(self, "put", name).decorator
|
|
32
|
+
|
|
33
|
+
def DELETE(self, name: str | None = None):
|
|
34
|
+
return Middleware(self, "delete", name).decorator
|
|
35
|
+
|
|
36
|
+
def CONNECT(self, name: str | None = None):
|
|
37
|
+
return Middleware(self, "connect", name).decorator
|
|
38
|
+
|
|
39
|
+
def TRACE(self, name: str | None = None):
|
|
40
|
+
return Middleware(self, "trace", name).decorator
|
|
41
|
+
|
|
42
|
+
def PATCH(self, name: str | None = None):
|
|
43
|
+
return Middleware(self, "patch", name).decorator
|
|
44
|
+
|
|
45
|
+
def Require(self,
|
|
46
|
+
body: dict = None,
|
|
47
|
+
headers: dict = None,
|
|
48
|
+
cookies: dict = None,
|
|
49
|
+
query: dict = None,
|
|
50
|
+
files: dict = None
|
|
51
|
+
):
|
|
52
|
+
for field in [body or {}, headers or {}, cookies or {}, query or {}]:
|
|
53
|
+
for v in field.values():
|
|
54
|
+
if not (isinstance(v, type) or isinstance(v, Requirements.Requirement)): raise TypeError(f"Context builder only accepts types and Requirements")
|
|
55
|
+
|
|
56
|
+
for v in (files or {}).values():
|
|
57
|
+
if not isinstance(v, Requirements.File): raise TypeError(f"File dict only accepts Requirements.File, provided '{type(v).__name__}'")
|
|
58
|
+
|
|
59
|
+
def decorator(func):
|
|
60
|
+
func._requirements = RouteRequirements(
|
|
61
|
+
body=body,
|
|
62
|
+
headers=headers,
|
|
63
|
+
cookies=cookies,
|
|
64
|
+
query=query,
|
|
65
|
+
files = files
|
|
66
|
+
)
|
|
67
|
+
return func
|
|
68
|
+
|
|
69
|
+
return decorator
|
|
70
|
+
|
|
71
|
+
def Before(self, fn):
|
|
72
|
+
def decorator(func):
|
|
73
|
+
if not hasattr(fn, "_before"):
|
|
74
|
+
fn._before = []
|
|
75
|
+
fn._before.append(func)
|
|
76
|
+
return func
|
|
77
|
+
return decorator
|
|
78
|
+
|
|
79
|
+
def After(self, fn):
|
|
80
|
+
def decorator(func):
|
|
81
|
+
if not hasattr(fn, "_after"):
|
|
82
|
+
fn._after = []
|
|
83
|
+
fn._after.append(func)
|
|
84
|
+
return func
|
|
85
|
+
return decorator
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
HTTP = Router = RouteManager()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from . import cli, click
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from colorama import Fore
|
|
5
|
+
|
|
6
|
+
from ..ext.Static import banner, GitIgnore, ProjectExample
|
|
7
|
+
from ..ext.Util import walkPath
|
|
8
|
+
|
|
9
|
+
from ..manager import Logger, LogLevel
|
|
10
|
+
|
|
11
|
+
from ..services import Registry
|
|
12
|
+
|
|
13
|
+
@cli.command()
|
|
14
|
+
@click.argument("name", type=str)
|
|
15
|
+
def create(name):
|
|
16
|
+
print(f"{Fore.BLUE}{banner()}{Fore.RESET}")
|
|
17
|
+
if os.path.exists(name) and len(walkPath(name, include_dirs=True)) > 0:
|
|
18
|
+
Logger.error(f"A Non-empty directory with this name already exists")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
#prep working directory
|
|
22
|
+
Logger.info("Creating project directories...")
|
|
23
|
+
os.makedirs(name, exist_ok=True)
|
|
24
|
+
os.chdir(name)
|
|
25
|
+
|
|
26
|
+
for f in [".logs", "config", "plugins", "src/http"]:
|
|
27
|
+
os.makedirs(f, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
with open(".gitignore", "w") as f: f.write(GitIgnore)
|
|
30
|
+
with open("src/http/+root.py", "w") as f: f.write(ProjectExample)
|
|
31
|
+
|
|
32
|
+
Logger.ok("Created project tree")
|
|
33
|
+
|
|
34
|
+
#install services
|
|
35
|
+
Logger.info("Installing services...")
|
|
36
|
+
|
|
37
|
+
Registry.ImportAll()
|
|
38
|
+
Registry.onInstall()
|
|
39
|
+
|
|
40
|
+
Logger.ok("Services Installed")
|
|
41
|
+
|
|
42
|
+
#clean up
|
|
43
|
+
os.chdir("..")
|
|
44
|
+
|
|
45
|
+
Logger.table() \
|
|
46
|
+
.labels(["Project Created! Get Started by running:"]) \
|
|
47
|
+
.row([f"cd {name}"]).row(["nautica install"]).row(["nautica run ."]) \
|
|
48
|
+
.row([""]).row(["Thank you for using Nautica3!"]) \
|
|
49
|
+
.display(LogLevel.DEBUG)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from . import cli
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from colorama import Fore
|
|
5
|
+
|
|
6
|
+
from ..ext.Static import banner
|
|
7
|
+
|
|
8
|
+
from ..manager import Logger, LogLevel
|
|
9
|
+
from ..manager.config import ROOT_CONFIGS
|
|
10
|
+
|
|
11
|
+
from ..services import Registry
|
|
12
|
+
|
|
13
|
+
@cli.command(aliases=["i"])
|
|
14
|
+
def install():
|
|
15
|
+
print(f"{Fore.BLUE}{banner()}{Fore.RESET}")
|
|
16
|
+
|
|
17
|
+
Logger.info("Validating project configuration...")
|
|
18
|
+
for path in ROOT_CONFIGS.values():
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
Logger.error(f"Project config '{path}' is missing!")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
#add download sequence whenever i add package manager
|
|
24
|
+
|
|
25
|
+
#install services
|
|
26
|
+
Logger.info("Installing services...")
|
|
27
|
+
|
|
28
|
+
Registry.ImportAll()
|
|
29
|
+
Registry.onInstall()
|
|
30
|
+
|
|
31
|
+
Logger.ok("Services Installed")
|
|
32
|
+
|
|
33
|
+
Logger.table() \
|
|
34
|
+
.labels(["Services Installed"]) \
|
|
35
|
+
.display(LogLevel.DEBUG)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from . import cli, click
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from colorama import Fore
|
|
5
|
+
|
|
6
|
+
from ..ext.Static import banner
|
|
7
|
+
|
|
8
|
+
from ..manager import Logger, LogLevel
|
|
9
|
+
from ..manager.config import ROOT_CONFIGS
|
|
10
|
+
|
|
11
|
+
from ..services import Services
|
|
12
|
+
|
|
13
|
+
@cli.command()
|
|
14
|
+
@click.argument("path", type=str)
|
|
15
|
+
def run(path: str = "."):
|
|
16
|
+
print(f"{Fore.BLUE}{banner()}{Fore.RESET}")
|
|
17
|
+
|
|
18
|
+
Logger.info("Validating project configuration...")
|
|
19
|
+
for path in ROOT_CONFIGS.values():
|
|
20
|
+
if not os.path.exists(path):
|
|
21
|
+
Logger.error(f"Project config '{path}' is missing!")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
Services.ImportAll()
|
|
25
|
+
Services.onStart()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AliasedGroup(click.Group):
|
|
6
|
+
def command(self, *args, aliases: list[str] = None, **kwargs):
|
|
7
|
+
decorator = super().command(*args, **kwargs)
|
|
8
|
+
def wrapper(func):
|
|
9
|
+
cmd = decorator(func)
|
|
10
|
+
for alias in (aliases or []):
|
|
11
|
+
self.add_command(cmd, name=alias)
|
|
12
|
+
return cmd
|
|
13
|
+
return wrapper
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(cls=AliasedGroup)
|
|
17
|
+
def cli(): ...
|