coloco 0.3.4__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.
Files changed (48) hide show
  1. {coloco-0.3.4 → coloco-0.3.5}/PKG-INFO +13 -11
  2. {coloco-0.3.4 → coloco-0.3.5}/README.md +6 -4
  3. {coloco-0.3.4 → coloco-0.3.5}/pyproject.toml +7 -7
  4. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/__init__.py +6 -2
  5. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/api.py +6 -1
  6. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/app.py +12 -8
  7. coloco-0.3.5/src/coloco/cli/db.py +252 -0
  8. coloco-0.3.5/src/coloco/db.py +80 -0
  9. coloco-0.3.5/src/coloco/dependencies.py +6 -0
  10. coloco-0.3.4/src/coloco/cli/db.py +0 -100
  11. coloco-0.3.4/src/coloco/db.py +0 -39
  12. {coloco-0.3.4 → coloco-0.3.5}/LICENSE +0 -0
  13. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/__main__.py +0 -0
  14. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/api.py +0 -0
  15. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/build.py +0 -0
  16. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/createapp.py +0 -0
  17. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/dev.py +0 -0
  18. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/node.py +0 -0
  19. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/package.py +0 -0
  20. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/cli/serve.py +0 -0
  21. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/codegen.py +0 -0
  22. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/config.py +0 -0
  23. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/exceptions.py +0 -0
  24. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/lifespan.py +0 -0
  25. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/static.py +0 -0
  26. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/docker/.dockerignore-tpl +0 -0
  27. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/docker/Dockerfile-tpl +0 -0
  28. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/docker/docker-compose.yml-tpl +0 -0
  29. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/404.svelte-tpl +0 -0
  30. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/app.css-tpl +0 -0
  31. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/app.svelte-tpl +0 -0
  32. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/index.html-tpl +0 -0
  33. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/index.svelte-tpl +0 -0
  34. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+app/main.ts-tpl +0 -0
  35. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/openapi-ts.config.ts-tpl +0 -0
  36. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/package.json-tpl +0 -0
  37. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/svelte.config.js-tpl +0 -0
  38. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/tsconfig.json-tpl +0 -0
  39. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/vite-env.d.ts-tpl +0 -0
  40. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/+node/vite.config.ts-tpl +0 -0
  41. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/.gitignore-tpl +0 -0
  42. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/README.md-tpl +0 -0
  43. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/example/api.py-tpl +0 -0
  44. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/example/index.svelte-tpl +0 -0
  45. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/main.py-tpl +0 -0
  46. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/pyproject.toml-tpl +0 -0
  47. {coloco-0.3.4 → coloco-0.3.5}/src/coloco/templates/standard/tsconfig.json-tpl +0 -0
  48. {coloco-0.3.4 → 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.4
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]==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
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
12
  Requires-Dist: type-less>=0.1.8
13
- Requires-Dist: typer==0.15.2
14
- Requires-Dist: uvicorn==0.31.0
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 Support
76
- - SQLite/Postgres support
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 Support
60
- - SQLite/Postgres support
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.4"
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]==0.8.1",
10
- "fastapi==0.115.0",
11
- "PyJWT==2.10.1",
12
- "tortoise-orm[asyncpg]==0.24.0",
9
+ "aerich[toml]>=0.8.1",
10
+ "fastapi>=0.115.0",
11
+ "PyJWT>=2.10.1",
12
+ "tortoise-orm[asyncpg]>=0.24.0",
13
13
  "type-less>=0.1.8",
14
- "typer==0.15.2",
15
- "uvicorn==0.31.0",
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 .app import create_app
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 .api import api
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].replace(".", "/")
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, lifecycle_connect_database
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 = "./+migrations"
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, model_files=discover_files(".", name="models.py")
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
@@ -0,0 +1,6 @@
1
+ from fastapi import Depends
2
+ from typing import Annotated
3
+
4
+ from .app import ColocoApp as ColocoAppType, get_current_app
5
+
6
+ ColocoApp = Annotated[ColocoAppType, Depends(get_current_app)]
@@ -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
-
@@ -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