diff --git a/Dockerfile b/Dockerfile index 4ed8898..741e439 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,9 +29,11 @@ RUN apt-get purge -y curl git build-essential \ FROM install as app-image -COPY tests tests -COPY app app -COPY alembic alembic +ENV PYTHONPATH=/home/code/ PYTHONHASHSEED=0 + +COPY tests/ tests/ +COPY app/ app/ +COPY alembic/ alembic/ COPY .env alembic.ini ./ # create a non-root user and switch to it, for security. diff --git a/Makefile b/Makefile index 200b124..b5ee6e5 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,9 @@ migrate-apply: ## apply alembic migrations to database/schema docker-compose run --rm app alembic upgrade head .PHONY: migrate-create -migrate-create: ## create new alembic migration - docker-compose run --rm app alembic revision --autogenerate +migrate-create: ## Create new alembic database migration aka database revision. + docker-compose up -d db | true + docker-compose run --no-deps app alembic revision --autogenerate -m "$(msg)" .PHONY: test test: ## Run project tests diff --git a/alembic.ini b/alembic.ini index 64ef121..4783932 100644 --- a/alembic.ini +++ b/alembic.ini @@ -5,7 +5,7 @@ script_location = alembic # template used to generate migration files -# file_template = %%(rev)s_%%(slug)s +file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s # timezone to use when rendering the date # within the migration file as well as the filename. @@ -15,7 +15,7 @@ script_location = alembic # max length of characters to apply to the # "slug" field -#truncate_slug_length = 40 +# truncate_slug_length = 40 # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate @@ -27,14 +27,24 @@ script_location = alembic # sourceless = false # version location specification; this defaults -# to alembic/versions. When using multiple version +# to app/alembic/versions. When using multiple version # directories, initial revisions must be specified with --version-path -# version_locations = %(here)s/bar %(here)s/bat alembic/versions +# version_locations = %(here)s/bar %(here)s/bat app/alembic/versions # the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8 +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks=black +# black.type=console_scripts +# black.entrypoint=black +# black.options=-l 79 # Logging configuration [loggers] @@ -52,12 +62,12 @@ handlers = console qualname = [logger_sqlalchemy] -level = DEBUG +level = WARN handlers = qualname = sqlalchemy.engine [logger_alembic] -level = DEBUG +level = INFO handlers = qualname = alembic @@ -68,5 +78,5 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-8s %(thread)d <%(name)s> %(message)r +format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S diff --git a/alembic/env.py b/alembic/env.py index 85492a5..948701f 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,23 +1,22 @@ import asyncio -import os -import sys from alembic import context from sqlalchemy.ext.asyncio import create_async_engine -parent_dir = os.path.abspath(os.path.join(os.getcwd())) -sys.path.append(parent_dir) +from app.config import settings +from app.models.base import Base -from app.models.base import Base as app_base - -target_metadata = app_base.metadata +target_metadata = Base.metadata def do_run_migrations(connection): context.configure( + compare_type=True, + dialect_opts={"paramstyle": "named"}, connection=connection, target_metadata=target_metadata, include_schemas=True, + # literal_binds=True, version_table_schema=target_metadata.schema, ) @@ -32,8 +31,7 @@ async def run_migrations_online(): and associate a connection with the context. """ - url = f"postgresql+asyncpg://user:secret@db:5432/devdb" - connectable = create_async_engine(url) + connectable = create_async_engine(settings.asyncpg_url, future=True) async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) diff --git a/alembic/versions/20230311_2008_0d1ee3949d21_init_migrations.py b/alembic/versions/20230311_2008_0d1ee3949d21_init_migrations.py new file mode 100644 index 0000000..0691d00 --- /dev/null +++ b/alembic/versions/20230311_2008_0d1ee3949d21_init_migrations.py @@ -0,0 +1,121 @@ +"""init migrations + +Revision ID: 0d1ee3949d21 +Revises: +Create Date: 2023-03-11 20:08:07.362613 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '0d1ee3949d21' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('nonsense', + sa.Column('id', sa.UUID(), autoincrement=True, nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name'), + sa.UniqueConstraint('id'), + sa.UniqueConstraint('name'), + schema='happy_hog' + ) + op.create_table('stuff', + sa.Column('id', sa.UUID(), autoincrement=True, nullable=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('description', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('name'), + sa.UniqueConstraint('id'), + sa.UniqueConstraint('name'), + schema='happy_hog' + ) + op.create_table('character', + sa.Column('id', sa.String(length=32), nullable=False), + sa.Column('name', sa.String(length=64), nullable=False), + sa.Column('speech_count', sa.Integer(), nullable=False), + sa.Column('abbrev', sa.String(length=32), nullable=True), + sa.Column('description', sa.String(length=2056), nullable=True), + sa.PrimaryKeyConstraint('id', name='character_pkey'), + schema='shakespeare' + ) + op.create_table('wordform', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('plain_text', sa.String(length=64), nullable=False), + sa.Column('phonetic_text', sa.String(length=64), nullable=False), + sa.Column('stem_text', sa.String(length=64), nullable=False), + sa.Column('occurences', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id', name='wordform_pkey'), + schema='shakespeare' + ) + op.create_table('work', + sa.Column('id', sa.String(length=32), nullable=False), + sa.Column('title', sa.String(length=32), nullable=False), + sa.Column('long_title', sa.String(length=64), nullable=False), + sa.Column('year', sa.Integer(), nullable=False), + sa.Column('genre_type', sa.String(length=1), nullable=False), + sa.Column('source', sa.String(length=16), nullable=False), + sa.Column('total_words', sa.Integer(), nullable=False), + sa.Column('total_paragraphs', sa.Integer(), nullable=False), + sa.Column('notes', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('id', name='work_pkey'), + schema='shakespeare' + ) + op.create_table('chapter', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('work_id', sa.String(length=32), nullable=False), + sa.Column('section_number', sa.Integer(), nullable=False), + sa.Column('chapter_number', sa.Integer(), nullable=False), + sa.Column('description', sa.String(length=256), nullable=False), + sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='chapter_work_id_fkey'), + sa.PrimaryKeyConstraint('id', name='chapter_pkey'), + sa.UniqueConstraint('work_id', 'section_number', 'chapter_number', name='chapter_work_id_section_number_chapter_number_key'), + schema='shakespeare' + ) + op.create_table('character_work', + sa.Column('character_id', sa.String(length=32), nullable=False), + sa.Column('work_id', sa.String(length=32), nullable=False), + sa.ForeignKeyConstraint(['character_id'], ['shakespeare.character.id'], name='character_work_character_id_fkey'), + sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='character_work_work_id_fkey'), + sa.PrimaryKeyConstraint('character_id', 'work_id', name='character_work_pkey'), + schema='shakespeare' + ) + op.create_table('paragraph', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('work_id', sa.String(length=32), nullable=False), + sa.Column('paragraph_num', sa.Integer(), nullable=False), + sa.Column('character_id', sa.String(length=32), nullable=False), + sa.Column('plain_text', sa.Text(), nullable=False), + sa.Column('phonetic_text', sa.Text(), nullable=False), + sa.Column('stem_text', sa.Text(), nullable=False), + sa.Column('paragraph_type', sa.String(length=1), nullable=False), + sa.Column('section_number', sa.Integer(), nullable=False), + sa.Column('chapter_number', sa.Integer(), nullable=False), + sa.Column('char_count', sa.Integer(), nullable=False), + sa.Column('word_count', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['character_id'], ['shakespeare.character.id'], name='paragraph_character_id_fkey'), + sa.ForeignKeyConstraint(['work_id', 'section_number', 'chapter_number'], ['shakespeare.chapter.work_id', 'shakespeare.chapter.section_number', 'shakespeare.chapter.chapter_number'], name='paragraph_chapter_fkey'), + sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='paragraph_work_id_fkey'), + sa.PrimaryKeyConstraint('id', name='paragraph_pkey'), + schema='shakespeare' + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('paragraph', schema='shakespeare') + op.drop_table('character_work', schema='shakespeare') + op.drop_table('chapter', schema='shakespeare') + op.drop_table('work', schema='shakespeare') + op.drop_table('wordform', schema='shakespeare') + op.drop_table('character', schema='shakespeare') + op.drop_table('stuff', schema='happy_hog') + op.drop_table('nonsense', schema='happy_hog') + # ### end Alembic commands ### diff --git a/alembic/versions/957232a5b7ee_initialize_happy_cog_and_shakespeare_.py b/alembic/versions/957232a5b7ee_initialize_happy_cog_and_shakespeare_.py deleted file mode 100644 index 9fd2506..0000000 --- a/alembic/versions/957232a5b7ee_initialize_happy_cog_and_shakespeare_.py +++ /dev/null @@ -1,160 +0,0 @@ -"""initialize happy_cog and shakespeare tables - -Revision ID: 957232a5b7ee -Revises: -Create Date: 2022-06-04 13:02:45.839649 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = "957232a5b7ee" -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "nonsense", - sa.Column( - "id", postgresql.UUID(as_uuid=True), autoincrement=True, nullable=True - ), - sa.Column("name", sa.String(), nullable=False), - sa.Column("description", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("name"), - sa.UniqueConstraint("id"), - sa.UniqueConstraint("name"), - schema="happy_hog", - ) - op.create_table( - "stuff", - sa.Column( - "id", postgresql.UUID(as_uuid=True), autoincrement=True, nullable=True - ), - sa.Column("name", sa.String(), nullable=False), - sa.Column("description", sa.String(), nullable=False), - sa.PrimaryKeyConstraint("name"), - sa.UniqueConstraint("id"), - sa.UniqueConstraint("name"), - schema="happy_hog", - ) - op.create_table( - "character", - sa.Column("id", sa.String(length=32), nullable=False), - sa.Column("name", sa.String(length=64), nullable=False), - sa.Column("speech_count", sa.Integer(), nullable=False), - sa.Column("abbrev", sa.String(length=32), nullable=True), - sa.Column("description", sa.String(length=2056), nullable=True), - sa.PrimaryKeyConstraint("id", name="character_pkey"), - schema="shakespeare", - ) - op.create_table( - "wordform", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("plain_text", sa.String(length=64), nullable=False), - sa.Column("phonetic_text", sa.String(length=64), nullable=False), - sa.Column("stem_text", sa.String(length=64), nullable=False), - sa.Column("occurences", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("id", name="wordform_pkey"), - schema="shakespeare", - ) - op.create_table( - "work", - sa.Column("id", sa.String(length=32), nullable=False), - sa.Column("title", sa.String(length=32), nullable=False), - sa.Column("long_title", sa.String(length=64), nullable=False), - sa.Column("year", sa.Integer(), nullable=False), - sa.Column("genre_type", sa.String(length=1), nullable=False), - sa.Column("source", sa.String(length=16), nullable=False), - sa.Column("total_words", sa.Integer(), nullable=False), - sa.Column("total_paragraphs", sa.Integer(), nullable=False), - sa.Column("notes", sa.Text(), nullable=True), - sa.PrimaryKeyConstraint("id", name="work_pkey"), - schema="shakespeare", - ) - op.create_table( - "chapter", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("work_id", sa.String(length=32), nullable=False), - sa.Column("section_number", sa.Integer(), nullable=False), - sa.Column("chapter_number", sa.Integer(), nullable=False), - sa.Column("description", sa.String(length=256), nullable=False), - sa.ForeignKeyConstraint( - ["work_id"], ["shakespeare.work.id"], name="chapter_work_id_fkey" - ), - sa.PrimaryKeyConstraint("id", name="chapter_pkey"), - sa.UniqueConstraint( - "work_id", - "section_number", - "chapter_number", - name="chapter_work_id_section_number_chapter_number_key", - ), - schema="shakespeare", - ) - op.create_table( - "character_work", - sa.Column("character_id", sa.String(length=32), nullable=False), - sa.Column("work_id", sa.String(length=32), nullable=False), - sa.ForeignKeyConstraint( - ["character_id"], - ["shakespeare.character.id"], - name="character_work_character_id_fkey", - ), - sa.ForeignKeyConstraint( - ["work_id"], ["shakespeare.work.id"], name="character_work_work_id_fkey" - ), - sa.PrimaryKeyConstraint("character_id", "work_id", name="character_work_pkey"), - schema="shakespeare", - ) - op.create_table( - "paragraph", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("work_id", sa.String(length=32), nullable=False), - sa.Column("paragraph_num", sa.Integer(), nullable=False), - sa.Column("character_id", sa.String(length=32), nullable=False), - sa.Column("plain_text", sa.Text(), nullable=False), - sa.Column("phonetic_text", sa.Text(), nullable=False), - sa.Column("stem_text", sa.Text(), nullable=False), - sa.Column("paragraph_type", sa.String(length=1), nullable=False), - sa.Column("section_number", sa.Integer(), nullable=False), - sa.Column("chapter_number", sa.Integer(), nullable=False), - sa.Column("char_count", sa.Integer(), nullable=False), - sa.Column("word_count", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["character_id"], - ["shakespeare.character.id"], - name="paragraph_character_id_fkey", - ), - sa.ForeignKeyConstraint( - ["work_id", "section_number", "chapter_number"], - [ - "shakespeare.chapter.work_id", - "shakespeare.chapter.section_number", - "shakespeare.chapter.chapter_number", - ], - name="paragraph_chapter_fkey", - ), - sa.ForeignKeyConstraint( - ["work_id"], ["shakespeare.work.id"], name="paragraph_work_id_fkey" - ), - sa.PrimaryKeyConstraint("id", name="paragraph_pkey"), - schema="shakespeare", - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("paragraph", schema="shakespeare") - op.drop_table("character_work", schema="shakespeare") - op.drop_table("chapter", schema="shakespeare") - op.drop_table("work", schema="shakespeare") - op.drop_table("wordform", schema="shakespeare") - op.drop_table("character", schema="shakespeare") - op.drop_table("stuff", schema="happy_hog") - op.drop_table("nonsense", schema="happy_hog") - # ### end Alembic commands ### diff --git a/app/config.py b/app/config.py index 79edf02..096d9b6 100644 --- a/app/config.py +++ b/app/config.py @@ -18,3 +18,5 @@ class Settings(BaseSettings): @lru_cache def get_settings(): return Settings() + +settings = get_settings() diff --git a/docker-compose.yml b/docker-compose.yml index d0ef692..099f066 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: '3.9' services: app: @@ -16,6 +16,7 @@ services: volumes: - ./app:/home/code/app - ./tests:/home/code/tests + - ./alembic:/home/code/alembic ports: - "8080:8080" depends_on: