coloco 0.3.3__tar.gz → 0.3.5__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.3.3 → coloco-0.3.5}/PKG-INFO +14 -12
- {coloco-0.3.3 → coloco-0.3.5}/README.md +6 -4
- {coloco-0.3.3 → coloco-0.3.5}/pyproject.toml +8 -8
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/__init__.py +6 -2
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/api.py +6 -1
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/app.py +12 -8
- coloco-0.3.5/src/coloco/cli/db.py +252 -0
- coloco-0.3.5/src/coloco/db.py +80 -0
- coloco-0.3.5/src/coloco/dependencies.py +6 -0
- coloco-0.3.3/src/coloco/cli/db.py +0 -100
- coloco-0.3.3/src/coloco/db.py +0 -39
- {coloco-0.3.3 → coloco-0.3.5}/LICENSE +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/__main__.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/api.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/build.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/createapp.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/dev.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/node.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/package.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/cli/serve.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/codegen.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/config.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/exceptions.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/lifespan.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/static.py +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/docker/.dockerignore-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/docker/Dockerfile-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/docker/docker-compose.yml-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/404.svelte-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/app.css-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/app.svelte-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/index.html-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/index.svelte-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+app/main.ts-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/openapi-ts.config.ts-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/package.json-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/svelte.config.js-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/tsconfig.json-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/vite-env.d.ts-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/+node/vite.config.ts-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/.gitignore-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/README.md-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/example/api.py-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/example/index.svelte-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/main.py-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/pyproject.toml-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/src/coloco/templates/standard/tsconfig.json-tpl +0 -0
- {coloco-0.3.3 → coloco-0.3.5}/tests/__init__.py +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: coloco
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A kit for creating FastAPI + Svelte applications
|
|
5
5
|
Author-Email: Channel Cat <channelcat@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.11
|
|
8
|
-
Requires-Dist: aerich[toml]
|
|
9
|
-
Requires-Dist: fastapi
|
|
10
|
-
Requires-Dist: PyJWT
|
|
11
|
-
Requires-Dist: tortoise-orm[asyncpg]
|
|
12
|
-
Requires-Dist: type-less
|
|
13
|
-
Requires-Dist: typer
|
|
14
|
-
Requires-Dist: uvicorn
|
|
8
|
+
Requires-Dist: aerich[toml]>=0.8.1
|
|
9
|
+
Requires-Dist: fastapi>=0.115.0
|
|
10
|
+
Requires-Dist: PyJWT>=2.10.1
|
|
11
|
+
Requires-Dist: tortoise-orm[asyncpg]>=0.24.0
|
|
12
|
+
Requires-Dist: type-less>=0.1.8
|
|
13
|
+
Requires-Dist: typer>=0.15.2
|
|
14
|
+
Requires-Dist: uvicorn>=0.31.0
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
17
|
# Coloco
|
|
@@ -56,8 +56,8 @@ Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=Coloco
|
|
|
56
56
|
# Running in Production
|
|
57
57
|
|
|
58
58
|
- `coloco build`
|
|
59
|
-
|
|
60
|
-
- `coloco serve`
|
|
59
|
+
- Artifacts will be saved to `dist`
|
|
60
|
+
- From dist, run `coloco serve`
|
|
61
61
|
|
|
62
62
|
# Opinions
|
|
63
63
|
|
|
@@ -72,9 +72,11 @@ This framework is opinionated and combines the following excellent tools:
|
|
|
72
72
|
# Plans
|
|
73
73
|
|
|
74
74
|
- Deploy tools
|
|
75
|
-
- ORM
|
|
76
|
-
-
|
|
75
|
+
- Migrations for ORM - `aerich` (Improvements needed, watching `tortoise-pathway`)
|
|
76
|
+
- CRUD
|
|
77
|
+
- Caching
|
|
77
78
|
- Package/share modules with git
|
|
79
|
+
-
|
|
78
80
|
|
|
79
81
|
# Dreams
|
|
80
82
|
|
|
@@ -40,8 +40,8 @@ Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=Coloco
|
|
|
40
40
|
# Running in Production
|
|
41
41
|
|
|
42
42
|
- `coloco build`
|
|
43
|
-
|
|
44
|
-
- `coloco serve`
|
|
43
|
+
- Artifacts will be saved to `dist`
|
|
44
|
+
- From dist, run `coloco serve`
|
|
45
45
|
|
|
46
46
|
# Opinions
|
|
47
47
|
|
|
@@ -56,9 +56,11 @@ This framework is opinionated and combines the following excellent tools:
|
|
|
56
56
|
# Plans
|
|
57
57
|
|
|
58
58
|
- Deploy tools
|
|
59
|
-
- ORM
|
|
60
|
-
-
|
|
59
|
+
- Migrations for ORM - `aerich` (Improvements needed, watching `tortoise-pathway`)
|
|
60
|
+
- CRUD
|
|
61
|
+
- Caching
|
|
61
62
|
- Package/share modules with git
|
|
63
|
+
-
|
|
62
64
|
|
|
63
65
|
# Dreams
|
|
64
66
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coloco"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5"
|
|
4
4
|
description = "A kit for creating FastAPI + Svelte applications"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Channel Cat", email = "channelcat@gmail.com" },
|
|
7
7
|
]
|
|
8
8
|
dependencies = [
|
|
9
|
-
"aerich[toml]
|
|
10
|
-
"fastapi
|
|
11
|
-
"PyJWT
|
|
12
|
-
"tortoise-orm[asyncpg]
|
|
13
|
-
"type-less
|
|
14
|
-
"typer
|
|
15
|
-
"uvicorn
|
|
9
|
+
"aerich[toml]>=0.8.1",
|
|
10
|
+
"fastapi>=0.115.0",
|
|
11
|
+
"PyJWT>=2.10.1",
|
|
12
|
+
"tortoise-orm[asyncpg]>=0.24.0",
|
|
13
|
+
"type-less>=0.1.8",
|
|
14
|
+
"typer>=0.15.2",
|
|
15
|
+
"uvicorn>=0.31.0",
|
|
16
16
|
]
|
|
17
17
|
requires-python = ">=3.11"
|
|
18
18
|
readme = "README.md"
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .api import api
|
|
2
|
+
from .app import ColocoApp, create_app, get_current_app
|
|
2
3
|
from .codegen import generate_openapi_schema, generate_openapi_code
|
|
3
4
|
from .exceptions import UserError
|
|
4
|
-
from .
|
|
5
|
+
from . import dependencies
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"api",
|
|
9
|
+
"ColocoApp",
|
|
8
10
|
"create_app",
|
|
11
|
+
"dependencies",
|
|
9
12
|
"generate_openapi_schema",
|
|
10
13
|
"generate_openapi_code",
|
|
14
|
+
"get_current_app",
|
|
11
15
|
"UserError",
|
|
12
16
|
]
|
|
@@ -18,6 +18,8 @@ logging.basicConfig(level=logging.INFO)
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _generate_openapi_thread(app: FastAPI):
|
|
21
|
+
# TODO: diff check the schema json
|
|
22
|
+
# TODO: diff check not working
|
|
21
23
|
logging.info("Generating OpenAPI schema in thread...")
|
|
22
24
|
generate_openapi_schema(app)
|
|
23
25
|
generate_openapi_code(host=f"http://localhost:5172", diff_files=True)
|
|
@@ -87,7 +89,10 @@ def _add_global_route(args, kwargs, func, method: str):
|
|
|
87
89
|
# TODO: Make this configurable
|
|
88
90
|
"/api/"
|
|
89
91
|
# TODO: Make this read project configuration, probably need to add routes after running
|
|
90
|
-
+ module_name.rsplit(".", 1)[0]
|
|
92
|
+
+ module_name.rsplit(".", 1)[0]
|
|
93
|
+
.replace(".", "/")
|
|
94
|
+
.replace(".-", ".") # Strip - from folders (for dev only)
|
|
95
|
+
.lstrip("-")
|
|
91
96
|
+ ("" if path.startswith("/") else "/")
|
|
92
97
|
+ path
|
|
93
98
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .api import create_api, global_router
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from .db import get_orm_config,
|
|
3
|
+
from .db import get_orm_config, create_lifecycle_connect_database
|
|
4
4
|
from fastapi import FastAPI
|
|
5
5
|
from importlib import import_module
|
|
6
6
|
from .lifespan import register_lifespan
|
|
@@ -17,10 +17,10 @@ class ColocoApp:
|
|
|
17
17
|
name: str
|
|
18
18
|
database_url: str = None
|
|
19
19
|
orm_config: dict = None
|
|
20
|
-
migrations_dir: str = "
|
|
20
|
+
migrations_dir: str = "+migrations"
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def discover_files(directory, name):
|
|
23
|
+
def discover_files(directory, name, is_dev=False):
|
|
24
24
|
api_files = []
|
|
25
25
|
try:
|
|
26
26
|
with os.scandir(directory) as entries:
|
|
@@ -29,12 +29,12 @@ def discover_files(directory, name):
|
|
|
29
29
|
# Skip directories starting with "+" and "node_modules"
|
|
30
30
|
if (
|
|
31
31
|
not entry.name.startswith("+")
|
|
32
|
-
and not entry.name.startswith("-")
|
|
32
|
+
and (not entry.name.startswith("-") or is_dev)
|
|
33
33
|
and not entry.name.startswith(".")
|
|
34
34
|
and not entry.name == "node_modules"
|
|
35
35
|
and not entry.name == "coloco"
|
|
36
36
|
):
|
|
37
|
-
api_files.extend(discover_files(entry.path, name))
|
|
37
|
+
api_files.extend(discover_files(entry.path, name, is_dev))
|
|
38
38
|
elif entry.is_file() and entry.name == name:
|
|
39
39
|
api_files.append(entry.path)
|
|
40
40
|
except (PermissionError, FileNotFoundError) as e:
|
|
@@ -54,7 +54,7 @@ def create_app(name: str, database_url: str = None) -> ColocoApp:
|
|
|
54
54
|
api = create_api(is_dev=mode == "dev")
|
|
55
55
|
|
|
56
56
|
# Discover all api.py files from root, excluding node_modules and +app
|
|
57
|
-
api_files = discover_files(".", name="api.py")
|
|
57
|
+
api_files = discover_files(".", name="api.py", is_dev=mode == "dev")
|
|
58
58
|
for api_file in api_files:
|
|
59
59
|
# convert python file path to module path
|
|
60
60
|
module_name = api_file.replace("./", "").replace(".py", "").replace("/", ".")
|
|
@@ -74,15 +74,19 @@ def create_app(name: str, database_url: str = None) -> ColocoApp:
|
|
|
74
74
|
# Setup Database
|
|
75
75
|
if database_url:
|
|
76
76
|
orm_config = get_orm_config(
|
|
77
|
-
database_url,
|
|
77
|
+
database_url,
|
|
78
|
+
model_files=discover_files(".", name="models.py", is_dev=mode == "dev"),
|
|
78
79
|
)
|
|
79
|
-
register_lifespan(lifecycle_connect_database)
|
|
80
80
|
else:
|
|
81
81
|
orm_config = None
|
|
82
82
|
|
|
83
83
|
CURRENT_APP = ColocoApp(
|
|
84
84
|
api=api, name=name, database_url=database_url, orm_config=orm_config
|
|
85
85
|
)
|
|
86
|
+
|
|
87
|
+
if database_url:
|
|
88
|
+
register_lifespan(create_lifecycle_connect_database(CURRENT_APP))
|
|
89
|
+
|
|
86
90
|
return CURRENT_APP
|
|
87
91
|
|
|
88
92
|
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import aerich
|
|
2
|
+
import aerich.migrate
|
|
3
|
+
|
|
4
|
+
from aerich import Command
|
|
5
|
+
from aerich.models import Aerich
|
|
6
|
+
from aerich.migrate import Migrate
|
|
7
|
+
from aerich.utils import get_app_connection, get_models_describe
|
|
8
|
+
from .api import _verify_app
|
|
9
|
+
from ..app import ColocoApp, get_current_app
|
|
10
|
+
from ..db import app_class_to_table_name
|
|
11
|
+
from asyncio import run
|
|
12
|
+
import functools
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from rich import print
|
|
16
|
+
|
|
17
|
+
from tortoise import generate_schema_for_client, Tortoise
|
|
18
|
+
from tortoise.exceptions import OperationalError
|
|
19
|
+
import typer
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
app = typer.Typer()
|
|
23
|
+
|
|
24
|
+
# ----------------------------- Monkey Patching -----------------------------
|
|
25
|
+
# TODO: Propose a patch to aerich to make migration location more configurable
|
|
26
|
+
_monkey_patched = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def patch_migration_location(migrations_dir: str):
|
|
30
|
+
global _monkey_patched
|
|
31
|
+
if not _monkey_patched:
|
|
32
|
+
|
|
33
|
+
def patched_path(*args, **kwargs):
|
|
34
|
+
if len(args) == 2 and args[0] == migrations_dir:
|
|
35
|
+
return Path("src", "app", args[1], args[0], **kwargs)
|
|
36
|
+
else:
|
|
37
|
+
return Path(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
aerich.Path = patched_path
|
|
40
|
+
aerich.migrate.Path = patched_path
|
|
41
|
+
|
|
42
|
+
_monkey_patched = True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ----------------------------- /Monkey Patching -----------------------------
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_coloco_app():
|
|
49
|
+
_verify_app()
|
|
50
|
+
return get_current_app()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_model_apps(coloco_app: ColocoApp):
|
|
54
|
+
return [app for app in coloco_app.orm_config["apps"] if app != "models"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def get_command(
|
|
58
|
+
app: str = "models", coloco_app: ColocoApp = None, init: bool = True
|
|
59
|
+
):
|
|
60
|
+
patch_migration_location(coloco_app.migrations_dir)
|
|
61
|
+
coloco_app = coloco_app or get_coloco_app()
|
|
62
|
+
command = Command(
|
|
63
|
+
tortoise_config=coloco_app.orm_config,
|
|
64
|
+
app=app,
|
|
65
|
+
location=coloco_app.migrations_dir,
|
|
66
|
+
)
|
|
67
|
+
if init:
|
|
68
|
+
await command.init()
|
|
69
|
+
# TODO: propose a better way to do this
|
|
70
|
+
Tortoise.table_name_generator = app_class_to_table_name
|
|
71
|
+
return command
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_app_migrations_path(app, migrations_dir: str) -> Path:
|
|
75
|
+
return Path(os.path.join("src", "app", app, migrations_dir))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def ensure_app_migrations_dir(app, migrations_dir: str) -> bool:
|
|
79
|
+
app_migrations_path = get_app_migrations_path(app, migrations_dir)
|
|
80
|
+
if not app_migrations_path.exists():
|
|
81
|
+
app_migrations_path.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
async def has_migrations_inited(app: str, coloco_app: ColocoApp) -> bool:
|
|
87
|
+
try:
|
|
88
|
+
any_migrations = await Aerich.filter(app=app).first()
|
|
89
|
+
return bool(any_migrations)
|
|
90
|
+
except OperationalError as e:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class InitTortoise(Tortoise):
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def ensure_migrations_init(coloco_app: ColocoApp, safe=False):
|
|
99
|
+
"""
|
|
100
|
+
Ensure that we have all initial migrations set up
|
|
101
|
+
Iterates through apps and creates a blank initial migration.
|
|
102
|
+
Aerich table is created under the app "models" and has the table initialized.
|
|
103
|
+
"""
|
|
104
|
+
app = "models"
|
|
105
|
+
|
|
106
|
+
await Tortoise.init(
|
|
107
|
+
config=coloco_app.orm_config, table_name_generator=app_class_to_table_name
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Ensure we create aerich first
|
|
111
|
+
app_list = ["models", *get_model_apps(coloco_app)]
|
|
112
|
+
for app in app_list:
|
|
113
|
+
has_migrations = await has_migrations_inited(app, coloco_app)
|
|
114
|
+
if not has_migrations:
|
|
115
|
+
all_apps = Tortoise.apps
|
|
116
|
+
single_app = {app: all_apps[app]} if app == "models" else {app: {}}
|
|
117
|
+
print(
|
|
118
|
+
f" |- Initializing migrations for [yellow]{app}...[/yellow]",
|
|
119
|
+
end="",
|
|
120
|
+
flush=True,
|
|
121
|
+
)
|
|
122
|
+
connection = get_app_connection(coloco_app.orm_config, app)
|
|
123
|
+
|
|
124
|
+
Tortoise.apps = single_app
|
|
125
|
+
|
|
126
|
+
# Create initial aerich table
|
|
127
|
+
if app == "models":
|
|
128
|
+
await generate_schema_for_client(connection, safe)
|
|
129
|
+
|
|
130
|
+
Migrate.app = app
|
|
131
|
+
version = await Migrate.generate_version()
|
|
132
|
+
await Aerich.create(
|
|
133
|
+
version=version, app=app, content=get_models_describe(app)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
Tortoise.apps = all_apps
|
|
137
|
+
|
|
138
|
+
print("[green]OK[/green]")
|
|
139
|
+
|
|
140
|
+
return has_migrations
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def db_command(func):
|
|
144
|
+
@functools.wraps(func)
|
|
145
|
+
def wrapper(*args, **kwargs):
|
|
146
|
+
run(func(*args, **kwargs))
|
|
147
|
+
run(Tortoise.close_connections())
|
|
148
|
+
|
|
149
|
+
return wrapper
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ----------------------------- Commands -----------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command()
|
|
156
|
+
@db_command
|
|
157
|
+
async def makemigrations(app: str = None):
|
|
158
|
+
coloco_app = get_coloco_app()
|
|
159
|
+
apps = [app] if app else get_model_apps(coloco_app)
|
|
160
|
+
|
|
161
|
+
await ensure_migrations_init(coloco_app)
|
|
162
|
+
|
|
163
|
+
total = 0
|
|
164
|
+
print("[yellow]Making migrations for all apps...[/yellow]")
|
|
165
|
+
for app in apps:
|
|
166
|
+
print(f" |- [yellow]{app}[/yellow] - ", end="", flush=True)
|
|
167
|
+
ensure_app_migrations_dir(app, coloco_app.migrations_dir)
|
|
168
|
+
command = await get_command(app, coloco_app)
|
|
169
|
+
|
|
170
|
+
# TODO: ensure no unran migrations exist - they will be overrwitten
|
|
171
|
+
|
|
172
|
+
# TODO: submit patch for aerich migration cleanup
|
|
173
|
+
Migrate.upgrade_operators = []
|
|
174
|
+
Migrate.downgrade_operators = []
|
|
175
|
+
|
|
176
|
+
# TODO: better default name?
|
|
177
|
+
migrations = await command.migrate(name="update", empty=False)
|
|
178
|
+
|
|
179
|
+
if migrations:
|
|
180
|
+
total += 1
|
|
181
|
+
print("[green]changes detected[/green]")
|
|
182
|
+
else:
|
|
183
|
+
print("[gray]no changes[/gray]")
|
|
184
|
+
|
|
185
|
+
if total:
|
|
186
|
+
print(f"[green]{total}[/green] migration{'s' if total > 1 else ''} created.")
|
|
187
|
+
else:
|
|
188
|
+
print("No changes found.")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.command()
|
|
192
|
+
@db_command
|
|
193
|
+
async def migrate(app: str = None):
|
|
194
|
+
coloco_app = get_coloco_app()
|
|
195
|
+
apps = [app] if app else get_model_apps(coloco_app)
|
|
196
|
+
|
|
197
|
+
await ensure_migrations_init(coloco_app)
|
|
198
|
+
|
|
199
|
+
# TODO: do we need to combine these together for proper handling of foreign key relationships?
|
|
200
|
+
# ex: app1.Table1 has a field that references app2.Table2 - does table2 need to be created first?
|
|
201
|
+
total = 0
|
|
202
|
+
print("[cyan]Running migrations for all apps...[/cyan]")
|
|
203
|
+
for app in apps:
|
|
204
|
+
print(f" |- [cyan]{app}[/cyan] - ", end="", flush=True)
|
|
205
|
+
ensure_app_migrations_dir(app, coloco_app.migrations_dir)
|
|
206
|
+
command = await get_command(app, coloco_app)
|
|
207
|
+
migrations = await command.upgrade(run_in_transaction=True)
|
|
208
|
+
if migrations:
|
|
209
|
+
total += 1
|
|
210
|
+
print("[green]migrated[/green]")
|
|
211
|
+
else:
|
|
212
|
+
print("no changes")
|
|
213
|
+
|
|
214
|
+
if total:
|
|
215
|
+
print(f"[green]{total} change{'s' if total > 1 else ''} applied.[/green]")
|
|
216
|
+
else:
|
|
217
|
+
print("No changes found.")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@app.command()
|
|
221
|
+
@db_command
|
|
222
|
+
async def init(app: str = None):
|
|
223
|
+
coloco_app = get_coloco_app()
|
|
224
|
+
has_migrations = await ensure_migrations_init(coloco_app)
|
|
225
|
+
if not has_migrations:
|
|
226
|
+
print("[green]Database initialized.[/green]")
|
|
227
|
+
else:
|
|
228
|
+
print("[green]Database already initialized.[/green]")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@app.command()
|
|
232
|
+
@db_command
|
|
233
|
+
async def revert(version: str, fake: bool = False):
|
|
234
|
+
# TODO: custom reversion based on app or timestamp?
|
|
235
|
+
# Reversion
|
|
236
|
+
raise typer.Abort("Not implemented yet")
|
|
237
|
+
command = await get_command(app)
|
|
238
|
+
await command.downgrade(version=version, delete=False, fake=fake)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@app.command()
|
|
242
|
+
@db_command
|
|
243
|
+
async def heads():
|
|
244
|
+
command = await get_command()
|
|
245
|
+
await command.heads()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@app.command()
|
|
249
|
+
@db_command
|
|
250
|
+
async def history():
|
|
251
|
+
command = await get_command()
|
|
252
|
+
await command.history()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from rich import print
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def app_class_to_table_name(cls):
|
|
7
|
+
try:
|
|
8
|
+
app_name = cls.__module__.split(".")[-2]
|
|
9
|
+
except IndexError:
|
|
10
|
+
raise ValueError(f"Could not determine app name for model {cls}")
|
|
11
|
+
return f"{app_name}_{cls.__name__.lower()}"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_orm_config(database_url: str, model_files: list[str]):
|
|
15
|
+
model_modules = [
|
|
16
|
+
model_file.replace("./", "").replace("/", ".").replace(".py", "")
|
|
17
|
+
for model_file in model_files
|
|
18
|
+
]
|
|
19
|
+
app_to_models = defaultdict(list)
|
|
20
|
+
for model_module in model_modules:
|
|
21
|
+
app = model_module.lstrip("src.app.").split(".")[0]
|
|
22
|
+
app_to_models[app].append(model_module)
|
|
23
|
+
return {
|
|
24
|
+
"connections": {"default": database_url},
|
|
25
|
+
"table_name_generator": app_class_to_table_name,
|
|
26
|
+
"apps": {
|
|
27
|
+
**{
|
|
28
|
+
app: {
|
|
29
|
+
"models": [
|
|
30
|
+
*models,
|
|
31
|
+
],
|
|
32
|
+
"default_connection": "default",
|
|
33
|
+
}
|
|
34
|
+
for app, models in app_to_models.items()
|
|
35
|
+
},
|
|
36
|
+
**{
|
|
37
|
+
"models": {
|
|
38
|
+
"models": [
|
|
39
|
+
"aerich.models",
|
|
40
|
+
],
|
|
41
|
+
"default_connection": "default",
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def init_tortoise(app):
|
|
49
|
+
try:
|
|
50
|
+
from tortoise import Tortoise
|
|
51
|
+
except ImportError:
|
|
52
|
+
print(
|
|
53
|
+
"[red]Tortoise is not installed. "
|
|
54
|
+
"Please install it with `pip install tortoise-orm`. "
|
|
55
|
+
"If you intend to use anything other than sqlite, "
|
|
56
|
+
"you will need to install the appropriate database driver as well "
|
|
57
|
+
"(e.g. `pip install tortoise-orm[asyncpg]` for postgres).[/red]"
|
|
58
|
+
)
|
|
59
|
+
raise
|
|
60
|
+
await Tortoise.init(
|
|
61
|
+
config=app.orm_config, table_name_generator=app_class_to_table_name
|
|
62
|
+
)
|
|
63
|
+
return Tortoise.close_connections
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def create_lifecycle_connect_database(app):
|
|
67
|
+
@asynccontextmanager
|
|
68
|
+
async def lifecycle_connect_database(api):
|
|
69
|
+
from .app import get_current_app
|
|
70
|
+
|
|
71
|
+
app = get_current_app()
|
|
72
|
+
|
|
73
|
+
print("[green]Connecting to database...[/green]")
|
|
74
|
+
close_connections = await init_tortoise(app)
|
|
75
|
+
print("[green]Database ready[/green]")
|
|
76
|
+
yield
|
|
77
|
+
print("[yellow]Closing database connection...[/yellow]")
|
|
78
|
+
await close_connections()
|
|
79
|
+
|
|
80
|
+
return lifecycle_connect_database
|
|
@@ -1,100 +0,0 @@
|
|
|
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
|
-
|
coloco-0.3.3/src/coloco/db.py
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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()
|
|
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
|
|
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
|