skrift 0.1.0a3__tar.gz → 0.1.0a5__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.0a3 → skrift-0.1.0a5}/PKG-INFO +1 -1
  2. {skrift-0.1.0a3 → skrift-0.1.0a5}/pyproject.toml +2 -3
  3. skrift-0.1.0a5/skrift/__main__.py +12 -0
  4. skrift-0.1.0a5/skrift/cli.py +143 -0
  5. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/config.py +2 -2
  6. skrift-0.1.0a3/skrift/__main__.py +0 -17
  7. skrift-0.1.0a3/skrift/cli.py +0 -54
  8. {skrift-0.1.0a3 → skrift-0.1.0a5}/.gitignore +0 -0
  9. {skrift-0.1.0a3 → skrift-0.1.0a5}/README.md +0 -0
  10. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/__init__.py +0 -0
  11. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/admin/__init__.py +0 -0
  12. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/admin/controller.py +0 -0
  13. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/admin/navigation.py +0 -0
  14. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/env.py +0 -0
  15. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/script.py.mako +0 -0
  16. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +0 -0
  17. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +0 -0
  18. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +0 -0
  19. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +0 -0
  20. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260122_200000_add_settings_table.py +0 -0
  21. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic/versions/20260129_add_oauth_accounts.py +0 -0
  22. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/alembic.ini +0 -0
  23. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/asgi.py +0 -0
  24. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/auth/__init__.py +0 -0
  25. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/auth/guards.py +0 -0
  26. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/auth/roles.py +0 -0
  27. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/auth/services.py +0 -0
  28. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/controllers/__init__.py +0 -0
  29. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/controllers/auth.py +0 -0
  30. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/controllers/web.py +0 -0
  31. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/__init__.py +0 -0
  32. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/base.py +0 -0
  33. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/__init__.py +0 -0
  34. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/oauth_account.py +0 -0
  35. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/page.py +0 -0
  36. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/role.py +0 -0
  37. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/setting.py +0 -0
  38. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/models/user.py +0 -0
  39. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/services/__init__.py +0 -0
  40. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/services/page_service.py +0 -0
  41. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/db/services/setting_service.py +0 -0
  42. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/lib/__init__.py +0 -0
  43. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/lib/exceptions.py +0 -0
  44. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/lib/template.py +0 -0
  45. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/__init__.py +0 -0
  46. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/config_writer.py +0 -0
  47. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/controller.py +0 -0
  48. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/middleware.py +0 -0
  49. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/providers.py +0 -0
  50. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/setup/state.py +0 -0
  51. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/static/css/style.css +0 -0
  52. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/admin.html +0 -0
  53. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/base.html +0 -0
  54. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/pages/edit.html +0 -0
  55. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/pages/list.html +0 -0
  56. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/settings/site.html +0 -0
  57. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/users/list.html +0 -0
  58. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/admin/users/roles.html +0 -0
  59. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/auth/dummy_login.html +0 -0
  60. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/auth/login.html +0 -0
  61. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/base.html +0 -0
  62. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/error-404.html +0 -0
  63. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/error-500.html +0 -0
  64. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/error.html +0 -0
  65. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/index.html +0 -0
  66. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/page.html +0 -0
  67. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/admin.html +0 -0
  68. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/auth.html +0 -0
  69. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/base.html +0 -0
  70. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/complete.html +0 -0
  71. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/configuring.html +0 -0
  72. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/database.html +0 -0
  73. {skrift-0.1.0a3 → skrift-0.1.0a5}/skrift/templates/setup/restart.html +0 -0
  74. {skrift-0.1.0a3 → skrift-0.1.0a5}/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.0a3
3
+ Version: 0.1.0a5
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.0a3"
3
+ version = "0.1.0a5"
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()
@@ -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()
@@ -9,8 +9,8 @@ from pydantic import BaseModel
9
9
  from pydantic_settings import BaseSettings, SettingsConfigDict
10
10
 
11
11
  # Load .env file early so env vars are available for YAML interpolation
12
- # Use explicit path to handle subprocess spawning (uvicorn workers)
13
- _env_file = Path(__file__).parent.parent / ".env"
12
+ # Load from current working directory (where app.yaml lives)
13
+ _env_file = Path.cwd() / ".env"
14
14
  load_dotenv(_env_file)
15
15
 
16
16
  # Pattern to match $VAR_NAME environment variable references
@@ -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