coloco 0.2.0__tar.gz → 0.3.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.
- {coloco-0.2.0 → coloco-0.3.0}/PKG-INFO +43 -19
- coloco-0.3.0/README.md +73 -0
- {coloco-0.2.0 → coloco-0.3.0}/pyproject.toml +2 -1
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/__main__.py +12 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/api.py +10 -8
- coloco-0.3.0/src/coloco/app.py +95 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/cli/api.py +43 -6
- coloco-0.3.0/src/coloco/cli/build.py +22 -0
- coloco-0.3.0/src/coloco/cli/db.py +100 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/cli/dev.py +5 -3
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/cli/node.py +41 -6
- coloco-0.3.0/src/coloco/cli/serve.py +14 -0
- coloco-0.3.0/src/coloco/db.py +39 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/exceptions.py +11 -3
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/lifespan.py +3 -1
- coloco-0.3.0/src/coloco/static.py +12 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/app.svelte-tpl +4 -4
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/package.json-tpl +2 -2
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/vite.config.ts-tpl +14 -0
- coloco-0.3.0/src/coloco/templates/standard/pyproject.toml-tpl +13 -0
- coloco-0.2.0/README.md +0 -50
- coloco-0.2.0/src/coloco/app.py +0 -58
- coloco-0.2.0/src/coloco/requirements.txt +0 -6
- coloco-0.2.0/src/coloco/static.py +0 -12
- coloco-0.2.0/src/coloco/templates/standard/requirements.txt-tpl +0 -1
- {coloco-0.2.0 → coloco-0.3.0}/LICENSE +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/__init__.py +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/cli/createapp.py +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/cli/package.py +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/codegen.py +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/docker/.dockerignore-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/docker/Dockerfile-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/docker/docker-compose.yml-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/404.svelte-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/app.css-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/index.html-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/index.svelte-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+app/main.ts-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/openapi-ts.config.ts-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/svelte.config.js-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/tsconfig.json-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/+node/vite-env.d.ts-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/.gitignore-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/README.md-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/example/api.py-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/example/index.svelte-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/main.py-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/src/coloco/templates/standard/tsconfig.json-tpl +0 -0
- {coloco-0.2.0 → coloco-0.3.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: coloco
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A kit for creating FastAPI + Svelte applications
|
|
5
5
|
Author-Email: Channel Cat <channelcat@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -9,17 +9,19 @@ Requires-Dist: aerich[toml]==0.8.1
|
|
|
9
9
|
Requires-Dist: fastapi==0.115.0
|
|
10
10
|
Requires-Dist: PyJWT==2.10.1
|
|
11
11
|
Requires-Dist: tortoise-orm[asyncpg]==0.24.0
|
|
12
|
+
Requires-Dist: type-less==0.1.4
|
|
12
13
|
Requires-Dist: typer==0.15.2
|
|
13
14
|
Requires-Dist: uvicorn==0.31.0
|
|
14
15
|
Description-Content-Type: text/markdown
|
|
15
16
|
|
|
16
17
|
# Coloco
|
|
17
18
|
|
|
18
|
-
A kit for creating full-stack apps with co-located code, built on FastAPI and Svelte.
|
|
19
|
+
A kit for creating full-stack apps with co-located code, built on FastAPI and Svelte. Bundle your front-end and back-end code and easily tie them together with codegen.
|
|
19
20
|
|
|
20
21
|
Example:
|
|
21
22
|
|
|
22
23
|
`hello/api.py`
|
|
24
|
+
|
|
23
25
|
```python
|
|
24
26
|
from coloco import api
|
|
25
27
|
|
|
@@ -30,36 +32,58 @@ def test(name: str) -> str:
|
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
`hello/index.svelte`
|
|
35
|
+
|
|
33
36
|
```svelte
|
|
34
37
|
<script lang="ts">
|
|
35
38
|
import { test } from "./api";
|
|
36
|
-
|
|
37
|
-
const response = test({ query: { name: "Coloco" } });
|
|
38
39
|
</script>
|
|
39
40
|
|
|
40
|
-
{#
|
|
41
|
-
|
|
42
|
-
{:
|
|
43
|
-
|
|
44
|
-
{/
|
|
41
|
+
{#await test({ name: "Coloco" })}
|
|
42
|
+
Loading...
|
|
43
|
+
{:then result}
|
|
44
|
+
The server says {result}
|
|
45
|
+
{/await}
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=Coloco` and prints the message `Hello Coloco!`
|
|
48
49
|
|
|
50
|
+
# Getting Started
|
|
51
|
+
|
|
52
|
+
- `pip install coloco`
|
|
53
|
+
- `coloco createapp myapp`
|
|
54
|
+
- From `myapp` - `coloco dev`
|
|
55
|
+
|
|
56
|
+
# Running in Production
|
|
57
|
+
|
|
58
|
+
- `coloco build`
|
|
59
|
+
|
|
60
|
+
- `coloco serve`
|
|
61
|
+
|
|
49
62
|
# Opinions
|
|
50
63
|
|
|
51
64
|
This framework is opinionated and combines the following excellent tools:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
|
|
66
|
+
- FastAPI
|
|
67
|
+
- Svelte
|
|
68
|
+
- openapi-ts (codegen)
|
|
69
|
+
- svelte5-router (file-based routing)
|
|
70
|
+
- tortoise-orm (optional)
|
|
57
71
|
|
|
58
72
|
# Plans
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
|
|
74
|
+
- Deploy tools
|
|
75
|
+
- ORM Support
|
|
76
|
+
- SQLite/Postgres support
|
|
77
|
+
- Package/share modules with git
|
|
62
78
|
|
|
63
79
|
# Dreams
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
|
|
81
|
+
- Move `node_modules` into `+node`
|
|
82
|
+
- Somehow clean up litter from `uv`
|
|
83
|
+
|
|
84
|
+
# TODO
|
|
85
|
+
|
|
86
|
+
- Swap to `cyclopts` for CLI
|
|
87
|
+
- Swap to `copier` for template creation
|
|
88
|
+
- `Textual` CLI app / installer / dev tools?
|
|
89
|
+
- Common dev `admin` module
|
coloco-0.3.0/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Coloco
|
|
2
|
+
|
|
3
|
+
A kit for creating full-stack apps with co-located code, built on FastAPI and Svelte. Bundle your front-end and back-end code and easily tie them together with codegen.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
|
|
7
|
+
`hello/api.py`
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from coloco import api
|
|
11
|
+
|
|
12
|
+
@api
|
|
13
|
+
def test(name: str) -> str:
|
|
14
|
+
return f"Hello {name}!"
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`hello/index.svelte`
|
|
19
|
+
|
|
20
|
+
```svelte
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
import { test } from "./api";
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#await test({ name: "Coloco" })}
|
|
26
|
+
Loading...
|
|
27
|
+
{:then result}
|
|
28
|
+
The server says {result}
|
|
29
|
+
{/await}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=Coloco` and prints the message `Hello Coloco!`
|
|
33
|
+
|
|
34
|
+
# Getting Started
|
|
35
|
+
|
|
36
|
+
- `pip install coloco`
|
|
37
|
+
- `coloco createapp myapp`
|
|
38
|
+
- From `myapp` - `coloco dev`
|
|
39
|
+
|
|
40
|
+
# Running in Production
|
|
41
|
+
|
|
42
|
+
- `coloco build`
|
|
43
|
+
|
|
44
|
+
- `coloco serve`
|
|
45
|
+
|
|
46
|
+
# Opinions
|
|
47
|
+
|
|
48
|
+
This framework is opinionated and combines the following excellent tools:
|
|
49
|
+
|
|
50
|
+
- FastAPI
|
|
51
|
+
- Svelte
|
|
52
|
+
- openapi-ts (codegen)
|
|
53
|
+
- svelte5-router (file-based routing)
|
|
54
|
+
- tortoise-orm (optional)
|
|
55
|
+
|
|
56
|
+
# Plans
|
|
57
|
+
|
|
58
|
+
- Deploy tools
|
|
59
|
+
- ORM Support
|
|
60
|
+
- SQLite/Postgres support
|
|
61
|
+
- Package/share modules with git
|
|
62
|
+
|
|
63
|
+
# Dreams
|
|
64
|
+
|
|
65
|
+
- Move `node_modules` into `+node`
|
|
66
|
+
- Somehow clean up litter from `uv`
|
|
67
|
+
|
|
68
|
+
# TODO
|
|
69
|
+
|
|
70
|
+
- Swap to `cyclopts` for CLI
|
|
71
|
+
- Swap to `copier` for template creation
|
|
72
|
+
- `Textual` CLI app / installer / dev tools?
|
|
73
|
+
- Common dev `admin` module
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coloco"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "A kit for creating FastAPI + Svelte applications"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Channel Cat", email = "channelcat@gmail.com" },
|
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
|
10
10
|
"fastapi==0.115.0",
|
|
11
11
|
"PyJWT==2.10.1",
|
|
12
12
|
"tortoise-orm[asyncpg]==0.24.0",
|
|
13
|
+
"type-less==0.1.4",
|
|
13
14
|
"typer==0.15.2",
|
|
14
15
|
"uvicorn==0.31.0",
|
|
15
16
|
]
|
|
@@ -3,13 +3,25 @@ from .cli.dev import dev
|
|
|
3
3
|
from .cli.api import app as api_app
|
|
4
4
|
from .cli.node import app as node_app
|
|
5
5
|
from .cli.createapp import createapp
|
|
6
|
+
from .cli.build import build
|
|
7
|
+
from .cli.serve import serve
|
|
6
8
|
|
|
7
9
|
app = typer.Typer()
|
|
8
10
|
|
|
9
11
|
app.command()(dev)
|
|
10
12
|
app.add_typer(node_app, name="node")
|
|
11
13
|
app.add_typer(api_app, name="api")
|
|
14
|
+
app.command()(build)
|
|
12
15
|
app.command()(createapp)
|
|
16
|
+
app.command()(serve)
|
|
17
|
+
|
|
18
|
+
# Add DB if tortoise/aerich are installed
|
|
19
|
+
try:
|
|
20
|
+
from .cli.db import app as db_app
|
|
21
|
+
|
|
22
|
+
app.add_typer(db_app, name="db")
|
|
23
|
+
except ImportError:
|
|
24
|
+
pass
|
|
13
25
|
|
|
14
26
|
if __name__ == "__main__":
|
|
15
27
|
app()
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from contextlib import asynccontextmanager
|
|
2
|
-
from fastapi import APIRouter, FastAPI
|
|
3
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
-
import logging
|
|
5
|
-
from os import environ
|
|
6
1
|
from .codegen import (
|
|
7
2
|
custom_generate_unique_id,
|
|
8
3
|
generate_openapi_code,
|
|
9
4
|
generate_openapi_schema,
|
|
10
5
|
)
|
|
11
|
-
from
|
|
12
|
-
from .static import bind_static
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
13
7
|
from .exceptions import bind_exceptions
|
|
8
|
+
from fastapi import APIRouter, FastAPI
|
|
9
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
10
|
+
from .lifespan import execute_lifespan, register_lifespan
|
|
11
|
+
import logging
|
|
12
|
+
from os import environ
|
|
13
|
+
from type_less import fill_type_hints
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -54,7 +54,7 @@ def create_api(is_dev: bool = False):
|
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
# bind_static(api)
|
|
57
|
-
bind_exceptions(api)
|
|
57
|
+
bind_exceptions(api, debug=is_dev)
|
|
58
58
|
|
|
59
59
|
return api
|
|
60
60
|
|
|
@@ -70,6 +70,8 @@ def api(func):
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _add_global_route(args, kwargs, func, method: str):
|
|
73
|
+
fill_type_hints(func, use_literals=True)
|
|
74
|
+
|
|
73
75
|
# Prepend module name to path
|
|
74
76
|
path = args[0] if args else kwargs.get("path", "")
|
|
75
77
|
path = (
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from .api import create_api, global_router
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from .db import get_orm_config, lifecycle_connect_database
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from .lifespan import register_lifespan
|
|
7
|
+
import os
|
|
8
|
+
from rich import print
|
|
9
|
+
from .static import bind_static
|
|
10
|
+
import traceback
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ColocoApp:
|
|
16
|
+
api: FastAPI
|
|
17
|
+
name: str
|
|
18
|
+
database_url: str = None
|
|
19
|
+
orm_config: dict = None
|
|
20
|
+
migrations_dir: str = "./+migrations"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def discover_files(directory, name):
|
|
24
|
+
api_files = []
|
|
25
|
+
try:
|
|
26
|
+
with os.scandir(directory) as entries:
|
|
27
|
+
for entry in entries:
|
|
28
|
+
if entry.is_dir():
|
|
29
|
+
# Skip directories starting with "+" and "node_modules"
|
|
30
|
+
if (
|
|
31
|
+
not entry.name.startswith("+")
|
|
32
|
+
and not entry.name.startswith("-")
|
|
33
|
+
and not entry.name.startswith(".")
|
|
34
|
+
and not entry.name == "node_modules"
|
|
35
|
+
and not entry.name == "coloco"
|
|
36
|
+
):
|
|
37
|
+
api_files.extend(discover_files(entry.path, name))
|
|
38
|
+
elif entry.is_file() and entry.name == name:
|
|
39
|
+
api_files.append(entry.path)
|
|
40
|
+
except (PermissionError, FileNotFoundError) as e:
|
|
41
|
+
print(f"Error accessing {directory}: {e}")
|
|
42
|
+
return api_files
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
CURRENT_APP = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_app(name: str, database_url: str = None) -> ColocoApp:
|
|
49
|
+
global CURRENT_APP
|
|
50
|
+
if CURRENT_APP:
|
|
51
|
+
raise ValueError("Coloco app already created")
|
|
52
|
+
|
|
53
|
+
mode: Literal["dev", "prod"] = os.environ.get("COLOCO_MODE", "dev")
|
|
54
|
+
api = create_api(is_dev=mode == "dev")
|
|
55
|
+
|
|
56
|
+
# Discover all api.py files from root, excluding node_modules and +app
|
|
57
|
+
api_files = discover_files(".", name="api.py")
|
|
58
|
+
for api_file in api_files:
|
|
59
|
+
# convert python file path to module path
|
|
60
|
+
module_name = api_file.replace("./", "").replace(".py", "").replace("/", ".")
|
|
61
|
+
try:
|
|
62
|
+
module = import_module(module_name)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print(f"[red]Error importing '{api_file}': {e}[/red]")
|
|
65
|
+
print(traceback.format_exc())
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
api.include_router(global_router)
|
|
69
|
+
from fastapi.staticfiles import StaticFiles
|
|
70
|
+
|
|
71
|
+
api.mount("/assets", StaticFiles(directory="dist/app/assets"), name="assets")
|
|
72
|
+
|
|
73
|
+
# Production mode serves dist
|
|
74
|
+
if mode == "prod":
|
|
75
|
+
bind_static(api)
|
|
76
|
+
|
|
77
|
+
# Setup Database
|
|
78
|
+
if database_url:
|
|
79
|
+
orm_config = get_orm_config(
|
|
80
|
+
database_url, model_files=discover_files(".", name="models.py")
|
|
81
|
+
)
|
|
82
|
+
register_lifespan(lifecycle_connect_database)
|
|
83
|
+
else:
|
|
84
|
+
orm_config = None
|
|
85
|
+
|
|
86
|
+
CURRENT_APP = ColocoApp(
|
|
87
|
+
api=api, name=name, database_url=database_url, orm_config=orm_config
|
|
88
|
+
)
|
|
89
|
+
return CURRENT_APP
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_current_app() -> ColocoApp:
|
|
93
|
+
if not CURRENT_APP:
|
|
94
|
+
raise ValueError("Coloco app not created")
|
|
95
|
+
return CURRENT_APP
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
from ..app import ColocoApp
|
|
2
|
+
from ..codegen import generate_openapi_schema, generate_openapi_code
|
|
2
3
|
from importlib import import_module
|
|
3
4
|
import os
|
|
4
5
|
from rich import print
|
|
5
6
|
import sys
|
|
6
7
|
import typer
|
|
8
|
+
from typing import Literal
|
|
7
9
|
import uvicorn
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
app = typer.Typer()
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def _verify_app(app: str):
|
|
15
|
+
def _verify_app(app: str = "main.app") -> ColocoApp:
|
|
14
16
|
if not "." in app:
|
|
15
17
|
print(
|
|
16
18
|
"[red]App should be the name of a variable in a python file, example: main.py -> api = main.api[/red]"
|
|
17
19
|
)
|
|
18
20
|
raise typer.Abort()
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
module_name, var_name = app.rsplit(".", 1)
|
|
21
23
|
try:
|
|
22
24
|
# Needed for when running the binary
|
|
@@ -35,17 +37,37 @@ def _verify_app(app: str):
|
|
|
35
37
|
if not isinstance(var, ColocoApp):
|
|
36
38
|
print(f"[red]{var_name} is not a ColocoApp. Please use create_app[/red]")
|
|
37
39
|
raise typer.Abort()
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
+
|
|
41
|
+
return var
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _verify_is_packaged():
|
|
45
|
+
dist_dir = os.path.join(os.getcwd(), "dist")
|
|
46
|
+
app_dir = os.path.join(dist_dir, "app")
|
|
47
|
+
|
|
48
|
+
# Verify dist dir exists
|
|
49
|
+
if not os.path.exists(dist_dir):
|
|
50
|
+
print(
|
|
51
|
+
f"[red]Dist dir {dist_dir} does not exist. Run [green]coloco build[/green] to package the app.[/red]"
|
|
52
|
+
)
|
|
53
|
+
raise typer.Abort()
|
|
54
|
+
if not os.path.exists(app_dir):
|
|
55
|
+
print(
|
|
56
|
+
f"[red]App is missing from package directory {app_dir}. Run [green]coloco build[/green] to package the app.[/red]"
|
|
57
|
+
)
|
|
58
|
+
raise typer.Abort()
|
|
59
|
+
|
|
40
60
|
|
|
41
61
|
def _serve(
|
|
42
62
|
app: str = "main.app",
|
|
43
63
|
host: str = "127.0.0.1",
|
|
44
64
|
port: int = 80,
|
|
45
65
|
log_level: str = "info",
|
|
66
|
+
mode: Literal["dev", "prod"] = "prod",
|
|
46
67
|
reload=False,
|
|
47
68
|
):
|
|
48
69
|
module_name, var_name = app.rsplit(".", 1)
|
|
70
|
+
os.environ["COLOCO_MODE"] = mode
|
|
49
71
|
uvicorn.run(
|
|
50
72
|
f"{module_name}:{var_name}.api.service",
|
|
51
73
|
host=host,
|
|
@@ -54,13 +76,28 @@ def _serve(
|
|
|
54
76
|
log_level=log_level,
|
|
55
77
|
)
|
|
56
78
|
|
|
79
|
+
|
|
57
80
|
@app.command()
|
|
58
81
|
def serve(
|
|
59
82
|
app: str = "main.app",
|
|
60
83
|
host: str = "127.0.0.1",
|
|
61
84
|
port: int = 80,
|
|
62
85
|
log_level: str = "info",
|
|
63
|
-
|
|
86
|
+
mode="prod",
|
|
87
|
+
reload: bool = False,
|
|
64
88
|
):
|
|
65
89
|
_verify_app(app)
|
|
66
|
-
|
|
90
|
+
if mode == "prod":
|
|
91
|
+
_verify_is_packaged()
|
|
92
|
+
_serve(app=app, host=host, port=port, log_level=log_level, mode=mode, reload=reload)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command()
|
|
96
|
+
def codegen(
|
|
97
|
+
app: str = "main.app",
|
|
98
|
+
):
|
|
99
|
+
coloco_app = _verify_app(app)
|
|
100
|
+
print("Generating OpenAPI code...")
|
|
101
|
+
generate_openapi_schema(coloco_app.api)
|
|
102
|
+
generate_openapi_code(host="http://localhost:5172")
|
|
103
|
+
print("[green]OpenAPI code generated successfully.[/green]")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from rich import print
|
|
2
|
+
from .api import _verify_app, codegen
|
|
3
|
+
from .node import build as build_node
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def build(
|
|
8
|
+
app: str = "main.app",
|
|
9
|
+
):
|
|
10
|
+
_verify_app(app)
|
|
11
|
+
dist_dir = f"{os.getcwd()}/dist"
|
|
12
|
+
print(f"Packaging {app}...")
|
|
13
|
+
|
|
14
|
+
# Codegen API
|
|
15
|
+
codegen(app)
|
|
16
|
+
# Build node app
|
|
17
|
+
build_node(dir=dist_dir)
|
|
18
|
+
|
|
19
|
+
print(
|
|
20
|
+
f"App packaged into {dist_dir}.\n"
|
|
21
|
+
f"Run [green]coloco serve[/green] to start the app in production mode."
|
|
22
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from aerich import Command
|
|
2
|
+
from .api import _verify_app
|
|
3
|
+
from ..app import ColocoApp, get_current_app
|
|
4
|
+
from asyncio import run
|
|
5
|
+
import functools
|
|
6
|
+
import os
|
|
7
|
+
from rich import print
|
|
8
|
+
from tortoise import Tortoise
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
app = typer.Typer()
|
|
12
|
+
|
|
13
|
+
def get_app():
|
|
14
|
+
_verify_app()
|
|
15
|
+
return get_current_app()
|
|
16
|
+
|
|
17
|
+
async def get_command(app: ColocoApp = None, init: bool = True):
|
|
18
|
+
app = app or get_app()
|
|
19
|
+
command = Command(
|
|
20
|
+
tortoise_config=app.orm_config,
|
|
21
|
+
app="models",
|
|
22
|
+
location=app.migrations_dir,
|
|
23
|
+
)
|
|
24
|
+
if init:
|
|
25
|
+
await command.init()
|
|
26
|
+
return command
|
|
27
|
+
|
|
28
|
+
def migrations_dir_exists(migrations_dir: str):
|
|
29
|
+
migrations_dir = os.path.join(migrations_dir, "models")
|
|
30
|
+
return os.path.exists(migrations_dir)
|
|
31
|
+
|
|
32
|
+
def ensure_migrations_dir(migrations_dir: str):
|
|
33
|
+
if not migrations_dir_exists(migrations_dir):
|
|
34
|
+
print("[red]Please run `coloco db init` first to initialize the database.[/red]")
|
|
35
|
+
raise typer.Exit(code=1)
|
|
36
|
+
|
|
37
|
+
def db_command(func):
|
|
38
|
+
@functools.wraps(func)
|
|
39
|
+
def wrapper(*args, **kwargs):
|
|
40
|
+
run(func(*args, **kwargs))
|
|
41
|
+
run(Tortoise.close_connections())
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
@app.command()
|
|
45
|
+
@db_command
|
|
46
|
+
async def makemigrations():
|
|
47
|
+
app = get_app()
|
|
48
|
+
ensure_migrations_dir(app.migrations_dir)
|
|
49
|
+
command = await get_command(app)
|
|
50
|
+
migrations = await command.migrate(name="update", empty=False)
|
|
51
|
+
if migrations:
|
|
52
|
+
print("[green]Migrations created successfully.[/green]")
|
|
53
|
+
else:
|
|
54
|
+
print("[gray]No database changes detected.[/gray]")
|
|
55
|
+
|
|
56
|
+
@app.command()
|
|
57
|
+
@db_command
|
|
58
|
+
async def migrate():
|
|
59
|
+
app = get_app()
|
|
60
|
+
ensure_migrations_dir(app.migrations_dir)
|
|
61
|
+
command = await get_command(app)
|
|
62
|
+
migrations = await command.upgrade(run_in_transaction=True)
|
|
63
|
+
if migrations:
|
|
64
|
+
print("[green]Database migrated successfully.[/green]")
|
|
65
|
+
else:
|
|
66
|
+
print("[gray]No database changes detected.[/gray]")
|
|
67
|
+
|
|
68
|
+
@app.command()
|
|
69
|
+
@db_command
|
|
70
|
+
async def init():
|
|
71
|
+
app = get_app()
|
|
72
|
+
if migrations_dir_exists(app.migrations_dir):
|
|
73
|
+
print("[red]Migrations directory already exists. You may want to run `coloco db migrate` instead.[/red]")
|
|
74
|
+
raise typer.Exit(code=1)
|
|
75
|
+
command = await get_command(app, init=False)
|
|
76
|
+
await command.init_db(safe=True)
|
|
77
|
+
print("[green]Database initialized successfully.[/green]")
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
@db_command
|
|
81
|
+
async def revert(version: str, fake: bool = False):
|
|
82
|
+
app = get_app()
|
|
83
|
+
ensure_migrations_dir(app.migrations_dir)
|
|
84
|
+
command = await get_command(app)
|
|
85
|
+
await command.downgrade(version=version, delete=False, fake=fake)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
@db_command
|
|
90
|
+
async def heads():
|
|
91
|
+
command = await get_command()
|
|
92
|
+
await command.heads()
|
|
93
|
+
|
|
94
|
+
@app.command()
|
|
95
|
+
@db_command
|
|
96
|
+
async def history():
|
|
97
|
+
command = await get_command()
|
|
98
|
+
await command.history()
|
|
99
|
+
|
|
100
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .api import _verify_app, _serve
|
|
2
|
-
from .node import install
|
|
2
|
+
from .node import install, _setup_dev_env
|
|
3
3
|
import os
|
|
4
4
|
from rich import print
|
|
5
5
|
from subprocess import Popen
|
|
@@ -12,14 +12,16 @@ def dev(app: str = "main.app", host: str = "127.0.0.1"):
|
|
|
12
12
|
if not os.path.exists(os.path.join(os.getcwd(), "node_modules")):
|
|
13
13
|
print("[yellow]Node modules not found, installing...[/yellow]")
|
|
14
14
|
install()
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
_setup_dev_env()
|
|
16
17
|
node = Popen([f"npm run dev"], cwd="+node", shell=True)
|
|
17
18
|
_serve(
|
|
18
19
|
app=app,
|
|
19
20
|
host=host,
|
|
20
21
|
port=5172,
|
|
21
|
-
reload=True,
|
|
22
22
|
log_level="debug",
|
|
23
|
+
mode="dev",
|
|
24
|
+
reload=True,
|
|
23
25
|
)
|
|
24
26
|
node.terminate()
|
|
25
27
|
node.wait()
|
|
@@ -7,10 +7,7 @@ import subprocess
|
|
|
7
7
|
app = typer.Typer()
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
def install():
|
|
12
|
-
"""Installs node dependencies for the project"""
|
|
13
|
-
|
|
10
|
+
def _run_npm(command):
|
|
14
11
|
# if not exists +node/package.json, raise error
|
|
15
12
|
if not os.path.exists("+node/package.json"):
|
|
16
13
|
print(
|
|
@@ -25,13 +22,13 @@ def install():
|
|
|
25
22
|
|
|
26
23
|
try:
|
|
27
24
|
# run npm install
|
|
28
|
-
subprocess.run(
|
|
25
|
+
subprocess.run(command, cwd=".")
|
|
29
26
|
|
|
30
27
|
# move package.json and package-lock.json back to +node
|
|
31
28
|
shutil.move("package.json", "+node/package.json")
|
|
32
29
|
shutil.move("package-lock.json", "+node/package-lock.json")
|
|
33
30
|
except Exception as e:
|
|
34
|
-
print(f"[red]Error
|
|
31
|
+
print(f"[red]Error: {e}[/red]")
|
|
35
32
|
try:
|
|
36
33
|
os.remove("package.json")
|
|
37
34
|
os.remove("package-lock.json")
|
|
@@ -39,11 +36,49 @@ def install():
|
|
|
39
36
|
pass
|
|
40
37
|
raise typer.Abort()
|
|
41
38
|
|
|
39
|
+
|
|
40
|
+
def _setup_dev_env():
|
|
41
|
+
os.environ["API_HOST"] = "http://localhost:5172"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command()
|
|
45
|
+
def install():
|
|
46
|
+
"""Installs node dependencies for the project"""
|
|
47
|
+
|
|
48
|
+
_run_npm(["npm", "install"])
|
|
49
|
+
|
|
42
50
|
print("[green]Packages installed successfully.[/green]")
|
|
43
51
|
|
|
44
52
|
|
|
53
|
+
@app.command()
|
|
54
|
+
def add(package: str):
|
|
55
|
+
"""Adds a node dependency to the project"""
|
|
56
|
+
|
|
57
|
+
_run_npm(["npm", "add", "-D", package])
|
|
58
|
+
|
|
59
|
+
print("[green]Package added successfully.[/green]")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command()
|
|
63
|
+
def link(package: str):
|
|
64
|
+
"""Links a node dependency to the project"""
|
|
65
|
+
|
|
66
|
+
_run_npm(["npm", "link", package])
|
|
67
|
+
|
|
68
|
+
print("[green]Package linked successfully.[/green]")
|
|
69
|
+
|
|
70
|
+
|
|
45
71
|
@app.command()
|
|
46
72
|
def dev():
|
|
47
73
|
"""Runs the node dev server"""
|
|
48
74
|
print("[green]Running node dev server...[/green]")
|
|
75
|
+
_setup_dev_env()
|
|
49
76
|
subprocess.run(["npm", "run", "dev"], cwd="+node")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def build(dir: str = None):
|
|
81
|
+
"""Runs the node dev server"""
|
|
82
|
+
print("[green]Building node app...[/green]")
|
|
83
|
+
|
|
84
|
+
subprocess.run(["npm", "run", "build"], cwd="+node")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from rich import print
|
|
2
|
+
from .api import _serve
|
|
3
|
+
import os
|
|
4
|
+
import typer
|
|
5
|
+
import uvicorn
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def serve(
|
|
9
|
+
app: str = "main.app",
|
|
10
|
+
port: int = 80,
|
|
11
|
+
host: str = "0.0.0.0",
|
|
12
|
+
log_level: str = "info",
|
|
13
|
+
):
|
|
14
|
+
return _serve(app=app, host=host, port=port, log_level=log_level, mode="prod")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from rich import print
|
|
3
|
+
|
|
4
|
+
def get_orm_config(database_url: str, model_files: list[str]):
|
|
5
|
+
return {
|
|
6
|
+
"connections": {
|
|
7
|
+
"default": database_url
|
|
8
|
+
},
|
|
9
|
+
"apps": {
|
|
10
|
+
"models": {
|
|
11
|
+
"models": [
|
|
12
|
+
*[model_file.replace("./", "").replace("/", ".").replace(".py", "") for model_file in model_files],
|
|
13
|
+
"aerich.models",
|
|
14
|
+
],
|
|
15
|
+
"default_connection": "default",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@asynccontextmanager
|
|
21
|
+
async def lifecycle_connect_database(api):
|
|
22
|
+
from .app import get_current_app
|
|
23
|
+
app = get_current_app()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from tortoise import Tortoise
|
|
27
|
+
except ImportError:
|
|
28
|
+
print("[red]Tortoise is not installed. "
|
|
29
|
+
"Please install it with `pip install tortoise-orm`. "
|
|
30
|
+
"If you intend to use anything other than sqlite, "
|
|
31
|
+
"you will need to install the appropriate database driver as well "
|
|
32
|
+
"(e.g. `pip install tortoise-orm[asyncpg]` for postgres).[/red]")
|
|
33
|
+
raise
|
|
34
|
+
print("[green]Connecting to database...[/green]")
|
|
35
|
+
await Tortoise.init(config=app.orm_config)
|
|
36
|
+
print("[green]Database ready[/green]")
|
|
37
|
+
yield
|
|
38
|
+
print("[yellow]Closing database connection...[/yellow]")
|
|
39
|
+
await Tortoise.close_connections()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from fastapi import FastAPI
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
from fastapi.requests import Request
|
|
4
|
+
from traceback import format_list, extract_tb
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class UserError(Exception):
|
|
@@ -16,17 +17,24 @@ class ServerError(Exception):
|
|
|
16
17
|
pass
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def
|
|
20
|
+
def _get_error_json(exception: Exception, debug: bool = False):
|
|
21
|
+
error = {"name": getattr(exception, "code", "api_error"), "message": f"{exception}"}
|
|
22
|
+
if debug:
|
|
23
|
+
error["stack"] = format_list(extract_tb(exception.__traceback__))
|
|
24
|
+
return error
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def bind_exceptions(api: FastAPI, debug: bool = False):
|
|
20
28
|
@api.exception_handler(UserError)
|
|
21
29
|
async def user_error_handler(request: Request, exc: UserError):
|
|
22
30
|
return JSONResponse(
|
|
23
31
|
status_code=exc.status_code,
|
|
24
|
-
content=
|
|
32
|
+
content=_get_error_json(exc, debug=debug),
|
|
25
33
|
)
|
|
26
34
|
|
|
27
35
|
@api.exception_handler(Exception)
|
|
28
36
|
async def exception_handler(request: Request, exc: Exception):
|
|
29
37
|
return JSONResponse(
|
|
30
38
|
status_code=500,
|
|
31
|
-
content=
|
|
39
|
+
content=_get_error_json(exc, debug=debug),
|
|
32
40
|
)
|
|
@@ -14,8 +14,10 @@ async def execute_lifespan(app: FastAPI):
|
|
|
14
14
|
contexts.append(generator)
|
|
15
15
|
if hasattr(generator, "__aenter__"):
|
|
16
16
|
await generator.__aenter__()
|
|
17
|
-
|
|
17
|
+
elif hasattr(generator, "__enter__"):
|
|
18
18
|
next(generator)
|
|
19
|
+
else:
|
|
20
|
+
raise ValueError(f"Invalid lifespan handler: {lifespan_handler}. Must be an sync/async context manager.")
|
|
19
21
|
|
|
20
22
|
yield
|
|
21
23
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.responses import FileResponse
|
|
3
|
+
from fastapi.staticfiles import StaticFiles
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Static Files
|
|
7
|
+
def bind_static(api: FastAPI, dist_dir: str = "dist"):
|
|
8
|
+
api.mount("/assets", StaticFiles(directory=f"{dist_dir}/app/assets"), name="static")
|
|
9
|
+
|
|
10
|
+
@api.get("/{all_paths:path}")
|
|
11
|
+
async def serve_app(all_paths: str):
|
|
12
|
+
return FileResponse(f"{dist_dir}/app/index.html")
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { makeClient, setDefaultClient } from "@coloco/api-client-svelte";
|
|
3
3
|
import { route, Router, getRoutes } from "@coloco/router";
|
|
4
4
|
|
|
5
5
|
import Index from "./index.svelte";
|
|
6
6
|
import NotFound from "./404.svelte";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
baseUrl: "http://localhost:5172",
|
|
10
|
-
});
|
|
8
|
+
const client = makeClient({ baseUrl: import.meta.env.API_HOST });
|
|
11
9
|
const routes = getRoutes({
|
|
12
10
|
index: Index,
|
|
13
11
|
notFound: NotFound,
|
|
14
12
|
});
|
|
13
|
+
|
|
14
|
+
setDefaultClient(client);
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
17
|
<main>
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@coloco/api-client-svelte": "^0.
|
|
14
|
-
"@coloco/router": "^0.1.
|
|
13
|
+
"@coloco/api-client-svelte": "^0.3.0",
|
|
14
|
+
"@coloco/router": "^0.1.5",
|
|
15
15
|
"@hey-api/client-fetch": "^0.8.2",
|
|
16
16
|
"@hey-api/openapi-ts": "^0.64.7",
|
|
17
17
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
|
@@ -14,6 +14,10 @@ export default defineConfig({
|
|
|
14
14
|
host: "0.0.0.0",
|
|
15
15
|
port: 5173,
|
|
16
16
|
},
|
|
17
|
+
build: {
|
|
18
|
+
outDir: '../dist/app',
|
|
19
|
+
emptyOutDir: true, // also necessary
|
|
20
|
+
},
|
|
17
21
|
clearScreen: false,
|
|
18
22
|
resolve: {
|
|
19
23
|
// @ts-ignore
|
|
@@ -29,6 +33,16 @@ export default defineConfig({
|
|
|
29
33
|
{
|
|
30
34
|
find: '@api',
|
|
31
35
|
replacement: '/.generated/client/api'
|
|
36
|
+
},
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
{
|
|
39
|
+
find: '@app/',
|
|
40
|
+
replacement: '/'
|
|
41
|
+
},
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
{
|
|
44
|
+
find: '@root',
|
|
45
|
+
replacement: '/..'
|
|
32
46
|
}]
|
|
33
47
|
}
|
|
34
48
|
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{ project_name }}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Your project description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"coloco>=0.2.0",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[tool.coloco]
|
|
12
|
+
app = "main.app"
|
|
13
|
+
migrations_dir = "./+migrations"
|
coloco-0.2.0/README.md
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Coloco
|
|
2
|
-
|
|
3
|
-
A kit for creating full-stack apps with co-located code, built on FastAPI and Svelte. Bundle your front-end and back-end code and easily tie them together with codegen.
|
|
4
|
-
|
|
5
|
-
Example:
|
|
6
|
-
|
|
7
|
-
`hello/api.py`
|
|
8
|
-
```python
|
|
9
|
-
from coloco import api
|
|
10
|
-
|
|
11
|
-
@api
|
|
12
|
-
def test(name: str) -> str:
|
|
13
|
-
return f"Hello {name}!"
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`hello/index.svelte`
|
|
18
|
-
```svelte
|
|
19
|
-
<script lang="ts">
|
|
20
|
-
import { test } from "./api";
|
|
21
|
-
|
|
22
|
-
const response = test({ query: { name: "Coloco" } });
|
|
23
|
-
</script>
|
|
24
|
-
|
|
25
|
-
{#if $response.loading}
|
|
26
|
-
Loading...
|
|
27
|
-
{:else}
|
|
28
|
-
The server says {$response.data}
|
|
29
|
-
{/if}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=Coloco` and prints the message `Hello Coloco!`
|
|
33
|
-
|
|
34
|
-
# Opinions
|
|
35
|
-
|
|
36
|
-
This framework is opinionated and combines the following excellent tools:
|
|
37
|
-
* FastAPI
|
|
38
|
-
* Svelte
|
|
39
|
-
* openapi-ts (codegen)
|
|
40
|
-
* svelte5-router (file-based routing)
|
|
41
|
-
* tortoise-orm (optional)
|
|
42
|
-
|
|
43
|
-
# Plans
|
|
44
|
-
* Deploy tools
|
|
45
|
-
* ORM Support
|
|
46
|
-
* SQLite/Postgres support
|
|
47
|
-
|
|
48
|
-
# Dreams
|
|
49
|
-
* Move `node_modules` into `+node`
|
|
50
|
-
* Remove the need for `query`, `params`, `body` for simple REST requests (maybe use legacy client but support the new one?)
|
coloco-0.2.0/src/coloco/app.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
from .api import create_api, global_router
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from fastapi import FastAPI
|
|
4
|
-
from importlib import import_module
|
|
5
|
-
import os
|
|
6
|
-
from rich import print
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class ColocoApp:
|
|
11
|
-
api: FastAPI
|
|
12
|
-
name: str
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import os
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def find_api_files(directory):
|
|
19
|
-
api_files = []
|
|
20
|
-
try:
|
|
21
|
-
with os.scandir(directory) as entries:
|
|
22
|
-
for entry in entries:
|
|
23
|
-
if entry.is_dir():
|
|
24
|
-
# Skip directories starting with "+" and "node_modules"
|
|
25
|
-
if (
|
|
26
|
-
not entry.name.startswith("+")
|
|
27
|
-
and not entry.name == "node_modules"
|
|
28
|
-
and not entry.name == "coloco"
|
|
29
|
-
):
|
|
30
|
-
api_files.extend(find_api_files(entry.path))
|
|
31
|
-
elif entry.is_file() and entry.name == "api.py":
|
|
32
|
-
api_files.append(entry.path)
|
|
33
|
-
except (PermissionError, FileNotFoundError) as e:
|
|
34
|
-
print(f"Error accessing {directory}: {e}")
|
|
35
|
-
return api_files
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def create_app(name: str):
|
|
39
|
-
api = create_api(is_dev=True)
|
|
40
|
-
|
|
41
|
-
# Discover all api.py files from root, excluding node_modules and +app
|
|
42
|
-
api_files = find_api_files(".")
|
|
43
|
-
for api_file in api_files:
|
|
44
|
-
# convert python file path to module path
|
|
45
|
-
module_name = api_file.replace("./", "").replace(".py", "").replace("/", ".")
|
|
46
|
-
try:
|
|
47
|
-
module = import_module(module_name)
|
|
48
|
-
except Exception as e:
|
|
49
|
-
print(f"[red]Error importing '{api_file}': {e}[/red]")
|
|
50
|
-
continue
|
|
51
|
-
# if not hasattr(module, "router"):
|
|
52
|
-
# print(f"[red]Module '{api_file}' has no router[/red]")
|
|
53
|
-
# continue
|
|
54
|
-
# api.include_router(module.router)
|
|
55
|
-
|
|
56
|
-
api.include_router(global_router)
|
|
57
|
-
|
|
58
|
-
return ColocoApp(api=api, name=name)
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from fastapi import FastAPI
|
|
2
|
-
from fastapi.responses import FileResponse
|
|
3
|
-
from fastapi.staticfiles import StaticFiles
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# Static Files
|
|
7
|
-
def bind_static(api: FastAPI):
|
|
8
|
-
api.mount("/static", StaticFiles(directory="static"), name="static")
|
|
9
|
-
|
|
10
|
-
@api.get("/")
|
|
11
|
-
def index():
|
|
12
|
-
return FileResponse("static/index.html")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
coloco
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|