skrift 0.1.0a12__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.
Files changed (74) hide show
  1. skrift/__init__.py +1 -0
  2. skrift/__main__.py +12 -0
  3. skrift/admin/__init__.py +11 -0
  4. skrift/admin/controller.py +452 -0
  5. skrift/admin/navigation.py +105 -0
  6. skrift/alembic/env.py +92 -0
  7. skrift/alembic/script.py.mako +26 -0
  8. skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +70 -0
  9. skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +57 -0
  10. skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +31 -0
  11. skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +43 -0
  12. skrift/alembic/versions/20260122_200000_add_settings_table.py +38 -0
  13. skrift/alembic/versions/20260129_add_oauth_accounts.py +141 -0
  14. skrift/alembic/versions/20260129_add_provider_metadata.py +29 -0
  15. skrift/alembic.ini +77 -0
  16. skrift/asgi.py +670 -0
  17. skrift/auth/__init__.py +58 -0
  18. skrift/auth/guards.py +130 -0
  19. skrift/auth/roles.py +129 -0
  20. skrift/auth/services.py +184 -0
  21. skrift/cli.py +143 -0
  22. skrift/config.py +259 -0
  23. skrift/controllers/__init__.py +4 -0
  24. skrift/controllers/auth.py +595 -0
  25. skrift/controllers/web.py +67 -0
  26. skrift/db/__init__.py +3 -0
  27. skrift/db/base.py +7 -0
  28. skrift/db/models/__init__.py +7 -0
  29. skrift/db/models/oauth_account.py +50 -0
  30. skrift/db/models/page.py +26 -0
  31. skrift/db/models/role.py +56 -0
  32. skrift/db/models/setting.py +13 -0
  33. skrift/db/models/user.py +36 -0
  34. skrift/db/services/__init__.py +1 -0
  35. skrift/db/services/oauth_service.py +195 -0
  36. skrift/db/services/page_service.py +217 -0
  37. skrift/db/services/setting_service.py +206 -0
  38. skrift/lib/__init__.py +3 -0
  39. skrift/lib/exceptions.py +168 -0
  40. skrift/lib/template.py +108 -0
  41. skrift/setup/__init__.py +14 -0
  42. skrift/setup/config_writer.py +213 -0
  43. skrift/setup/controller.py +888 -0
  44. skrift/setup/middleware.py +89 -0
  45. skrift/setup/providers.py +214 -0
  46. skrift/setup/state.py +315 -0
  47. skrift/static/css/style.css +1003 -0
  48. skrift/templates/admin/admin.html +19 -0
  49. skrift/templates/admin/base.html +24 -0
  50. skrift/templates/admin/pages/edit.html +32 -0
  51. skrift/templates/admin/pages/list.html +62 -0
  52. skrift/templates/admin/settings/site.html +32 -0
  53. skrift/templates/admin/users/list.html +58 -0
  54. skrift/templates/admin/users/roles.html +42 -0
  55. skrift/templates/auth/dummy_login.html +102 -0
  56. skrift/templates/auth/login.html +139 -0
  57. skrift/templates/base.html +52 -0
  58. skrift/templates/error-404.html +19 -0
  59. skrift/templates/error-500.html +19 -0
  60. skrift/templates/error.html +19 -0
  61. skrift/templates/index.html +9 -0
  62. skrift/templates/page.html +26 -0
  63. skrift/templates/setup/admin.html +24 -0
  64. skrift/templates/setup/auth.html +110 -0
  65. skrift/templates/setup/base.html +407 -0
  66. skrift/templates/setup/complete.html +17 -0
  67. skrift/templates/setup/configuring.html +158 -0
  68. skrift/templates/setup/database.html +125 -0
  69. skrift/templates/setup/restart.html +28 -0
  70. skrift/templates/setup/site.html +39 -0
  71. skrift-0.1.0a12.dist-info/METADATA +235 -0
  72. skrift-0.1.0a12.dist-info/RECORD +74 -0
  73. skrift-0.1.0a12.dist-info/WHEEL +4 -0
  74. skrift-0.1.0a12.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,26 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: Union[str, None] = ${repr(down_revision)}
17
+ branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18
+ depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1,70 @@
1
+ """initial schema
2
+
3
+ Revision ID: 09b0364dbb7b
4
+ Revises:
5
+ Create Date: 2026-01-20 21:01:54.470260
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from advanced_alchemy.types import GUID, DateTimeUTC
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = '09b0364dbb7b'
17
+ down_revision: Union[str, None] = None
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # Create users table
24
+ op.create_table(
25
+ 'users',
26
+ sa.Column('id', GUID(length=16), nullable=False),
27
+ sa.Column('created_at', DateTimeUTC(timezone=True), nullable=False),
28
+ sa.Column('updated_at', DateTimeUTC(timezone=True), nullable=False),
29
+ sa.Column('oauth_provider', sa.String(length=50), nullable=False),
30
+ sa.Column('oauth_id', sa.String(length=255), nullable=False),
31
+ sa.Column('email', sa.String(length=255), nullable=False),
32
+ sa.Column('name', sa.String(length=255), nullable=True),
33
+ sa.Column('picture_url', sa.String(length=512), nullable=True),
34
+ sa.Column('is_active', sa.Boolean(), nullable=False, default=True),
35
+ sa.Column('last_login_at', sa.DateTime(timezone=True), nullable=True),
36
+ sa.PrimaryKeyConstraint('id'),
37
+ sa.UniqueConstraint('oauth_id'),
38
+ sa.UniqueConstraint('email'),
39
+ )
40
+
41
+ # Create pages table
42
+ op.create_table(
43
+ 'pages',
44
+ sa.Column('id', GUID(length=16), nullable=False),
45
+ sa.Column('created_at', DateTimeUTC(timezone=True), nullable=False),
46
+ sa.Column('updated_at', DateTimeUTC(timezone=True), nullable=False),
47
+ sa.Column('user_id', GUID(length=16), nullable=True),
48
+ sa.Column('type', sa.String(length=50), nullable=False, default='page'),
49
+ sa.Column('slug', sa.String(length=255), nullable=False),
50
+ sa.Column('title', sa.String(length=500), nullable=False),
51
+ sa.Column('content', sa.Text(), nullable=False, default=''),
52
+ sa.Column('is_published', sa.Boolean(), nullable=False, default=False),
53
+ sa.Column('published_at', sa.DateTime(timezone=True), nullable=True),
54
+ sa.PrimaryKeyConstraint('id'),
55
+ sa.ForeignKeyConstraint(['user_id'], ['users.id']),
56
+ sa.UniqueConstraint('slug'),
57
+ )
58
+
59
+ # Create indexes
60
+ op.create_index('ix_pages_slug', 'pages', ['slug'])
61
+ op.create_index('ix_pages_user_id', 'pages', ['user_id'])
62
+ op.create_index('ix_pages_type_published', 'pages', ['type', 'is_published'])
63
+
64
+
65
+ def downgrade() -> None:
66
+ op.drop_index('ix_pages_type_published', 'pages')
67
+ op.drop_index('ix_pages_user_id', 'pages')
68
+ op.drop_index('ix_pages_slug', 'pages')
69
+ op.drop_table('pages')
70
+ op.drop_table('users')
@@ -0,0 +1,57 @@
1
+ """add_roles_and_permissions
2
+
3
+ Revision ID: 0b7c927d2591
4
+ Revises: 09b0364dbb7b
5
+ Create Date: 2026-01-22 15:27:44.922770
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ import advanced_alchemy.types
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = '0b7c927d2591'
17
+ down_revision: Union[str, None] = '09b0364dbb7b'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table('roles',
24
+ sa.Column('id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
25
+ sa.Column('name', sa.String(length=50), nullable=False),
26
+ sa.Column('display_name', sa.String(length=100), nullable=True),
27
+ sa.Column('description', sa.String(length=500), nullable=True),
28
+ sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True),
29
+ sa.Column('created_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
30
+ sa.Column('updated_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
31
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_roles')),
32
+ sa.UniqueConstraint('name', name=op.f('uq_roles_name'))
33
+ )
34
+ op.create_table('role_permissions',
35
+ sa.Column('id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
36
+ sa.Column('role_id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
37
+ sa.Column('permission', sa.String(length=100), nullable=False),
38
+ sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True),
39
+ sa.Column('created_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
40
+ sa.Column('updated_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
41
+ sa.ForeignKeyConstraint(['role_id'], ['roles.id'], name=op.f('fk_role_permissions_role_id_roles'), ondelete='CASCADE'),
42
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_role_permissions')),
43
+ sa.UniqueConstraint('role_id', 'permission', name='uq_role_permission')
44
+ )
45
+ op.create_table('user_roles',
46
+ sa.Column('user_id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
47
+ sa.Column('role_id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
48
+ sa.ForeignKeyConstraint(['role_id'], ['roles.id'], name=op.f('fk_user_roles_role_id_roles'), ondelete='CASCADE'),
49
+ sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_user_roles_user_id_users'), ondelete='CASCADE'),
50
+ sa.PrimaryKeyConstraint('user_id', 'role_id', name=op.f('pk_user_roles'))
51
+ )
52
+
53
+
54
+ def downgrade() -> None:
55
+ op.drop_table('user_roles')
56
+ op.drop_table('role_permissions')
57
+ op.drop_table('roles')
@@ -0,0 +1,31 @@
1
+ """add sa_orm_sentinel column
2
+
3
+ Revision ID: cdf734a5b847
4
+ Revises: 0b7c927d2591
5
+ Create Date: 2026-01-22 17:28:36.586660
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'cdf734a5b847'
16
+ down_revision: Union[str, None] = '0b7c927d2591'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # Add sa_orm_sentinel column to users table
23
+ op.add_column('users', sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True))
24
+
25
+ # Add sa_orm_sentinel column to pages table
26
+ op.add_column('pages', sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True))
27
+
28
+
29
+ def downgrade() -> None:
30
+ op.drop_column('pages', 'sa_orm_sentinel')
31
+ op.drop_column('users', 'sa_orm_sentinel')
@@ -0,0 +1,43 @@
1
+ """remove page type column
2
+
3
+ Revision ID: a9c55348eae7
4
+ Revises: cdf734a5b847
5
+ Create Date: 2026-01-22 17:56:37.000000
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'a9c55348eae7'
16
+ down_revision: Union[str, None] = 'cdf734a5b847'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # Delete any posts (convert to pages not needed since we're removing posts entirely)
23
+ op.execute("DELETE FROM pages WHERE type = 'post'")
24
+
25
+ # Drop the composite index on type and is_published
26
+ op.drop_index('ix_pages_type_published', table_name='pages')
27
+
28
+ # Drop the type column
29
+ op.drop_column('pages', 'type')
30
+
31
+
32
+ def downgrade() -> None:
33
+ # Add the type column back
34
+ op.add_column('pages', sa.Column('type', sa.String(50), nullable=True))
35
+
36
+ # Set all existing pages to type 'page'
37
+ op.execute("UPDATE pages SET type = 'page'")
38
+
39
+ # Make the column non-nullable
40
+ op.alter_column('pages', 'type', nullable=False)
41
+
42
+ # Recreate the composite index
43
+ op.create_index('ix_pages_type_published', 'pages', ['type', 'is_published'])
@@ -0,0 +1,38 @@
1
+ """add settings table
2
+
3
+ Revision ID: 8f3a5c2d1e0b
4
+ Revises: a9c55348eae7
5
+ Create Date: 2026-01-22 20:00:00.000000
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ import advanced_alchemy.types
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = '8f3a5c2d1e0b'
17
+ down_revision: Union[str, None] = 'a9c55348eae7'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table('settings',
24
+ sa.Column('id', advanced_alchemy.types.guid.GUID(length=16), nullable=False),
25
+ sa.Column('key', sa.String(length=255), nullable=False),
26
+ sa.Column('value', sa.Text(), nullable=True),
27
+ sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True),
28
+ sa.Column('created_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
29
+ sa.Column('updated_at', advanced_alchemy.types.datetime.DateTimeUTC(timezone=True), nullable=False),
30
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_settings')),
31
+ sa.UniqueConstraint('key', name=op.f('uq_settings_key'))
32
+ )
33
+ op.create_index(op.f('ix_settings_key'), 'settings', ['key'], unique=True)
34
+
35
+
36
+ def downgrade() -> None:
37
+ op.drop_index(op.f('ix_settings_key'), table_name='settings')
38
+ op.drop_table('settings')
@@ -0,0 +1,141 @@
1
+ """add oauth_accounts table
2
+
3
+ Revision ID: 1a2b3c4d5e6f
4
+ Revises: 8f3a5c2d1e0b
5
+ Create Date: 2026-01-29 10:00:00.000000
6
+
7
+ This migration:
8
+ 1. Creates the oauth_accounts table to store multiple OAuth identities per user
9
+ 2. Migrates existing oauth data from users table to oauth_accounts
10
+ 3. Makes users.email nullable (for providers like Twitter that don't provide email)
11
+ 4. Removes oauth_provider and oauth_id columns from users table
12
+ """
13
+ from typing import Sequence, Union
14
+
15
+ from alembic import op
16
+ import sqlalchemy as sa
17
+ from advanced_alchemy.types import GUID, DateTimeUTC
18
+
19
+
20
+ # revision identifiers, used by Alembic.
21
+ revision: str = '1a2b3c4d5e6f'
22
+ down_revision: Union[str, None] = '8f3a5c2d1e0b'
23
+ branch_labels: Union[str, Sequence[str], None] = None
24
+ depends_on: Union[str, Sequence[str], None] = None
25
+
26
+
27
+ def upgrade() -> None:
28
+ # Step 1: Create oauth_accounts table
29
+ op.create_table(
30
+ 'oauth_accounts',
31
+ sa.Column('id', GUID(length=16), nullable=False),
32
+ sa.Column('created_at', DateTimeUTC(timezone=True), nullable=False),
33
+ sa.Column('updated_at', DateTimeUTC(timezone=True), nullable=False),
34
+ sa.Column('sa_orm_sentinel', sa.Integer(), nullable=True),
35
+ sa.Column('provider', sa.String(length=50), nullable=False),
36
+ sa.Column('provider_account_id', sa.String(length=255), nullable=False),
37
+ sa.Column('provider_email', sa.String(length=255), nullable=True),
38
+ sa.Column('user_id', GUID(length=16), nullable=False),
39
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_oauth_accounts')),
40
+ sa.ForeignKeyConstraint(
41
+ ['user_id'], ['users.id'],
42
+ name=op.f('fk_oauth_accounts_user_id_users'),
43
+ ondelete='CASCADE'
44
+ ),
45
+ sa.UniqueConstraint(
46
+ 'provider', 'provider_account_id',
47
+ name='uq_oauth_provider_account'
48
+ ),
49
+ )
50
+ op.create_index(
51
+ op.f('ix_oauth_accounts_user_id'),
52
+ 'oauth_accounts',
53
+ ['user_id'],
54
+ unique=False
55
+ )
56
+ op.create_index(
57
+ op.f('ix_oauth_accounts_provider_account'),
58
+ 'oauth_accounts',
59
+ ['provider', 'provider_account_id'],
60
+ unique=True
61
+ )
62
+
63
+ # Step 2: Migrate existing data from users to oauth_accounts
64
+ # Generate binary UUIDs (16 bytes) for new records and copy oauth data
65
+ conn = op.get_bind()
66
+ dialect = conn.dialect.name
67
+
68
+ if dialect == 'sqlite':
69
+ uuid_func = 'randomblob(16)'
70
+ else: # PostgreSQL and others
71
+ uuid_func = 'gen_random_uuid()'
72
+
73
+ conn.execute(sa.text(f"""
74
+ INSERT INTO oauth_accounts (id, created_at, updated_at, provider, provider_account_id, provider_email, user_id)
75
+ SELECT
76
+ {uuid_func},
77
+ created_at,
78
+ updated_at,
79
+ oauth_provider,
80
+ oauth_id,
81
+ email,
82
+ id
83
+ FROM users
84
+ WHERE oauth_provider IS NOT NULL AND oauth_id IS NOT NULL
85
+ """))
86
+
87
+ # Step 3: Make email nullable on users table
88
+ # SQLite doesn't support ALTER COLUMN, so we need to recreate the table
89
+ # For SQLite, we'll use batch_alter_table
90
+ with op.batch_alter_table('users', schema=None) as batch_op:
91
+ # Drop the unique constraint on oauth_id
92
+ batch_op.drop_constraint('uq_users_oauth_id', type_='unique')
93
+ # Drop the oauth columns
94
+ batch_op.drop_column('oauth_provider')
95
+ batch_op.drop_column('oauth_id')
96
+ # Make email nullable - this requires recreating the column in SQLite
97
+ batch_op.alter_column('email',
98
+ existing_type=sa.String(length=255),
99
+ nullable=True)
100
+
101
+
102
+ def downgrade() -> None:
103
+ # Step 1: Add back oauth columns to users table
104
+ with op.batch_alter_table('users', schema=None) as batch_op:
105
+ batch_op.add_column(sa.Column('oauth_provider', sa.String(length=50), nullable=True))
106
+ batch_op.add_column(sa.Column('oauth_id', sa.String(length=255), nullable=True))
107
+ batch_op.alter_column('email',
108
+ existing_type=sa.String(length=255),
109
+ nullable=False)
110
+
111
+ # Step 2: Migrate data back from oauth_accounts to users
112
+ # Only migrate the first oauth account per user
113
+ conn = op.get_bind()
114
+ conn.execute(sa.text("""
115
+ UPDATE users
116
+ SET oauth_provider = (
117
+ SELECT provider FROM oauth_accounts
118
+ WHERE oauth_accounts.user_id = users.id
119
+ LIMIT 1
120
+ ),
121
+ oauth_id = (
122
+ SELECT provider_account_id FROM oauth_accounts
123
+ WHERE oauth_accounts.user_id = users.id
124
+ LIMIT 1
125
+ )
126
+ """))
127
+
128
+ # Step 3: Make oauth columns non-nullable and add unique constraint
129
+ with op.batch_alter_table('users', schema=None) as batch_op:
130
+ batch_op.alter_column('oauth_provider',
131
+ existing_type=sa.String(length=50),
132
+ nullable=False)
133
+ batch_op.alter_column('oauth_id',
134
+ existing_type=sa.String(length=255),
135
+ nullable=False)
136
+ batch_op.create_unique_constraint('uq_users_oauth_id', ['oauth_id'])
137
+
138
+ # Step 4: Drop oauth_accounts table
139
+ op.drop_index(op.f('ix_oauth_accounts_provider_account'), table_name='oauth_accounts')
140
+ op.drop_index(op.f('ix_oauth_accounts_user_id'), table_name='oauth_accounts')
141
+ op.drop_table('oauth_accounts')
@@ -0,0 +1,29 @@
1
+ """add provider_metadata column to oauth_accounts
2
+
3
+ Revision ID: 2b3c4d5e6f7g
4
+ Revises: 1a2b3c4d5e6f
5
+ Create Date: 2026-01-29 12:00:00.000000
6
+
7
+ This migration adds a JSON column to store the full raw OAuth provider response.
8
+ """
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+ import sqlalchemy as sa
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = '2b3c4d5e6f7g'
17
+ down_revision: Union[str, None] = '1a2b3c4d5e6f'
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ with op.batch_alter_table('oauth_accounts') as batch_op:
24
+ batch_op.add_column(sa.Column('provider_metadata', sa.JSON(), nullable=True))
25
+
26
+
27
+ def downgrade() -> None:
28
+ with op.batch_alter_table('oauth_accounts') as batch_op:
29
+ batch_op.drop_column('provider_metadata')
skrift/alembic.ini ADDED
@@ -0,0 +1,77 @@
1
+ # Alembic Configuration File
2
+
3
+ [alembic]
4
+ # Path to migration scripts (relative to this file)
5
+ script_location = %(here)s/alembic
6
+
7
+ # Template used to generate migration files
8
+ file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d%%(second).2d_%%(rev)s_%%(slug)s
9
+
10
+ # Prepend sys.path with this value before running migrations
11
+ prepend_sys_path = .
12
+
13
+ # Timezone for timestamps in migration files (uses system timezone if not set)
14
+ # timezone =
15
+
16
+ # Max length of characters to apply to the "slug" field
17
+ truncate_slug_length = 40
18
+
19
+ # Set to 'true' to run the environment during revision generation (for autogenerate support)
20
+ revision_environment = false
21
+
22
+ # Set to 'true' to allow running in "offline" mode
23
+ # sourceless = false
24
+
25
+ # Version path separator (os, space, :, ;)
26
+ # version_path_separator = os
27
+
28
+ # Output encoding used when revision files are written from script.py.mako
29
+ # output_encoding = utf-8
30
+
31
+ # Database URL - override with environment variable DATABASE_URL
32
+ # The env.py file reads this from the Settings class, so this is just a placeholder
33
+ sqlalchemy.url = sqlite+aiosqlite:///./app.db
34
+
35
+
36
+ [post_write_hooks]
37
+ # Format newly generated revision files using ruff
38
+ # hooks = ruff_format
39
+ # ruff_format.type = exec
40
+ # ruff_format.executable = ruff
41
+ # ruff_format.options = format REVISION_SCRIPT_FILENAME
42
+
43
+
44
+ # Logging configuration
45
+ [loggers]
46
+ keys = root,sqlalchemy,alembic
47
+
48
+ [handlers]
49
+ keys = console
50
+
51
+ [formatters]
52
+ keys = generic
53
+
54
+ [logger_root]
55
+ level = WARN
56
+ handlers = console
57
+ qualname =
58
+
59
+ [logger_sqlalchemy]
60
+ level = WARN
61
+ handlers =
62
+ qualname = sqlalchemy.engine
63
+
64
+ [logger_alembic]
65
+ level = INFO
66
+ handlers =
67
+ qualname = alembic
68
+
69
+ [handler_console]
70
+ class = StreamHandler
71
+ args = (sys.stderr,)
72
+ level = NOTSET
73
+ formatter = generic
74
+
75
+ [formatter_generic]
76
+ format = %(levelname)-5.5s [%(name)s] %(message)s
77
+ datefmt = %H:%M:%S