├── .gitignore ├── LICENSE ├── README.md ├── alembic.ini ├── app ├── __init__.py ├── db.py ├── main.py └── models.py ├── migrations ├── README ├── env.py ├── script.py.mako └── versions │ ├── 27a6b90d4f0f_remove_genre_column_in_film_table.py │ ├── 7a3822d18836_add_relation_genre_to_film.py │ ├── 8dbf1cdc38f7_create_genre_table.py │ └── cc2a39b8bf11_init.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Viqi Nurhaqiqi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI with SQLModel and PostgresSQL 2 | 3 | ## Introduction 4 | Example FastAPI with [SQLModel by fouder itself, tiangolo](https://sqlmodel.tiangolo.com/), connected to PostgreSQL. As reference, this tutorial by Michael Herman about [fastapi-sqlmodel](https://testdriven.io/blog/fastapi-sqlmodel/) and I made some tweaks from there. 5 | 6 | ## Installment 7 | #### clone the project 8 | 9 | ```bash 10 | git clone https://github.com/vnurhaqiqi/fastapi-sqlmodel-postgresql.git 11 | ``` 12 | 13 | #### run virtual env 14 | 15 | ```bash 16 | source /your-env/bin/activate -> Linux 17 | 18 | your-env\Scripts\activate.bat -> Windows 19 | ``` 20 | 21 | #### migrate models using alembic 22 | 23 | ```bash 24 | alembic upgrade head 25 | ``` 26 | 27 | #### run project using uvicorn 28 | 29 | ```bash 30 | uvicorn main:app --reload 31 | ``` 32 | 33 | --- 34 | Copyright © 2021 by Viqi Nurhaqiqi -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = migrations 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # sys.path path, will be prepended to sys.path if present. 11 | # defaults to the current working directory. 12 | prepend_sys_path = . 13 | 14 | # timezone to use when rendering the date within the migration file 15 | # as well as the filename. 16 | # If specified, requires the python-dateutil library that can be 17 | # installed by adding `alembic[tz]` to the pip requirements 18 | # string value is passed to dateutil.tz.gettz() 19 | # leave blank for localtime 20 | # timezone = 21 | 22 | # max length of characters to apply to the 23 | # "slug" field 24 | # truncate_slug_length = 40 25 | 26 | # set to 'true' to run the environment during 27 | # the 'revision' command, regardless of autogenerate 28 | # revision_environment = false 29 | 30 | # set to 'true' to allow .pyc and .pyo files without 31 | # a source .py file to be detected as revisions in the 32 | # versions/ directory 33 | # sourceless = false 34 | 35 | # version location specification; This defaults 36 | # to migrations/versions. When using multiple version 37 | # directories, initial revisions must be specified with --version-path. 38 | # The path separator used here should be the separator specified by "version_path_separator" 39 | # version_locations = %(here)s/bar:%(here)s/bat:migrations/versions 40 | 41 | # version path separator; As mentioned above, this is the character used to split 42 | # version_locations. Valid values are: 43 | # 44 | # version_path_separator = : 45 | # version_path_separator = ; 46 | # version_path_separator = space 47 | version_path_separator = os # default: use os.pathsep 48 | 49 | # the output encoding used when revision files 50 | # are written from script.py.mako 51 | # output_encoding = utf-8 52 | 53 | sqlalchemy.url = your_database_connection 54 | 55 | 56 | [post_write_hooks] 57 | # post_write_hooks defines scripts or Python functions that are run 58 | # on newly generated revision scripts. See the documentation for further 59 | # detail and examples 60 | 61 | # format using "black" - use the console_scripts runner, against the "black" entrypoint 62 | # hooks = black 63 | # black.type = console_scripts 64 | # black.entrypoint = black 65 | # black.options = -l 79 REVISION_SCRIPT_FILENAME 66 | 67 | # Logging configuration 68 | [loggers] 69 | keys = root,sqlalchemy,alembic 70 | 71 | [handlers] 72 | keys = console 73 | 74 | [formatters] 75 | keys = generic 76 | 77 | [logger_root] 78 | level = WARN 79 | handlers = console 80 | qualname = 81 | 82 | [logger_sqlalchemy] 83 | level = WARN 84 | handlers = 85 | qualname = sqlalchemy.engine 86 | 87 | [logger_alembic] 88 | level = INFO 89 | handlers = 90 | qualname = alembic 91 | 92 | [handler_console] 93 | class = StreamHandler 94 | args = (sys.stderr,) 95 | level = NOTSET 96 | formatter = generic 97 | 98 | [formatter_generic] 99 | format = %(levelname)-5.5s [%(name)s] %(message)s 100 | datefmt = %H:%M:%S 101 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vnurhaqiqi/fastapi-sqlmodel-postgresql/1b5e2bc55c305ac8bbe7d2c5df6d694c7afa7401/app/__init__.py -------------------------------------------------------------------------------- /app/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlmodel import create_engine, SQLModel, Session 4 | 5 | # DATABASE_URL = os.environ.get("") 6 | DATABASE_URL = "YOUR-DATABASE-NAME" 7 | engine = create_engine(DATABASE_URL, echo=True) 8 | 9 | 10 | def init_db(): 11 | SQLModel.metadata.create_all(engine) 12 | 13 | 14 | def get_session(): 15 | with Session(engine) as session: 16 | yield session 17 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Depends, HTTPException 2 | from sqlalchemy import select 3 | from sqlmodel import Session 4 | 5 | from db import init_db, get_session 6 | from models import * 7 | 8 | app = FastAPI() 9 | 10 | 11 | # @app.on_event("startup") 12 | # def on_startup(): 13 | # init_db() 14 | 15 | 16 | @app.get("/") 17 | async def index(): 18 | return {"Hello": "World"} 19 | 20 | 21 | @app.get("/films", response_model=List[Film]) 22 | def get_films(*, session: Session = Depends(get_session)): 23 | films = session.exec(select(Film)).scalars().all() 24 | 25 | return films 26 | 27 | 28 | @app.post("/films", response_model=Film) 29 | def create_film(*, session: Session = Depends(get_session), film: FilmCreate): 30 | add_film = Film.from_orm(film) 31 | session.add(add_film) 32 | session.commit() 33 | session.refresh(add_film) 34 | 35 | return add_film 36 | 37 | 38 | @app.get("/films/{film_id}", response_model=Film) 39 | def get_film_by_id(*, session: Session = Depends(get_session), film_id: int): 40 | film = session.get(Film, film_id) 41 | 42 | if not film: 43 | raise HTTPException(status_code=404, detail="Film not found.") 44 | 45 | return film 46 | 47 | 48 | @app.delete("/films/{film_id}") 49 | def delete_film_by_id(*, session: Session = Depends(get_session), film_id: int): 50 | film = session.get(Film, film_id) 51 | 52 | if not film: 53 | raise HTTPException(status_code=404, detail="Film not found.") 54 | 55 | session.delete(film) 56 | session.commit() 57 | 58 | return {"Success"} 59 | 60 | 61 | @app.patch("/films/{film_id}", response_model=Film) 62 | def update_film_by_id(*, session: Session = Depends(get_session), film_id: int, film: FilmUpdate): 63 | film_obj = session.get(Film, film_id) 64 | 65 | if not film_obj: 66 | raise HTTPException(status_code=404, detail="Film not found.") 67 | 68 | film_data = film.dict(exclude_unset=True) 69 | for key, value in film_data.items(): 70 | setattr(film_obj, key, value) 71 | 72 | session.add(film_obj) 73 | session.commit() 74 | session.refresh(film_obj) 75 | 76 | return film_obj 77 | 78 | 79 | @app.get("/genres", response_model=List[Genre]) 80 | def get_genres(*, session: Session = Depends(get_session)): 81 | genres = session.exec(select(Genre)).scalars().all() 82 | 83 | return genres 84 | 85 | 86 | @app.post("/genres", response_model=Genre) 87 | def create_genre(*, session: Session = Depends(get_session), genre: GenreCreate): 88 | add_genre = Genre.from_orm(genre) 89 | 90 | session.add(add_genre) 91 | session.commit() 92 | session.refresh(add_genre) 93 | 94 | return add_genre 95 | 96 | 97 | @app.get("/genres/{genre_id}", response_model=Genre) 98 | def get_genre_by_id(*, session: Session = Depends(get_session), genre_id: int): 99 | genre = session.get(Genre, genre_id) 100 | 101 | if not genre: 102 | raise HTTPException(status_code=404, detail="Genre not found.") 103 | 104 | return genre 105 | 106 | 107 | @app.delete("/genres/{genre_id}") 108 | def delete_genre_by_id(*, session: Session = Depends(get_session), genre_id: int): 109 | genre = session.get(Genre, genre_id) 110 | 111 | if not genre: 112 | raise HTTPException(status_code=404, detail="Genre not found.") 113 | 114 | session.delete(genre) 115 | session.commit() 116 | 117 | return {"Success"} 118 | 119 | 120 | @app.patch("/genres/{genre_id}", response_model=Genre) 121 | def update_genre_by_id(*, session: Session = Depends(get_session), genre_id: int, genre: GenreUpdate): 122 | genre_obj = session.get(Genre, genre_id) 123 | 124 | if not genre_obj: 125 | raise HTTPException(status_code=404, detail="Genre not found.") 126 | 127 | genre_data = genre.dict(exclude_unset=True) 128 | for key, value in genre_data.items(): 129 | setattr(genre_obj, key, value) 130 | 131 | session.add(genre_obj) 132 | session.commit() 133 | session.refresh(genre_obj) 134 | 135 | return genre_obj 136 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import SQLModel, Field, Relationship 2 | from typing import Optional, List 3 | 4 | 5 | class FilmBase(SQLModel): 6 | name: str 7 | released_year: int 8 | director: str 9 | 10 | genre_id: Optional[int] = Field(default=None, foreign_key="genre.id") 11 | 12 | 13 | class Film(FilmBase, table=True): 14 | id: int = Field(default=None, primary_key=True) 15 | 16 | genre: Optional["Genre"] = Relationship(back_populates="films") 17 | 18 | 19 | class FilmCreate(FilmBase): 20 | pass 21 | 22 | 23 | class FilmUpdate(SQLModel): 24 | name: Optional[str] = None 25 | released_year: Optional[int] = None 26 | director: Optional[str] = None 27 | genre_id: Optional[int] = None 28 | 29 | 30 | class GenreBase(SQLModel): 31 | name: str 32 | 33 | 34 | class Genre(GenreBase, table=True): 35 | id: int = Field(default=None, primary_key=True) 36 | 37 | films: List[Film] = Relationship(back_populates="genre") 38 | 39 | 40 | class GenreCreate(GenreBase): 41 | pass 42 | 43 | 44 | class GenreUpdate(SQLModel): 45 | name: Optional[str] 46 | -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from logging.config import fileConfig 2 | 3 | from sqlalchemy import engine_from_config 4 | from sqlalchemy import pool 5 | from sqlmodel import SQLModel 6 | 7 | from alembic import context 8 | 9 | from app.models import * 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | 19 | # add your model's MetaData object here 20 | # for 'autogenerate' support 21 | # from myapp import mymodel 22 | # target_metadata = mymodel.Base.metadata 23 | target_metadata = SQLModel.metadata 24 | 25 | # other values from the config, defined by the needs of env.py, 26 | # can be acquired: 27 | # my_important_option = config.get_main_option("my_important_option") 28 | # ... etc. 29 | 30 | 31 | def run_migrations_offline(): 32 | """Run migrations in 'offline' mode. 33 | 34 | This configures the context with just a URL 35 | and not an Engine, though an Engine is acceptable 36 | here as well. By skipping the Engine creation 37 | we don't even need a DBAPI to be available. 38 | 39 | Calls to context.execute() here emit the given string to the 40 | script output. 41 | 42 | """ 43 | url = config.get_main_option("sqlalchemy.url") 44 | context.configure( 45 | url=url, 46 | target_metadata=target_metadata, 47 | literal_binds=True, 48 | dialect_opts={"paramstyle": "named"}, 49 | ) 50 | 51 | with context.begin_transaction(): 52 | context.run_migrations() 53 | 54 | 55 | def run_migrations_online(): 56 | """Run migrations in 'online' mode. 57 | 58 | In this scenario we need to create an Engine 59 | and associate a connection with the context. 60 | 61 | """ 62 | connectable = engine_from_config( 63 | config.get_section(config.config_ini_section), 64 | prefix="sqlalchemy.", 65 | poolclass=pool.NullPool, 66 | ) 67 | 68 | with connectable.connect() as connection: 69 | context.configure( 70 | connection=connection, target_metadata=target_metadata 71 | ) 72 | 73 | with context.begin_transaction(): 74 | context.run_migrations() 75 | 76 | 77 | if context.is_offline_mode(): 78 | run_migrations_offline() 79 | else: 80 | run_migrations_online() 81 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/27a6b90d4f0f_remove_genre_column_in_film_table.py: -------------------------------------------------------------------------------- 1 | """remove genre column in film table 2 | 3 | Revision ID: 27a6b90d4f0f 4 | Revises: cc2a39b8bf11 5 | Create Date: 2021-10-28 14:54:31.910831 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '27a6b90d4f0f' 14 | down_revision = 'cc2a39b8bf11' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_index('ix_film_genre', table_name='film') 22 | op.drop_column('film', 'genre') 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.add_column('film', sa.Column('genre', sa.VARCHAR(), autoincrement=False, nullable=True)) 29 | op.create_index('ix_film_genre', 'film', ['genre'], unique=False) 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /migrations/versions/7a3822d18836_add_relation_genre_to_film.py: -------------------------------------------------------------------------------- 1 | """add relation genre to film 2 | 3 | Revision ID: 7a3822d18836 4 | Revises: 8dbf1cdc38f7 5 | Create Date: 2021-10-29 13:13:22.050185 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = '7a3822d18836' 13 | down_revision = '8dbf1cdc38f7' 14 | branch_labels = None 15 | depends_on = None 16 | 17 | 18 | def upgrade(): 19 | # ### commands auto generated by Alembic - please adjust! ### 20 | op.add_column('film', sa.Column('genre_id', sa.Integer(), nullable=True)) 21 | op.create_index(op.f('ix_film_genre_id'), 'film', ['genre_id'], unique=False) 22 | op.create_foreign_key(None, 'film', 'genre', ['genre_id'], ['id']) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_constraint(None, 'film', type_='foreignkey') 29 | op.drop_index(op.f('ix_film_genre_id'), table_name='film') 30 | op.drop_column('film', 'genre_id') 31 | # ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /migrations/versions/8dbf1cdc38f7_create_genre_table.py: -------------------------------------------------------------------------------- 1 | """create genre table 2 | 3 | Revision ID: 8dbf1cdc38f7 4 | Revises: 27a6b90d4f0f 5 | Create Date: 2021-10-29 10:32:22.037285 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = '8dbf1cdc38f7' 13 | down_revision = '27a6b90d4f0f' 14 | branch_labels = None 15 | depends_on = None 16 | 17 | 18 | def upgrade(): 19 | # ### commands auto generated by Alembic - please adjust! ### 20 | op.create_table('genre', 21 | sa.Column('name', sa.String(), nullable=False), 22 | sa.Column('id', sa.Integer(), nullable=True), 23 | sa.PrimaryKeyConstraint('id') 24 | ) 25 | op.create_index(op.f('ix_genre_id'), 'genre', ['id'], unique=False) 26 | op.create_index(op.f('ix_genre_name'), 'genre', ['name'], unique=False) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_index(op.f('ix_genre_name'), table_name='genre') 33 | op.drop_index(op.f('ix_genre_id'), table_name='genre') 34 | op.drop_table('genre') 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /migrations/versions/cc2a39b8bf11_init.py: -------------------------------------------------------------------------------- 1 | """init 2 | 3 | Revision ID: cc2a39b8bf11 4 | Revises: 5 | Create Date: 2021-10-28 14:46:20.943795 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = 'cc2a39b8bf11' 13 | down_revision = None 14 | branch_labels = None 15 | depends_on = None 16 | 17 | 18 | def upgrade(): 19 | # ### commands auto generated by Alembic - please adjust! ### 20 | op.drop_table('song') 21 | op.create_index(op.f('ix_film_director'), 'film', ['director'], unique=False) 22 | op.create_index(op.f('ix_film_genre'), 'film', ['genre'], unique=False) 23 | op.create_index(op.f('ix_film_id'), 'film', ['id'], unique=False) 24 | op.create_index(op.f('ix_film_name'), 'film', ['name'], unique=False) 25 | op.create_index(op.f('ix_film_released_year'), 'film', ['released_year'], unique=False) 26 | # ### end Alembic commands ### 27 | 28 | 29 | def downgrade(): 30 | # ### commands auto generated by Alembic - please adjust! ### 31 | op.drop_index(op.f('ix_film_released_year'), table_name='film') 32 | op.drop_index(op.f('ix_film_name'), table_name='film') 33 | op.drop_index(op.f('ix_film_id'), table_name='film') 34 | op.drop_index(op.f('ix_film_genre'), table_name='film') 35 | op.drop_index(op.f('ix_film_director'), table_name='film') 36 | op.alter_column('film', 'id', 37 | existing_type=sa.INTEGER(), 38 | nullable=False, 39 | autoincrement=True) 40 | op.create_table('song', 41 | sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=False), 42 | sa.Column('released_year', sa.INTEGER(), autoincrement=False, nullable=False), 43 | sa.Column('director', sa.VARCHAR(), autoincrement=False, nullable=False), 44 | sa.Column('genre', sa.VARCHAR(), autoincrement=False, nullable=True), 45 | sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), 46 | sa.PrimaryKeyConstraint('id', name='song_pkey') 47 | ) 48 | # ### end Alembic commands ### 49 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.7.4 2 | click==7.1.2 3 | fastapi==0.63.0 4 | greenlet==1.0.0 5 | h11==0.12.0 6 | importlib-metadata==4.8.1 7 | importlib-resources==5.3.0 8 | Mako==1.1.5 9 | MarkupSafe==2.0.1 10 | psycopg2-binary==2.9.1 11 | pydantic==1.8.2 12 | SQLAlchemy==1.4.26 13 | sqlalchemy2-stubs==0.0.2a18 14 | sqlmodel==0.0.4 15 | starlette==0.13.6 16 | typing-extensions==3.7.4.3 17 | uvicorn==0.13.4 18 | zipp==3.6.0 19 | --------------------------------------------------------------------------------