skrift 0.1.0a4__py3-none-any.whl → 0.1.0a6__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.
skrift/__main__.py CHANGED
@@ -1,16 +1,11 @@
1
1
  """Entry point for the skrift package."""
2
2
 
3
- import uvicorn
3
+ from skrift.cli import cli
4
4
 
5
5
 
6
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
- )
7
+ """Run the Skrift CLI."""
8
+ cli()
14
9
 
15
10
 
16
11
  if __name__ == "__main__":
skrift/asgi.py CHANGED
@@ -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
skrift/cli.py CHANGED
@@ -1,26 +1,119 @@
1
- """CLI commands for Skrift database management."""
1
+ """CLI commands for Skrift."""
2
2
 
3
+ import base64
4
+ import os
5
+ import re
6
+ import secrets
3
7
  import sys
4
8
  from pathlib import Path
5
9
 
10
+ import click
6
11
 
7
- def db() -> None:
8
- """Run Alembic database migrations.
9
12
 
10
- This is a thin wrapper around Alembic that sets up the correct working
11
- directory and passes through all arguments.
13
+ @click.group()
14
+ @click.version_option(package_name="skrift")
15
+ def cli():
16
+ """Skrift - A lightweight async Python CMS."""
17
+ pass
12
18
 
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
+ @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
19
114
  """
20
115
  from alembic.config import main as alembic_main
21
116
 
22
- import os
23
-
24
117
  # Always run from the project root (where app.yaml and .env are)
25
118
  # This ensures database paths like ./app.db resolve correctly
26
119
  project_root = Path.cwd()
@@ -36,19 +129,15 @@ def db() -> None:
36
129
  alembic_ini = skrift_dir / "alembic.ini"
37
130
 
38
131
  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)
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)
41
134
  sys.exit(1)
42
135
 
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
136
+ # Build argv for alembic: ['-c', '/path/to/alembic.ini', ...extra_args]
137
+ alembic_argv = ["-c", str(alembic_ini)] + ctx.args
48
138
 
49
- # Pass through all CLI arguments to Alembic
50
- sys.exit(alembic_main(sys.argv[1:]))
139
+ sys.exit(alembic_main(alembic_argv))
51
140
 
52
141
 
53
142
  if __name__ == "__main__":
54
- db()
143
+ cli()
@@ -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,8 +1,8 @@
1
1
  skrift/__init__.py,sha256=eXE5PFVkJpH5XsV_ZlrTIeFPUPrmcHYAj4GpRS3R5PY,29
2
- skrift/__main__.py,sha256=Fs17xxkqTjZpJn9MMC6pzkW4sOzNhV5RGLsA2ONPFww,271
2
+ skrift/__main__.py,sha256=wt6JZL9nBhKU36vdyurhOEtWy7w3C9zohyy24PLcKho,164
3
3
  skrift/alembic.ini,sha256=mYguI6CbMCTyfHctsGiTyf9Z5gv21FdeI3qtfgOHO3A,1815
4
- skrift/asgi.py,sha256=U-9p_JlWlwjrhKJzAlBHpQ3DvTB2uIcvZe3-5Gs87Ec,19248
5
- skrift/cli.py,sha256=DB_MssHeBAvpm7DVgd3V-BQr5BjBqSO0O_0vm15PZJ8,1941
4
+ skrift/asgi.py,sha256=PQRC1tyPT4iYNbrTusvLlLQ9rbsDp8w8V2scsNWM7qY,19874
5
+ skrift/cli.py,sha256=aT-7pXvOuuZC-Eypx1h-xCiqaBKhIFjSqd5Ky_dHna0,4214
6
6
  skrift/config.py,sha256=_fXfdRdy03yJWUhijthC4cNgOXLJ0jhB__C-VwVYaxc,7398
7
7
  skrift/admin/__init__.py,sha256=x81Cj_ilVmv6slaMl16HHyT_AgrnLxKEWkS0RPa4V9s,289
8
8
  skrift/admin/controller.py,sha256=5ZDypvKHXLNDESsKNsdsH2E3Si5OqlpzttFl7Ot8aF0,15651
@@ -66,7 +66,7 @@ skrift/templates/setup/configuring.html,sha256=2KHW9h2BrJgL_kO5IizbAYs4pnFLyRf76
66
66
  skrift/templates/setup/database.html,sha256=gU4-315-QraHa2Eq4Fh3b55QpOM2CkJzh27_Yz13frA,5495
67
67
  skrift/templates/setup/restart.html,sha256=GHg31F_e2uLFhWUzJoalk0Y0oYLqsFWyZXWKX3mblbY,1355
68
68
  skrift/templates/setup/site.html,sha256=PSOH-q1-ZBl47iSW9-Ad6lEfJn_fzdGD3Pk4vb3xgK4,1680
69
- skrift-0.1.0a4.dist-info/METADATA,sha256=4fREc0NuDDDf5aPruF_rmqwPOBzMDsBF12N9lB4CFPw,6435
70
- skrift-0.1.0a4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
71
- skrift-0.1.0a4.dist-info/entry_points.txt,sha256=4AIrmbeWKOdZnvTsKT3US6N3X9rrgk9jEDsYOPEZ1AE,74
72
- skrift-0.1.0a4.dist-info/RECORD,,
69
+ skrift-0.1.0a6.dist-info/METADATA,sha256=TAXajlBybDa-iLbvVsiERUoP9xrjDj636dNpE2auDto,6435
70
+ skrift-0.1.0a6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
71
+ skrift-0.1.0a6.dist-info/entry_points.txt,sha256=uquZ5Mumqr0xwYTpTcNiJtFSITGfF6_QCCy2DZJSZig,42
72
+ skrift-0.1.0a6.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ skrift = skrift.cli:cli
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- skrift = skrift.__main__:main
3
- skrift-db = skrift.cli:db