vibetuner 2.14.1__py3-none-any.whl → 2.26.9__py3-none-any.whl
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.
Potentially problematic release.
This version of vibetuner might be problematic. Click here for more details.
- vibetuner/cli/__init__.py +65 -3
- vibetuner/cli/run.py +2 -2
- vibetuner/config.py +19 -9
- vibetuner/context.py +3 -0
- vibetuner/frontend/__init__.py +27 -5
- vibetuner/frontend/lifespan.py +18 -7
- vibetuner/frontend/middleware.py +2 -6
- vibetuner/frontend/routes/debug.py +1 -1
- vibetuner/frontend/routes/user.py +1 -1
- vibetuner/frontend/templates.py +1 -3
- vibetuner/mongo.py +18 -3
- vibetuner/paths.py +31 -10
- vibetuner/tasks/__init__.py +0 -2
- vibetuner/tasks/lifespan.py +28 -0
- vibetuner/tasks/worker.py +2 -5
- vibetuner/templates/email/{default/magic_link.html.jinja → magic_link.html.jinja} +2 -1
- vibetuner/templates/frontend/base/favicons.html.jinja +1 -1
- vibetuner/templates/frontend/base/skeleton.html.jinja +5 -2
- vibetuner/templates/frontend/debug/collections.html.jinja +2 -0
- vibetuner/templates/frontend/debug/components/debug_nav.html.jinja +6 -6
- vibetuner/templates/frontend/debug/index.html.jinja +6 -4
- vibetuner/templates/frontend/debug/info.html.jinja +2 -0
- vibetuner/templates/frontend/debug/users.html.jinja +4 -2
- vibetuner/templates/frontend/debug/version.html.jinja +2 -0
- vibetuner/templates/frontend/email_sent.html.jinja +2 -1
- vibetuner/templates/frontend/index.html.jinja +1 -0
- vibetuner/templates/frontend/login.html.jinja +8 -3
- vibetuner/templates/frontend/user/edit.html.jinja +3 -2
- vibetuner/templates/frontend/user/profile.html.jinja +2 -1
- vibetuner/versioning.py +6 -2
- {vibetuner-2.14.1.dist-info → vibetuner-2.26.9.dist-info}/METADATA +23 -20
- vibetuner-2.26.9.dist-info/RECORD +71 -0
- {vibetuner-2.14.1.dist-info → vibetuner-2.26.9.dist-info}/WHEEL +1 -1
- vibetuner/frontend/AGENTS.md +0 -113
- vibetuner/frontend/CLAUDE.md +0 -113
- vibetuner/frontend/context.py +0 -10
- vibetuner/models/AGENTS.md +0 -165
- vibetuner/models/CLAUDE.md +0 -165
- vibetuner/services/AGENTS.md +0 -104
- vibetuner/services/CLAUDE.md +0 -104
- vibetuner/tasks/AGENTS.md +0 -98
- vibetuner/tasks/CLAUDE.md +0 -98
- vibetuner/tasks/context.py +0 -34
- vibetuner/templates/email/AGENTS.md +0 -48
- vibetuner/templates/email/CLAUDE.md +0 -48
- vibetuner/templates/frontend/AGENTS.md +0 -74
- vibetuner/templates/frontend/CLAUDE.md +0 -74
- vibetuner/templates/markdown/AGENTS.md +0 -29
- vibetuner/templates/markdown/CLAUDE.md +0 -29
- vibetuner-2.14.1.dist-info/RECORD +0 -86
- /vibetuner/templates/email/{default/magic_link.txt.jinja → magic_link.txt.jinja} +0 -0
- {vibetuner-2.14.1.dist-info → vibetuner-2.26.9.dist-info}/entry_points.txt +0 -0
vibetuner/cli/__init__.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# ABOUTME: Core CLI setup with AsyncTyper wrapper and base configuration
|
|
2
2
|
# ABOUTME: Provides main CLI entry point and logging configuration
|
|
3
|
+
import importlib.metadata
|
|
3
4
|
import inspect
|
|
4
5
|
from functools import partial, wraps
|
|
5
6
|
from importlib import import_module
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
6
9
|
|
|
7
10
|
import asyncer
|
|
8
11
|
import typer
|
|
9
12
|
from rich.console import Console
|
|
13
|
+
from rich.table import Table
|
|
10
14
|
|
|
11
15
|
from vibetuner.cli.run import run_app
|
|
12
16
|
from vibetuner.cli.scaffold import scaffold_app
|
|
13
|
-
from vibetuner.logging import LogLevel, setup_logging
|
|
17
|
+
from vibetuner.logging import LogLevel, logger, setup_logging
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
console = Console()
|
|
@@ -69,11 +73,69 @@ def callback(log_level: LogLevel | None = LOG_LEVEL_OPTION) -> None:
|
|
|
69
73
|
setup_logging(level=log_level)
|
|
70
74
|
|
|
71
75
|
|
|
76
|
+
@app.command()
|
|
77
|
+
def version(
|
|
78
|
+
show_app: bool = typer.Option(
|
|
79
|
+
False,
|
|
80
|
+
"--app",
|
|
81
|
+
"-a",
|
|
82
|
+
help="Show app settings version even if not in a project directory",
|
|
83
|
+
),
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Show version information."""
|
|
86
|
+
try:
|
|
87
|
+
# Get vibetuner package version
|
|
88
|
+
vibetuner_version = importlib.metadata.version("vibetuner")
|
|
89
|
+
except importlib.metadata.PackageNotFoundError:
|
|
90
|
+
vibetuner_version = "unknown"
|
|
91
|
+
|
|
92
|
+
# Create table for nice display
|
|
93
|
+
table = Table(title="Version Information")
|
|
94
|
+
table.add_column("Component", style="cyan", no_wrap=True)
|
|
95
|
+
table.add_column("Version", style="green", no_wrap=True)
|
|
96
|
+
|
|
97
|
+
# Always show vibetuner package version
|
|
98
|
+
table.add_row("vibetuner package", vibetuner_version)
|
|
99
|
+
|
|
100
|
+
# Show app version if requested or if in a project
|
|
101
|
+
try:
|
|
102
|
+
from vibetuner.config import CoreConfiguration
|
|
103
|
+
|
|
104
|
+
settings = CoreConfiguration()
|
|
105
|
+
table.add_row(f"{settings.project.project_name} settings", settings.version)
|
|
106
|
+
except Exception:
|
|
107
|
+
if show_app:
|
|
108
|
+
table.add_row("app settings", "not in project directory")
|
|
109
|
+
# else: don't show app version if not in project and not requested
|
|
110
|
+
|
|
111
|
+
console.print(table)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def core_template_symlink(
|
|
116
|
+
target: Annotated[
|
|
117
|
+
Path,
|
|
118
|
+
typer.Argument(
|
|
119
|
+
help="Path where the 'core' symlink should be created or updated",
|
|
120
|
+
),
|
|
121
|
+
],
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Create or update a 'core' symlink to the package templates directory."""
|
|
124
|
+
from vibetuner.paths import create_core_templates_symlink
|
|
125
|
+
|
|
126
|
+
create_core_templates_symlink(target)
|
|
127
|
+
|
|
128
|
+
|
|
72
129
|
app.add_typer(run_app, name="run")
|
|
73
130
|
app.add_typer(scaffold_app, name="scaffold")
|
|
74
131
|
|
|
75
132
|
try:
|
|
76
133
|
import_module("app.cli")
|
|
77
|
-
except
|
|
134
|
+
except ModuleNotFoundError:
|
|
135
|
+
# Silent pass for missing app.cli module (expected in some projects)
|
|
78
136
|
pass
|
|
79
|
-
|
|
137
|
+
except ImportError as e:
|
|
138
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
139
|
+
logger.warning(
|
|
140
|
+
f"Failed to import app.cli: {e}. User CLI commands will not be available."
|
|
141
|
+
)
|
vibetuner/cli/run.py
CHANGED
|
@@ -47,7 +47,7 @@ def dev(
|
|
|
47
47
|
|
|
48
48
|
# Call streaq programmatically
|
|
49
49
|
streaq_main(
|
|
50
|
-
worker_path="
|
|
50
|
+
worker_path="vibetuner.tasks.worker.worker",
|
|
51
51
|
workers=1,
|
|
52
52
|
reload=True,
|
|
53
53
|
verbose=True,
|
|
@@ -123,7 +123,7 @@ def prod(
|
|
|
123
123
|
|
|
124
124
|
# Call streaq programmatically
|
|
125
125
|
streaq_main(
|
|
126
|
-
worker_path="
|
|
126
|
+
worker_path="vibetuner.tasks.worker.worker",
|
|
127
127
|
workers=workers_count,
|
|
128
128
|
reload=False,
|
|
129
129
|
verbose=False,
|
vibetuner/config.py
CHANGED
|
@@ -17,6 +17,8 @@ from pydantic import (
|
|
|
17
17
|
from pydantic_extra_types.language_code import LanguageAlpha2
|
|
18
18
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
19
19
|
|
|
20
|
+
from vibetuner.logging import logger
|
|
21
|
+
|
|
20
22
|
from .paths import config_vars as config_vars_path
|
|
21
23
|
from .versioning import version
|
|
22
24
|
|
|
@@ -32,12 +34,16 @@ def _load_project_config() -> "ProjectConfiguration":
|
|
|
32
34
|
)
|
|
33
35
|
if not config_vars_path.exists():
|
|
34
36
|
return ProjectConfiguration()
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
37
|
+
|
|
38
|
+
yaml_data = yaml.safe_load(config_vars_path.read_text(encoding="utf-8"))
|
|
39
|
+
return ProjectConfiguration(**yaml_data)
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class ProjectConfiguration(BaseSettings):
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_project_config(cls) -> "ProjectConfiguration":
|
|
45
|
+
return _load_project_config()
|
|
46
|
+
|
|
41
47
|
project_slug: str = "default_project"
|
|
42
48
|
project_name: str = "default_project"
|
|
43
49
|
|
|
@@ -47,9 +53,6 @@ class ProjectConfiguration(BaseSettings):
|
|
|
47
53
|
supported_languages: set[LanguageAlpha2] | None = None
|
|
48
54
|
default_language: LanguageAlpha2 = LanguageAlpha2("en")
|
|
49
55
|
|
|
50
|
-
mongodb_url: MongoDsn | None = None
|
|
51
|
-
redis_url: RedisDsn | None = None
|
|
52
|
-
|
|
53
56
|
# AWS Parameters
|
|
54
57
|
aws_default_region: str = "eu-central-1"
|
|
55
58
|
|
|
@@ -90,16 +93,20 @@ class ProjectConfiguration(BaseSettings):
|
|
|
90
93
|
)
|
|
91
94
|
return f"© {year_part}{f' {self.company_name}' if self.company_name else ''}"
|
|
92
95
|
|
|
93
|
-
model_config = SettingsConfigDict(extra="ignore")
|
|
96
|
+
model_config = SettingsConfigDict(case_sensitive=False, extra="ignore")
|
|
94
97
|
|
|
95
98
|
|
|
96
99
|
class CoreConfiguration(BaseSettings):
|
|
97
|
-
project: ProjectConfiguration
|
|
100
|
+
project: ProjectConfiguration = ProjectConfiguration.from_project_config()
|
|
98
101
|
|
|
99
102
|
debug: bool = False
|
|
100
103
|
version: str = version
|
|
101
104
|
session_key: SecretStr = SecretStr("ct-!secret-must-change-me")
|
|
102
105
|
|
|
106
|
+
# Database and Cache URLs
|
|
107
|
+
mongodb_url: MongoDsn = MongoDsn("mongodb://localhost:27017")
|
|
108
|
+
redis_url: RedisDsn = RedisDsn("redis://localhost:6379")
|
|
109
|
+
|
|
103
110
|
aws_access_key_id: SecretStr | None = None
|
|
104
111
|
aws_secret_access_key: SecretStr | None = None
|
|
105
112
|
|
|
@@ -130,4 +137,7 @@ class CoreConfiguration(BaseSettings):
|
|
|
130
137
|
)
|
|
131
138
|
|
|
132
139
|
|
|
133
|
-
settings = CoreConfiguration(
|
|
140
|
+
settings = CoreConfiguration()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
logger.info("Configuration loaded for project: {}", settings.project.project_name)
|
vibetuner/context.py
CHANGED
vibetuner/frontend/__init__.py
CHANGED
|
@@ -4,10 +4,12 @@ from fastapi import APIRouter, Depends as Depends, FastAPI, Request
|
|
|
4
4
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
5
5
|
from fastapi.staticfiles import StaticFiles
|
|
6
6
|
|
|
7
|
+
import vibetuner.frontend.lifespan as lifespan_module
|
|
7
8
|
from vibetuner import paths
|
|
9
|
+
from vibetuner.logging import logger
|
|
8
10
|
|
|
9
11
|
from .deps import LangDep as LangDep, MagicCookieDep as MagicCookieDep
|
|
10
|
-
from .lifespan import ctx
|
|
12
|
+
from .lifespan import ctx
|
|
11
13
|
from .middleware import middlewares
|
|
12
14
|
from .routes import auth, debug, health, language, meta, user
|
|
13
15
|
from .templates import render_template
|
|
@@ -21,15 +23,35 @@ def register_router(router: APIRouter) -> None:
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
try:
|
|
24
|
-
import app.frontend.oauth as _app_oauth #
|
|
25
|
-
import app.frontend.routes as _app_routes #
|
|
26
|
+
import app.frontend.oauth as _app_oauth # type: ignore[unresolved-import] # noqa: F401
|
|
27
|
+
import app.frontend.routes as _app_routes # type: ignore[unresolved-import] # noqa: F401
|
|
26
28
|
|
|
27
29
|
# Register OAuth routes after providers are registered
|
|
28
30
|
from .routes.auth import register_oauth_routes
|
|
29
31
|
|
|
30
32
|
register_oauth_routes()
|
|
31
|
-
except
|
|
33
|
+
except ModuleNotFoundError:
|
|
34
|
+
# Silent pass for missing app.frontend.oauth or app.frontend.routes modules (expected in some projects)
|
|
32
35
|
pass
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
38
|
+
logger.warning(
|
|
39
|
+
f"Failed to import app.frontend.oauth or app.frontend.routes: {e}. OAuth and custom routes will not be available."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from app.frontend.middleware import (
|
|
44
|
+
middlewares as app_middlewares, # type: ignore[unresolved-import]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
middlewares.extend(app_middlewares)
|
|
48
|
+
except ModuleNotFoundError:
|
|
49
|
+
pass
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
52
|
+
logger.warning(
|
|
53
|
+
f"Failed to import app.frontend.middleware: {e}. Additional middlewares will not be available."
|
|
54
|
+
)
|
|
33
55
|
|
|
34
56
|
|
|
35
57
|
dependencies: list[Any] = [
|
|
@@ -38,7 +60,7 @@ dependencies: list[Any] = [
|
|
|
38
60
|
|
|
39
61
|
app = FastAPI(
|
|
40
62
|
debug=ctx.DEBUG,
|
|
41
|
-
lifespan=lifespan,
|
|
63
|
+
lifespan=lifespan_module.lifespan,
|
|
42
64
|
docs_url=None,
|
|
43
65
|
redoc_url=None,
|
|
44
66
|
openapi_url=None,
|
vibetuner/frontend/lifespan.py
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
from contextlib import asynccontextmanager
|
|
2
|
+
from typing import AsyncGenerator
|
|
2
3
|
|
|
3
4
|
from fastapi import FastAPI
|
|
4
5
|
|
|
6
|
+
from vibetuner.context import ctx
|
|
7
|
+
from vibetuner.logging import logger
|
|
5
8
|
from vibetuner.mongo import init_models
|
|
6
9
|
|
|
7
|
-
from .context import ctx
|
|
8
10
|
from .hotreload import hotreload
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@asynccontextmanager
|
|
12
|
-
async def
|
|
14
|
+
async def base_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
15
|
+
logger.info("Vibetuner frontend starting")
|
|
13
16
|
if ctx.DEBUG:
|
|
14
17
|
await hotreload.startup()
|
|
15
18
|
|
|
16
19
|
await init_models()
|
|
17
|
-
# Add below anything that should happen before startup
|
|
18
20
|
|
|
19
|
-
# Until here
|
|
20
21
|
yield
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Until here
|
|
23
|
+
logger.info("Vibetuner frontend stopping")
|
|
25
24
|
if ctx.DEBUG:
|
|
26
25
|
await hotreload.shutdown()
|
|
26
|
+
logger.info("Vibetuner frontend stopped")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from app.frontend.lifespan import lifespan # ty: ignore
|
|
31
|
+
except ModuleNotFoundError:
|
|
32
|
+
# Silent pass for missing app.frontend.lifespan module (expected in some projects)
|
|
33
|
+
lifespan = base_lifespan
|
|
34
|
+
except ImportError as e:
|
|
35
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
36
|
+
logger.warning(f"Failed to import app.frontend.lifespan: {e}. Using base lifespan.")
|
|
37
|
+
lifespan = base_lifespan
|
vibetuner/frontend/middleware.py
CHANGED
|
@@ -13,12 +13,12 @@ from starlette_babel import (
|
|
|
13
13
|
LocaleMiddleware,
|
|
14
14
|
get_translator,
|
|
15
15
|
)
|
|
16
|
-
from starlette_htmx.middleware import HtmxMiddleware
|
|
16
|
+
from starlette_htmx.middleware import HtmxMiddleware
|
|
17
17
|
|
|
18
18
|
from vibetuner.config import settings
|
|
19
|
+
from vibetuner.context import ctx
|
|
19
20
|
from vibetuner.paths import locales as locales_path
|
|
20
21
|
|
|
21
|
-
from .context import ctx
|
|
22
22
|
from .oauth import WebUser
|
|
23
23
|
|
|
24
24
|
|
|
@@ -126,7 +126,6 @@ class AuthBackend(AuthenticationBackend):
|
|
|
126
126
|
return None
|
|
127
127
|
|
|
128
128
|
|
|
129
|
-
# Until this line
|
|
130
129
|
middlewares: list[Middleware] = [
|
|
131
130
|
Middleware(TrustedHostMiddleware),
|
|
132
131
|
Middleware(ForwardedProtocolMiddleware),
|
|
@@ -145,7 +144,4 @@ middlewares: list[Middleware] = [
|
|
|
145
144
|
),
|
|
146
145
|
Middleware(AdjustLangCookieMiddleware),
|
|
147
146
|
Middleware(AuthenticationMiddleware, backend=AuthBackend()),
|
|
148
|
-
# Add your middleware below this line
|
|
149
147
|
]
|
|
150
|
-
|
|
151
|
-
# EOF
|
|
@@ -10,10 +10,10 @@ from fastapi.responses import (
|
|
|
10
10
|
RedirectResponse,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
+
from vibetuner.context import ctx
|
|
13
14
|
from vibetuner.models import UserModel
|
|
14
15
|
from vibetuner.models.registry import get_all_models
|
|
15
16
|
|
|
16
|
-
from ..context import ctx
|
|
17
17
|
from ..deps import MAGIC_COOKIE_NAME
|
|
18
18
|
from ..templates import render_template
|
|
19
19
|
|
|
@@ -3,9 +3,9 @@ from fastapi.responses import HTMLResponse, RedirectResponse
|
|
|
3
3
|
from pydantic_extra_types.language_code import LanguageAlpha2
|
|
4
4
|
from starlette.authentication import requires
|
|
5
5
|
|
|
6
|
+
from vibetuner.context import ctx
|
|
6
7
|
from vibetuner.models import UserModel
|
|
7
8
|
|
|
8
|
-
from ..context import ctx
|
|
9
9
|
from ..templates import render_template
|
|
10
10
|
|
|
11
11
|
|
vibetuner/frontend/templates.py
CHANGED
|
@@ -7,7 +7,7 @@ from starlette.responses import HTMLResponse
|
|
|
7
7
|
from starlette_babel import gettext_lazy as _, gettext_lazy as ngettext
|
|
8
8
|
from starlette_babel.contrib.jinja import configure_jinja_env
|
|
9
9
|
|
|
10
|
-
from vibetuner.context import
|
|
10
|
+
from vibetuner.context import ctx as data_ctx
|
|
11
11
|
from vibetuner.paths import frontend_templates
|
|
12
12
|
from vibetuner.templates import render_static_template
|
|
13
13
|
from vibetuner.time import age_in_timedelta
|
|
@@ -19,8 +19,6 @@ __all__ = [
|
|
|
19
19
|
"render_static_template",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
|
-
data_ctx = Context()
|
|
23
|
-
|
|
24
22
|
|
|
25
23
|
def timeago(dt):
|
|
26
24
|
"""Converts a datetime object to a human-readable string representing the time elapsed since the given datetime.
|
vibetuner/mongo.py
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
|
|
1
3
|
from beanie import init_beanie
|
|
2
4
|
from pymongo import AsyncMongoClient
|
|
3
5
|
|
|
4
|
-
from .config import settings
|
|
5
|
-
from .
|
|
6
|
+
from vibetuner.config import settings
|
|
7
|
+
from vibetuner.logging import logger
|
|
8
|
+
from vibetuner.models.registry import get_all_models
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
async def init_models() -> None:
|
|
9
12
|
"""Initialize MongoDB connection and register all Beanie models."""
|
|
10
13
|
|
|
14
|
+
# Try to import user models to trigger their registration
|
|
15
|
+
try:
|
|
16
|
+
import_module("app.models")
|
|
17
|
+
except ModuleNotFoundError:
|
|
18
|
+
# Silent pass for missing app.models module (expected in some projects)
|
|
19
|
+
pass
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
22
|
+
logger.warning(
|
|
23
|
+
f"Failed to import app.models: {e}. User models will not be registered."
|
|
24
|
+
)
|
|
25
|
+
|
|
11
26
|
client: AsyncMongoClient = AsyncMongoClient(
|
|
12
|
-
host=str(settings.
|
|
27
|
+
host=str(settings.mongodb_url),
|
|
13
28
|
compressors=["zstd"],
|
|
14
29
|
)
|
|
15
30
|
|
vibetuner/paths.py
CHANGED
|
@@ -5,6 +5,8 @@ from typing import Self
|
|
|
5
5
|
from pydantic import computed_field, model_validator
|
|
6
6
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
7
7
|
|
|
8
|
+
from vibetuner.logging import logger
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
# Package-relative paths (for bundled templates in the vibetuner package)
|
|
10
12
|
_package_files = files("vibetuner")
|
|
@@ -22,6 +24,35 @@ def _get_package_templates_path() -> Path:
|
|
|
22
24
|
) from None
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
def create_core_templates_symlink(target: Path) -> None:
|
|
28
|
+
"""Create or update a 'core' symlink pointing to the package templates directory."""
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
source = _get_package_templates_path().resolve()
|
|
32
|
+
except RuntimeError as e:
|
|
33
|
+
logger.error(f"Cannot create symlink: {e}")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# Case 1: target does not exist → create symlink
|
|
37
|
+
if not target.exists():
|
|
38
|
+
target.symlink_to(source, target_is_directory=True)
|
|
39
|
+
logger.info(f"Created symlink '{target}' → '{source}'")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
# Case 2: exists but is not a symlink → error
|
|
43
|
+
if not target.is_symlink():
|
|
44
|
+
logger.error(f"Cannot create symlink: '{target}' exists and is not a symlink.")
|
|
45
|
+
raise FileExistsError(
|
|
46
|
+
f"Cannot create symlink: '{target}' exists and is not a symlink."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Case 3: is a symlink but points somewhere else → update it
|
|
50
|
+
if target.resolve() != source:
|
|
51
|
+
target.unlink()
|
|
52
|
+
target.symlink_to(source, target_is_directory=True)
|
|
53
|
+
logger.info(f"Updated symlink '{target}' → '{source}'")
|
|
54
|
+
|
|
55
|
+
|
|
25
56
|
# Package templates always available
|
|
26
57
|
package_templates = _get_package_templates_path()
|
|
27
58
|
core_templates = package_templates # Alias for backwards compatibility
|
|
@@ -158,10 +189,6 @@ class PathSettings(BaseSettings):
|
|
|
158
189
|
paths.append(package_templates / "markdown")
|
|
159
190
|
return paths
|
|
160
191
|
|
|
161
|
-
def set_root(self, project_root: Path) -> None:
|
|
162
|
-
"""Explicitly set project root (overrides auto-detection)."""
|
|
163
|
-
self.root = project_root
|
|
164
|
-
|
|
165
192
|
def to_template_path_list(self, path: Path) -> list[Path]:
|
|
166
193
|
"""Convert path to list with fallback."""
|
|
167
194
|
return [path, path / self.fallback_path]
|
|
@@ -191,12 +218,6 @@ class PathSettings(BaseSettings):
|
|
|
191
218
|
_settings = PathSettings()
|
|
192
219
|
|
|
193
220
|
|
|
194
|
-
# Backwards-compatible module-level API
|
|
195
|
-
def set_project_root(project_root: Path) -> None:
|
|
196
|
-
"""Set the project root directory explicitly."""
|
|
197
|
-
_settings.set_root(project_root)
|
|
198
|
-
|
|
199
|
-
|
|
200
221
|
def to_template_path_list(path: Path) -> list[Path]:
|
|
201
222
|
"""Convert path to list with fallback."""
|
|
202
223
|
return _settings.to_template_path_list(path)
|
vibetuner/tasks/__init__.py
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from typing import AsyncGenerator
|
|
3
|
+
|
|
4
|
+
from vibetuner.context import Context, ctx
|
|
5
|
+
from vibetuner.logging import logger
|
|
6
|
+
from vibetuner.mongo import init_models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@asynccontextmanager
|
|
10
|
+
async def base_lifespan() -> AsyncGenerator[Context, None]:
|
|
11
|
+
logger.info("Vibetuner task worker starting")
|
|
12
|
+
|
|
13
|
+
await init_models()
|
|
14
|
+
|
|
15
|
+
yield ctx
|
|
16
|
+
|
|
17
|
+
logger.info("Vibetuner task worker stopping")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from app.tasks.lifespan import lifespan # ty: ignore
|
|
22
|
+
except ModuleNotFoundError:
|
|
23
|
+
# Silent pass for missing app.tasks.lifespan module (expected in some projects)
|
|
24
|
+
lifespan = base_lifespan
|
|
25
|
+
except ImportError as e:
|
|
26
|
+
# Log warning for any import error (including syntax errors, missing dependencies, etc.)
|
|
27
|
+
logger.warning(f"Failed to import app.tasks.lifespan: {e}. Using base lifespan.")
|
|
28
|
+
lifespan = base_lifespan
|
vibetuner/tasks/worker.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from streaq import Worker
|
|
2
2
|
|
|
3
3
|
from vibetuner.config import settings
|
|
4
|
-
from vibetuner.tasks.
|
|
4
|
+
from vibetuner.tasks.lifespan import lifespan
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
worker = Worker(
|
|
8
|
-
redis_url=str(settings.
|
|
8
|
+
redis_url=str(settings.redis_url),
|
|
9
9
|
queue_name=(
|
|
10
10
|
settings.project.project_slug
|
|
11
11
|
if not settings.debug
|
|
@@ -13,6 +13,3 @@ worker = Worker(
|
|
|
13
13
|
),
|
|
14
14
|
lifespan=lifespan,
|
|
15
15
|
)
|
|
16
|
-
|
|
17
|
-
# Register tasks
|
|
18
|
-
# use something like from . import task_module_name // noqa: E402, F401
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
{# djlint:off #}
|
|
1
2
|
<html>
|
|
2
3
|
<body>
|
|
3
|
-
<h2>Sign in to {{ project_name }}</h2>
|
|
4
4
|
<p>Click the link below to sign in to your account:</p>
|
|
5
5
|
<p>
|
|
6
6
|
<a href="{{ login_url }}"
|
|
@@ -14,3 +14,4 @@
|
|
|
14
14
|
<p>If you didn't request this, you can safely ignore this email.</p>
|
|
15
15
|
</body>
|
|
16
16
|
</html>
|
|
17
|
+
{# djlint:on #}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<link rel="manifest" href="{{ url_for(
|
|
1
|
+
<link rel="manifest" href="{{ url_for('site_webmanifest').path }}" />
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<meta name="description" content="{{ meta_description | default(
|
|
7
|
-
<meta name="keywords" content="{{ meta_keywords | default(
|
|
6
|
+
<meta name="description" content="{{ meta_description | default('') }}" />
|
|
7
|
+
<meta name="keywords" content="{{ meta_keywords | default('') }}" />
|
|
8
8
|
<meta name="color-scheme" content="light" />
|
|
9
9
|
<title>
|
|
10
10
|
{% block title %}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
{% endif %}
|
|
18
18
|
{% include "base/favicons.html.jinja" %}
|
|
19
19
|
{% include "base/opengraph.html.jinja" %}
|
|
20
|
+
|
|
20
21
|
{% block head %}
|
|
21
22
|
{% endblock head %}
|
|
22
23
|
</head>
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
{% block header %}
|
|
27
28
|
{% if not SKIP_HEADER %}
|
|
28
29
|
{% include "base/header.html.jinja" %}
|
|
30
|
+
|
|
29
31
|
{% endif %}
|
|
30
32
|
{% endblock header %}
|
|
31
33
|
{% block body %}
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
{% block footer %}
|
|
34
36
|
{% if not SKIP_FOOTER %}
|
|
35
37
|
{% include "base/footer.html.jinja" %}
|
|
38
|
+
|
|
36
39
|
{% endif %}
|
|
37
40
|
{% endblock footer %}
|
|
38
41
|
{% if DEBUG %}{{ hotreload.script(url_for('hot-reload') ) | safe }}{% endif %}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
2
3
|
{% block title %}
|
|
3
4
|
Database Collections Schema - Debug
|
|
4
5
|
{% endblock title %}
|
|
@@ -99,5 +100,6 @@
|
|
|
99
100
|
</div>
|
|
100
101
|
<!-- Debug Navigation Footer -->
|
|
101
102
|
{% include "debug/components/debug_nav.html.jinja" %}
|
|
103
|
+
|
|
102
104
|
</div>
|
|
103
105
|
{% endblock body %}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- Debug Navigation Component -->
|
|
2
2
|
<nav class="mt-8 pt-6 border-t border-base-200">
|
|
3
3
|
<div class="flex flex-wrap gap-4">
|
|
4
|
-
<a href="{{ url_for(
|
|
4
|
+
<a href="{{ url_for('debug_index') }}"
|
|
5
5
|
class="btn btn-outline btn-primary gap-2">
|
|
6
6
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
7
7
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z">
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
</svg>
|
|
12
12
|
Debug Home
|
|
13
13
|
</a>
|
|
14
|
-
<a href="{{ url_for(
|
|
14
|
+
<a href="{{ url_for('debug_info') }}"
|
|
15
15
|
class="btn btn-outline btn-primary gap-2">
|
|
16
16
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
17
17
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
</svg>
|
|
20
20
|
System Info
|
|
21
21
|
</a>
|
|
22
|
-
<a href="{{ url_for(
|
|
22
|
+
<a href="{{ url_for('debug_collections') }}"
|
|
23
23
|
class="btn btn-outline btn-primary gap-2">
|
|
24
24
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
25
25
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4">
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
</svg>
|
|
28
28
|
Collections
|
|
29
29
|
</a>
|
|
30
|
-
<a href="{{ url_for(
|
|
30
|
+
<a href="{{ url_for('debug_users') }}"
|
|
31
31
|
class="btn btn-outline btn-warning gap-2">
|
|
32
32
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
33
33
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z">
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
</svg>
|
|
36
36
|
User Impersonation
|
|
37
37
|
</a>
|
|
38
|
-
<a href="{{ url_for(
|
|
38
|
+
<a href="{{ url_for('debug_version') }}"
|
|
39
39
|
class="btn btn-outline btn-primary gap-2">
|
|
40
40
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
41
41
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z">
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
</svg>
|
|
44
44
|
Version
|
|
45
45
|
</a>
|
|
46
|
-
<a href="{{ url_for(
|
|
46
|
+
<a href="{{ url_for('health_ping') }}"
|
|
47
47
|
class="btn btn-outline btn-success gap-2">
|
|
48
48
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
49
49
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z">
|