skrift 0.1.0a9__tar.gz → 0.1.0a10__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.
- {skrift-0.1.0a9 → skrift-0.1.0a10}/PKG-INFO +1 -1
- {skrift-0.1.0a9 → skrift-0.1.0a10}/pyproject.toml +5 -1
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/asgi.py +108 -1
- {skrift-0.1.0a9 → skrift-0.1.0a10}/.gitignore +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/README.md +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/__main__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/admin/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/admin/controller.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/admin/navigation.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/env.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/script.py.mako +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260122_200000_add_settings_table.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260129_add_oauth_accounts.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260129_add_provider_metadata.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic.ini +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/auth/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/auth/guards.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/auth/roles.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/auth/services.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/cli.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/config.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/controllers/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/controllers/auth.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/controllers/web.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/base.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/oauth_account.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/page.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/role.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/setting.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/models/user.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/services/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/services/oauth_service.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/services/page_service.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/db/services/setting_service.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/lib/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/lib/exceptions.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/lib/template.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/__init__.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/config_writer.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/controller.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/middleware.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/providers.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/setup/state.py +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/static/css/style.css +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/admin.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/base.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/pages/edit.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/pages/list.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/settings/site.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/users/list.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/admin/users/roles.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/auth/dummy_login.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/auth/login.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/base.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/error-404.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/error-500.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/error.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/index.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/page.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/admin.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/auth.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/base.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/complete.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/configuring.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/database.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/restart.html +0 -0
- {skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/templates/setup/site.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "skrift"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.0a10"
|
|
4
4
|
description = "A lightweight async Python CMS for crafting modern websites"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -30,6 +30,10 @@ requires = ["hatchling"]
|
|
|
30
30
|
build-backend = "hatchling.build"
|
|
31
31
|
|
|
32
32
|
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0.0",
|
|
35
|
+
"pytest-asyncio>=0.24.0",
|
|
36
|
+
]
|
|
33
37
|
docs = [
|
|
34
38
|
"zensical>=0.0.19",
|
|
35
39
|
]
|
|
@@ -26,6 +26,7 @@ from litestar import Litestar
|
|
|
26
26
|
from litestar.config.compression import CompressionConfig
|
|
27
27
|
from litestar.contrib.jinja import JinjaTemplateEngine
|
|
28
28
|
from litestar.exceptions import HTTPException
|
|
29
|
+
from litestar.middleware import DefineMiddleware
|
|
29
30
|
from litestar.middleware.session.client_side import CookieBackendConfig
|
|
30
31
|
from litestar.static_files import create_static_files_router
|
|
31
32
|
from litestar.template import TemplateConfig
|
|
@@ -73,6 +74,109 @@ def load_controllers() -> list:
|
|
|
73
74
|
return controllers
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
def _load_middleware_factory(spec: str):
|
|
78
|
+
"""Import a single middleware factory from a module:name spec.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
spec: String in format "module.path:factory_name"
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The callable middleware factory
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If spec doesn't contain exactly one colon
|
|
88
|
+
ImportError: If the module cannot be imported
|
|
89
|
+
AttributeError: If the factory doesn't exist in the module
|
|
90
|
+
TypeError: If the factory is not callable
|
|
91
|
+
"""
|
|
92
|
+
if ":" not in spec:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Invalid middleware spec '{spec}': must be in format 'module:factory'"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
parts = spec.split(":")
|
|
98
|
+
if len(parts) != 2:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"Invalid middleware spec '{spec}': must contain exactly one colon"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
module_path, factory_name = parts
|
|
104
|
+
module = importlib.import_module(module_path)
|
|
105
|
+
factory = getattr(module, factory_name)
|
|
106
|
+
|
|
107
|
+
if not callable(factory):
|
|
108
|
+
raise TypeError(
|
|
109
|
+
f"Middleware factory '{spec}' is not callable"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return factory
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def load_middleware() -> list:
|
|
116
|
+
"""Load middleware from app.yaml configuration.
|
|
117
|
+
|
|
118
|
+
Supports two formats in app.yaml:
|
|
119
|
+
|
|
120
|
+
Simple (no args):
|
|
121
|
+
middleware:
|
|
122
|
+
- myapp.middleware:create_logging_middleware
|
|
123
|
+
|
|
124
|
+
With kwargs:
|
|
125
|
+
middleware:
|
|
126
|
+
- factory: myapp.middleware:create_rate_limit_middleware
|
|
127
|
+
kwargs:
|
|
128
|
+
requests_per_minute: 100
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of middleware factories or DefineMiddleware instances
|
|
132
|
+
"""
|
|
133
|
+
config_path = get_config_path()
|
|
134
|
+
|
|
135
|
+
if not config_path.exists():
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
with open(config_path, "r") as f:
|
|
139
|
+
config = yaml.safe_load(f)
|
|
140
|
+
|
|
141
|
+
if not config:
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
middleware_specs = config.get("middleware", [])
|
|
145
|
+
if not middleware_specs:
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
# Add working directory to sys.path for local middleware imports
|
|
149
|
+
cwd = os.getcwd()
|
|
150
|
+
if cwd not in sys.path:
|
|
151
|
+
sys.path.insert(0, cwd)
|
|
152
|
+
|
|
153
|
+
middleware = []
|
|
154
|
+
for spec in middleware_specs:
|
|
155
|
+
if isinstance(spec, str):
|
|
156
|
+
# Simple format: "module:factory"
|
|
157
|
+
factory = _load_middleware_factory(spec)
|
|
158
|
+
middleware.append(factory)
|
|
159
|
+
elif isinstance(spec, dict):
|
|
160
|
+
# Dict format with optional kwargs
|
|
161
|
+
if "factory" not in spec:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Middleware dict spec must have 'factory' key: {spec}"
|
|
164
|
+
)
|
|
165
|
+
factory = _load_middleware_factory(spec["factory"])
|
|
166
|
+
kwargs = spec.get("kwargs", {})
|
|
167
|
+
if kwargs:
|
|
168
|
+
middleware.append(DefineMiddleware(factory, **kwargs))
|
|
169
|
+
else:
|
|
170
|
+
middleware.append(factory)
|
|
171
|
+
else:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"Invalid middleware spec type: {type(spec).__name__}. "
|
|
174
|
+
"Must be string or dict."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return middleware
|
|
178
|
+
|
|
179
|
+
|
|
76
180
|
async def check_setup_complete(db_config: SQLAlchemyAsyncConfig) -> bool:
|
|
77
181
|
"""Check if setup has been completed."""
|
|
78
182
|
try:
|
|
@@ -282,6 +386,9 @@ def create_app() -> Litestar:
|
|
|
282
386
|
# Load controllers from app.yaml
|
|
283
387
|
controllers = load_controllers()
|
|
284
388
|
|
|
389
|
+
# Load middleware from app.yaml
|
|
390
|
+
user_middleware = load_middleware()
|
|
391
|
+
|
|
285
392
|
# Database configuration
|
|
286
393
|
if "sqlite" in settings.db.url:
|
|
287
394
|
engine_config = EngineConfig(echo=settings.db.echo)
|
|
@@ -350,7 +457,7 @@ def create_app() -> Litestar:
|
|
|
350
457
|
on_startup=[on_startup],
|
|
351
458
|
route_handlers=[*controllers, static_files_router],
|
|
352
459
|
plugins=[SQLAlchemyPlugin(config=db_config)],
|
|
353
|
-
middleware=[session_config.middleware],
|
|
460
|
+
middleware=[session_config.middleware, *user_middleware],
|
|
354
461
|
template_config=template_config,
|
|
355
462
|
compression_config=CompressionConfig(backend="gzip"),
|
|
356
463
|
exception_handlers={
|
|
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
|
{skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260122_200000_add_settings_table.py
RENAMED
|
File without changes
|
|
File without changes
|
{skrift-0.1.0a9 → skrift-0.1.0a10}/skrift/alembic/versions/20260129_add_provider_metadata.py
RENAMED
|
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
|
|
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
|