skrift 0.1.0a4__tar.gz → 0.1.0a6__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 (74) hide show
  1. {skrift-0.1.0a4 → skrift-0.1.0a6}/PKG-INFO +1 -1
  2. {skrift-0.1.0a4 → skrift-0.1.0a6}/pyproject.toml +2 -3
  3. skrift-0.1.0a6/skrift/__main__.py +12 -0
  4. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/asgi.py +13 -6
  5. skrift-0.1.0a6/skrift/cli.py +143 -0
  6. skrift-0.1.0a4/skrift/__main__.py +0 -17
  7. skrift-0.1.0a4/skrift/cli.py +0 -54
  8. {skrift-0.1.0a4 → skrift-0.1.0a6}/.gitignore +0 -0
  9. {skrift-0.1.0a4 → skrift-0.1.0a6}/README.md +0 -0
  10. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/__init__.py +0 -0
  11. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/admin/__init__.py +0 -0
  12. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/admin/controller.py +0 -0
  13. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/admin/navigation.py +0 -0
  14. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/env.py +0 -0
  15. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/script.py.mako +0 -0
  16. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +0 -0
  17. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +0 -0
  18. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +0 -0
  19. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +0 -0
  20. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260122_200000_add_settings_table.py +0 -0
  21. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic/versions/20260129_add_oauth_accounts.py +0 -0
  22. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/alembic.ini +0 -0
  23. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/auth/__init__.py +0 -0
  24. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/auth/guards.py +0 -0
  25. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/auth/roles.py +0 -0
  26. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/auth/services.py +0 -0
  27. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/config.py +0 -0
  28. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/controllers/__init__.py +0 -0
  29. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/controllers/auth.py +0 -0
  30. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/controllers/web.py +0 -0
  31. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/__init__.py +0 -0
  32. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/base.py +0 -0
  33. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/__init__.py +0 -0
  34. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/oauth_account.py +0 -0
  35. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/page.py +0 -0
  36. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/role.py +0 -0
  37. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/setting.py +0 -0
  38. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/models/user.py +0 -0
  39. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/services/__init__.py +0 -0
  40. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/services/page_service.py +0 -0
  41. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/db/services/setting_service.py +0 -0
  42. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/lib/__init__.py +0 -0
  43. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/lib/exceptions.py +0 -0
  44. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/lib/template.py +0 -0
  45. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/__init__.py +0 -0
  46. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/config_writer.py +0 -0
  47. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/controller.py +0 -0
  48. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/middleware.py +0 -0
  49. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/providers.py +0 -0
  50. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/setup/state.py +0 -0
  51. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/static/css/style.css +0 -0
  52. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/admin.html +0 -0
  53. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/base.html +0 -0
  54. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/pages/edit.html +0 -0
  55. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/pages/list.html +0 -0
  56. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/settings/site.html +0 -0
  57. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/users/list.html +0 -0
  58. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/admin/users/roles.html +0 -0
  59. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/auth/dummy_login.html +0 -0
  60. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/auth/login.html +0 -0
  61. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/base.html +0 -0
  62. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/error-404.html +0 -0
  63. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/error-500.html +0 -0
  64. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/error.html +0 -0
  65. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/index.html +0 -0
  66. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/page.html +0 -0
  67. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/admin.html +0 -0
  68. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/auth.html +0 -0
  69. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/base.html +0 -0
  70. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/complete.html +0 -0
  71. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/configuring.html +0 -0
  72. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/database.html +0 -0
  73. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/restart.html +0 -0
  74. {skrift-0.1.0a4 → skrift-0.1.0a6}/skrift/templates/setup/site.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skrift
3
- Version: 0.1.0a4
3
+ Version: 0.1.0a6
4
4
  Summary: A lightweight async Python CMS for crafting modern websites
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: advanced-alchemy>=0.26.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "skrift"
3
- version = "0.1.0a4"
3
+ version = "0.1.0a6"
4
4
  description = "A lightweight async Python CMS for crafting modern websites"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -20,8 +20,7 @@ dependencies = [
20
20
  ]
21
21
 
22
22
  [project.scripts]
23
- skrift = "skrift.__main__:main"
24
- skrift-db = "skrift.cli:db"
23
+ skrift = "skrift.cli:cli"
25
24
 
26
25
  [tool.uv]
27
26
  package = true
@@ -0,0 +1,12 @@
1
+ """Entry point for the skrift package."""
2
+
3
+ from skrift.cli import cli
4
+
5
+
6
+ def main():
7
+ """Run the Skrift CLI."""
8
+ cli()
9
+
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -10,6 +10,7 @@ The application uses a dispatcher architecture:
10
10
  import asyncio
11
11
  import hashlib
12
12
  import importlib
13
+ import os
13
14
  from datetime import datetime
14
15
  from pathlib import Path
15
16
 
@@ -309,9 +310,11 @@ def create_app() -> Litestar:
309
310
  )
310
311
 
311
312
  # Template configuration
313
+ # Search working directory first for user overrides, then package directory
314
+ working_dir_templates = Path(os.getcwd()) / "templates"
312
315
  template_dir = Path(__file__).parent / "templates"
313
316
  template_config = TemplateConfig(
314
- directory=template_dir,
317
+ directory=[working_dir_templates, template_dir],
315
318
  engine=JinjaTemplateEngine,
316
319
  engine_callback=lambda engine: engine.engine.globals.update({
317
320
  "now": datetime.now,
@@ -322,10 +325,11 @@ def create_app() -> Litestar:
322
325
  }),
323
326
  )
324
327
 
325
- # Static files
328
+ # Static files - working directory first for user overrides, then package directory
329
+ working_dir_static = Path(os.getcwd()) / "static"
326
330
  static_files_router = create_static_files_router(
327
331
  path="/static",
328
- directories=[Path(__file__).parent / "static"],
332
+ directories=[working_dir_static, Path(__file__).parent / "static"],
329
333
  )
330
334
 
331
335
  from skrift.auth import sync_roles_to_database
@@ -381,9 +385,11 @@ def create_setup_app() -> Litestar:
381
385
  )
382
386
 
383
387
  # Template configuration
388
+ # Search working directory first for user overrides, then package directory
389
+ working_dir_templates = Path(os.getcwd()) / "templates"
384
390
  template_dir = Path(__file__).parent / "templates"
385
391
  template_config = TemplateConfig(
386
- directory=template_dir,
392
+ directory=[working_dir_templates, template_dir],
387
393
  engine=JinjaTemplateEngine,
388
394
  engine_callback=lambda engine: engine.engine.globals.update({
389
395
  "now": datetime.now,
@@ -394,10 +400,11 @@ def create_setup_app() -> Litestar:
394
400
  }),
395
401
  )
396
402
 
397
- # Static files
403
+ # Static files - working directory first for user overrides, then package directory
404
+ working_dir_static = Path(os.getcwd()) / "static"
398
405
  static_files_router = create_static_files_router(
399
406
  path="/static",
400
- directories=[Path(__file__).parent / "static"],
407
+ directories=[working_dir_static, Path(__file__).parent / "static"],
401
408
  )
402
409
 
403
410
  # Import controllers
@@ -0,0 +1,143 @@
1
+ """CLI commands for Skrift."""
2
+
3
+ import base64
4
+ import os
5
+ import re
6
+ import secrets
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import click
11
+
12
+
13
+ @click.group()
14
+ @click.version_option(package_name="skrift")
15
+ def cli():
16
+ """Skrift - A lightweight async Python CMS."""
17
+ pass
18
+
19
+
20
+ @cli.command()
21
+ @click.option("--host", default="127.0.0.1", help="Host to bind to")
22
+ @click.option("--port", default=8080, type=int, help="Port to bind to")
23
+ @click.option("--reload", is_flag=True, help="Enable auto-reload for development")
24
+ @click.option("--workers", default=1, type=int, help="Number of worker processes")
25
+ @click.option(
26
+ "--log-level",
27
+ default="info",
28
+ type=click.Choice(["debug", "info", "warning", "error"]),
29
+ help="Logging level",
30
+ )
31
+ def serve(host, port, reload, workers, log_level):
32
+ """Run the Skrift server."""
33
+ import uvicorn
34
+
35
+ uvicorn.run(
36
+ "skrift.asgi:app",
37
+ host=host,
38
+ port=port,
39
+ reload=reload,
40
+ workers=workers if not reload else 1,
41
+ log_level=log_level,
42
+ )
43
+
44
+
45
+ @cli.command()
46
+ @click.option(
47
+ "--write",
48
+ type=click.Path(),
49
+ default=None,
50
+ help="Write SECRET_KEY to a .env file",
51
+ )
52
+ @click.option(
53
+ "--format",
54
+ "fmt",
55
+ default="urlsafe",
56
+ type=click.Choice(["urlsafe", "hex", "base64"]),
57
+ help="Output format for the secret key",
58
+ )
59
+ @click.option("--length", default=32, type=int, help="Number of random bytes")
60
+ def secret(write, fmt, length):
61
+ """Generate a secure secret key."""
62
+ # Generate key based on format
63
+ if fmt == "urlsafe":
64
+ key = secrets.token_urlsafe(length)
65
+ elif fmt == "hex":
66
+ key = secrets.token_hex(length)
67
+ else: # base64
68
+ key = base64.b64encode(secrets.token_bytes(length)).decode("ascii")
69
+
70
+ if write:
71
+ env_path = Path(write)
72
+ env_content = ""
73
+
74
+ # Read existing content if file exists
75
+ if env_path.exists():
76
+ env_content = env_path.read_text()
77
+
78
+ # Update or add SECRET_KEY
79
+ secret_key_pattern = re.compile(r"^SECRET_KEY=.*$", re.MULTILINE)
80
+ new_line = f"SECRET_KEY={key}"
81
+
82
+ if secret_key_pattern.search(env_content):
83
+ # Replace existing SECRET_KEY
84
+ env_content = secret_key_pattern.sub(new_line, env_content)
85
+ else:
86
+ # Add SECRET_KEY at the end
87
+ if env_content and not env_content.endswith("\n"):
88
+ env_content += "\n"
89
+ env_content += new_line + "\n"
90
+
91
+ env_path.write_text(env_content)
92
+ click.echo(f"SECRET_KEY written to {env_path}")
93
+ else:
94
+ click.echo(key)
95
+
96
+
97
+ @cli.command(
98
+ context_settings=dict(
99
+ ignore_unknown_options=True,
100
+ allow_extra_args=True,
101
+ )
102
+ )
103
+ @click.pass_context
104
+ def db(ctx):
105
+ """Run database migrations via Alembic.
106
+
107
+ \b
108
+ Examples:
109
+ skrift db upgrade head # Apply all migrations
110
+ skrift db downgrade -1 # Rollback one migration
111
+ skrift db current # Show current revision
112
+ skrift db history # Show migration history
113
+ skrift db revision -m "description" --autogenerate # Create new migration
114
+ """
115
+ from alembic.config import main as alembic_main
116
+
117
+ # Always run from the project root (where app.yaml and .env are)
118
+ # This ensures database paths like ./app.db resolve correctly
119
+ project_root = Path.cwd()
120
+ if not (project_root / "app.yaml").exists():
121
+ # If not in project root, try parent directory
122
+ project_root = Path(__file__).parent.parent
123
+ os.chdir(project_root)
124
+
125
+ # Find alembic.ini - check project root first, then skrift package directory
126
+ alembic_ini = project_root / "alembic.ini"
127
+ if not alembic_ini.exists():
128
+ skrift_dir = Path(__file__).parent
129
+ alembic_ini = skrift_dir / "alembic.ini"
130
+
131
+ if not alembic_ini.exists():
132
+ click.echo("Error: Could not find alembic.ini", err=True)
133
+ click.echo("Make sure you're running from the project root directory.", err=True)
134
+ sys.exit(1)
135
+
136
+ # Build argv for alembic: ['-c', '/path/to/alembic.ini', ...extra_args]
137
+ alembic_argv = ["-c", str(alembic_ini)] + ctx.args
138
+
139
+ sys.exit(alembic_main(alembic_argv))
140
+
141
+
142
+ if __name__ == "__main__":
143
+ cli()
@@ -1,17 +0,0 @@
1
- """Entry point for the skrift package."""
2
-
3
- import uvicorn
4
-
5
-
6
- def main():
7
- """Run the Skrift development server."""
8
- uvicorn.run(
9
- "skrift.asgi:app",
10
- host="0.0.0.0",
11
- port=8080,
12
- reload=True,
13
- )
14
-
15
-
16
- if __name__ == "__main__":
17
- main()
@@ -1,54 +0,0 @@
1
- """CLI commands for Skrift database management."""
2
-
3
- import sys
4
- from pathlib import Path
5
-
6
-
7
- def db() -> None:
8
- """Run Alembic database migrations.
9
-
10
- This is a thin wrapper around Alembic that sets up the correct working
11
- directory and passes through all arguments.
12
-
13
- Usage:
14
- skrift-db upgrade head # Apply all migrations
15
- skrift-db downgrade -1 # Rollback one migration
16
- skrift-db current # Show current revision
17
- skrift-db history # Show migration history
18
- skrift-db revision -m "description" --autogenerate # Create new migration
19
- """
20
- from alembic.config import main as alembic_main
21
-
22
- import os
23
-
24
- # Always run from the project root (where app.yaml and .env are)
25
- # This ensures database paths like ./app.db resolve correctly
26
- project_root = Path.cwd()
27
- if not (project_root / "app.yaml").exists():
28
- # If not in project root, try parent directory
29
- project_root = Path(__file__).parent.parent
30
- os.chdir(project_root)
31
-
32
- # Find alembic.ini - check project root first, then skrift package directory
33
- alembic_ini = project_root / "alembic.ini"
34
- if not alembic_ini.exists():
35
- skrift_dir = Path(__file__).parent
36
- alembic_ini = skrift_dir / "alembic.ini"
37
-
38
- if not alembic_ini.exists():
39
- print("Error: Could not find alembic.ini", file=sys.stderr)
40
- print("Make sure you're running from the project root directory.", file=sys.stderr)
41
- sys.exit(1)
42
-
43
- # Build argv with config path at the beginning (before any subcommand)
44
- # Original argv: ['skrift-db', 'upgrade', 'head']
45
- # New argv: ['skrift-db', '-c', '/path/to/alembic.ini', 'upgrade', 'head']
46
- new_argv = [sys.argv[0], "-c", str(alembic_ini)] + sys.argv[1:]
47
- sys.argv = new_argv
48
-
49
- # Pass through all CLI arguments to Alembic
50
- sys.exit(alembic_main(sys.argv[1:]))
51
-
52
-
53
- if __name__ == "__main__":
54
- db()
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