Skip to content

Commit 96876e8

Browse files
authored
Merge pull request #53 from bugout-dev/entry-lock
Entry lock
2 parents d9000b0 + 9066ecf commit 96876e8

12 files changed

Lines changed: 428 additions & 222 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Entry lock
2+
3+
Revision ID: f909b4acb52f
4+
Revises: 245914e1ddf9
5+
Create Date: 2022-09-05 10:25:46.047276
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'f909b4acb52f'
14+
down_revision = '245914e1ddf9'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('journal_entry_locks',
22+
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
23+
sa.Column('journal_entry_id', postgresql.UUID(as_uuid=True), nullable=True),
24+
sa.Column('locked_by', sa.String(), nullable=False),
25+
sa.Column('locked_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False),
26+
sa.ForeignKeyConstraint(['journal_entry_id'], ['journal_entries.id'], name=op.f('fk_journal_entry_locks_journal_entry_id_journal_entries'), ondelete='CASCADE'),
27+
sa.PrimaryKeyConstraint('id', name=op.f('pk_journal_entry_locks')),
28+
sa.UniqueConstraint('id', name=op.f('uq_journal_entry_locks_id')),
29+
sa.UniqueConstraint('journal_entry_id', name=op.f('uq_journal_entry_locks_journal_entry_id'))
30+
)
31+
op.create_index(op.f('ix_journal_entry_locks_locked_at'), 'journal_entry_locks', ['locked_at'], unique=False)
32+
op.create_index(op.f('ix_journal_entry_locks_locked_by'), 'journal_entry_locks', ['locked_by'], unique=False)
33+
op.alter_column('journal_ttls', 'journal_id',
34+
existing_type=postgresql.UUID(),
35+
nullable=True)
36+
# ### end Alembic commands ###
37+
38+
39+
def downgrade():
40+
# ### commands auto generated by Alembic - please adjust! ###
41+
op.alter_column('journal_ttls', 'journal_id',
42+
existing_type=postgresql.UUID(),
43+
nullable=False)
44+
op.drop_index(op.f('ix_journal_entry_locks_locked_by'), table_name='journal_entry_locks')
45+
op.drop_index(op.f('ix_journal_entry_locks_locked_at'), table_name='journal_entry_locks')
46+
op.drop_table('journal_entry_locks')
47+
# ### end Alembic commands ###

sample.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export BUGOUT_SLACK_CLIENT_SECRET="<slack client secret>"
44
export BUGOUT_SLACK_SIGNING_SECRET="<slack signing secret>"
55
export BUGOUT_SLACK_VERIFICATION_TOKEN="<slack verification token>"
66
export SPIRE_DB_URI="postgresql://<username>:<password>@<db_host>/<db_name>"
7+
export SPIRE_DB_URI_READ_ONLY="postgresql://<username>:<password>@<db_host>/<db_name>"
78
export BUGOUT_OAUTH_COMPLETION_URL="https://bugout.dev"
89
export BUGOUT_WEB_URL="https://bugout.dev"
910
export BUGOUT_AUTH_URL="http://localhost:7474"
@@ -37,7 +38,7 @@ export BUGOUT_BOT_INSTALLATION_TOKEN_HEADER="<bugout installation token header>"
3738
export BUGOUT_DRONES_TOKEN="<auth internal drones token>"
3839
export BUGOUT_DRONES_TOKEN_HEADER="<auth internal drones token header>"
3940
export SPIRE_OPENAPI_LIST="journals,humbug,preferences,public,go"
40-
export BUGOUT_REDIS_URL="redis.andrey:6379"
41+
export BUGOUT_REDIS_URL="http://127.0.0.1:6379"
4142
export BUGOUT_REDIS_PASSWORD="mypassword"
4243
export REDIS_REPORTS_QUEUE="<redis key to humbug reports queue>"
4344
export BUGOUT_HUMBUG_REDIS_TIMEOUT="0.5"

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@
7272
],
7373
"distribute": ["setuptools", "twine", "wheel"],
7474
},
75+
entry_points={"console_scripts": ["journals=spire.journal.cli:main"]},
7576
)

spire/db.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
Spire database connection
33
"""
44
from contextlib import contextmanager
5-
from datetime import time
6-
import os
5+
from typing import Optional
76

87
import redis
98
from sqlalchemy import create_engine
109
from sqlalchemy.orm import sessionmaker, Session
11-
from sqlalchemy.sql.expression import true
1210

1311
from .utils.settings import (
12+
SPIRE_DB_URI,
13+
SPIRE_DB_URI_READ_ONLY,
14+
SPIRE_DB_POOL_RECYCLE_SECONDS,
15+
SPIRE_DB_STATEMENT_TIMEOUT_MILLIS,
1416
BUGOUT_SPIRE_THREAD_DB_POOL_SIZE,
1517
BUGOUT_SPIRE_THREAD_DB_MAX_OVERFLOW,
1618
BUGOUT_REDIS_URL,
@@ -19,14 +21,31 @@
1921
BUGOUT_HUMBUG_REDIS_CONNECTIONS_PER_PROCESS,
2022
)
2123

22-
connection_str = os.environ.get("SPIRE_DB_URI")
23-
if connection_str is None:
24-
raise ValueError("SPIRE_DB_URI environment variable not set")
2524

26-
engine = create_engine(
27-
connection_str,
25+
def create_spire_engine(
26+
url: Optional[str],
27+
pool_size: int,
28+
max_overflow: int,
29+
statement_timeout: int,
30+
pool_recycle: int = SPIRE_DB_POOL_RECYCLE_SECONDS,
31+
):
32+
# Pooling: https://docs.sqlalchemy.org/en/14/core/pooling.html#sqlalchemy.pool.QueuePool
33+
# Statement timeout: https://stackoverflow.com/a/44936982
34+
return create_engine(
35+
url=url,
36+
pool_size=pool_size,
37+
pool_recycle=pool_recycle,
38+
max_overflow=max_overflow,
39+
connect_args={"options": f"-c statement_timeout={statement_timeout}"},
40+
)
41+
42+
43+
engine = create_spire_engine(
44+
url=SPIRE_DB_URI,
2845
pool_size=BUGOUT_SPIRE_THREAD_DB_POOL_SIZE,
2946
max_overflow=BUGOUT_SPIRE_THREAD_DB_MAX_OVERFLOW,
47+
statement_timeout=SPIRE_DB_STATEMENT_TIMEOUT_MILLIS,
48+
pool_recycle=SPIRE_DB_POOL_RECYCLE_SECONDS,
3049
)
3150
SessionLocal = sessionmaker(bind=engine)
3251

@@ -43,6 +62,31 @@ def yield_connection_from_env() -> Session:
4362
session.close()
4463

4564

65+
# Read only database
66+
RO_engine = create_spire_engine(
67+
url=SPIRE_DB_URI_READ_ONLY,
68+
pool_size=BUGOUT_SPIRE_THREAD_DB_POOL_SIZE,
69+
max_overflow=BUGOUT_SPIRE_THREAD_DB_MAX_OVERFLOW,
70+
statement_timeout=SPIRE_DB_STATEMENT_TIMEOUT_MILLIS,
71+
pool_recycle=SPIRE_DB_POOL_RECYCLE_SECONDS,
72+
)
73+
RO_SessionLocal = sessionmaker(bind=RO_engine)
74+
75+
76+
def yield_db_read_only_session() -> Session:
77+
"""
78+
Yields read only database connection (created using environment variables).
79+
As per FastAPI docs:
80+
https://fastapi.tiangolo.com/tutorial/sql-databases/#create-a-dependency
81+
"""
82+
session = RO_SessionLocal()
83+
try:
84+
yield session
85+
finally:
86+
session.close()
87+
88+
89+
# Redis
4690
RedisPool = redis.ConnectionPool.from_url(
4791
f"redis://:{BUGOUT_REDIS_PASSWORD}@{BUGOUT_REDIS_URL}",
4892
max_connections=BUGOUT_HUMBUG_REDIS_CONNECTIONS_PER_PROCESS,

0 commit comments

Comments
 (0)