├── .env ├── .gitattributes ├── .vscode └── settings.json ├── Dockerfile ├── __pycache__ └── db_conf.cpython-39.pyc ├── alembic.ini ├── alembic ├── README ├── __pycache__ │ └── env.cpython-39.pyc ├── env.py ├── script.py.mako └── versions │ └── txt.txt.txt ├── commands.md ├── db_conf.py ├── docker-compose.yml ├── main.py ├── models.py ├── requirements.txt └── schemas.py /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql+psycopg2://postgres:password@db:5432/post_db 2 | DB_USER=postgres 3 | DB_PASSWORD=password 4 | DB_NAME=post_db 5 | PGADMIN_EMAIL=admin@admin.com 6 | PGADMIN_PASSWORD=admin -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "editor.formatOnSave": true, 4 | "python.pythonPath": "venv\\Scripts\\python.exe", 5 | "python.sortImports.args": [ 6 | "--profile=black", 7 | ], 8 | "python.formatting.provider": "black", 9 | "python.formatting.blackArgs": [ 10 | "--line-length=119" 11 | ], 12 | "[python]": { 13 | "editor.codeActionsOnSave": { 14 | "source.organizeImports": true 15 | } 16 | }, 17 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | ENV PYTHONUNBUFFERED=1 3 | WORKDIR /app 4 | COPY requirements.txt requirements.txt 5 | RUN pip3 install --upgrade pip 6 | RUN pip3 install -r requirements.txt 7 | COPY . /app 8 | EXPOSE 8000 -------------------------------------------------------------------------------- /__pycache__/db_conf.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT_FastAPI_Beginner_Fast-Track-GraphQL/e0cbb73e9f05d40ee6a11efda7d2b72159aec70e/__pycache__/db_conf.cpython-39.pyc -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | 2 | [alembic] 3 | script_location = alembic 4 | 5 | prepend_sys_path = . 6 | 7 | sqlalchemy.url = 8 | 9 | [post_write_hooks] 10 | 11 | [loggers] 12 | keys = root,sqlalchemy,alembic 13 | 14 | [handlers] 15 | keys = console 16 | 17 | [formatters] 18 | keys = generic 19 | 20 | [logger_root] 21 | level = WARN 22 | handlers = console 23 | qualname = 24 | 25 | [logger_sqlalchemy] 26 | level = WARN 27 | handlers = 28 | qualname = sqlalchemy.engine 29 | 30 | [logger_alembic] 31 | level = INFO 32 | handlers = 33 | qualname = alembic 34 | 35 | [handler_console] 36 | class = StreamHandler 37 | args = (sys.stderr,) 38 | level = NOTSET 39 | formatter = generic 40 | 41 | [formatter_generic] 42 | format = %(levelname)-5.5s [%(name)s] %(message)s 43 | datefmt = %H:%M:%S 44 | -------------------------------------------------------------------------------- /alembic/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /alembic/__pycache__/env.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT_FastAPI_Beginner_Fast-Track-GraphQL/e0cbb73e9f05d40ee6a11efda7d2b72159aec70e/alembic/__pycache__/env.cpython-39.pyc -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | import os 2 | from logging.config import fileConfig 3 | 4 | from dotenv import load_dotenv 5 | from sqlalchemy import engine_from_config, pool 6 | 7 | from alembic import context 8 | 9 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 10 | load_dotenv(os.path.join(BASE_DIR, ".env")) 11 | 12 | config = context.config 13 | 14 | config.set_main_option("sqlalchemy.url", os.environ["DATABASE_URL"]) 15 | 16 | fileConfig(config.config_file_name) 17 | 18 | import models 19 | 20 | target_metadata = models.Base.metadata 21 | 22 | 23 | def run_migrations_offline(): 24 | """Run migrations in 'offline' mode. 25 | 26 | This configures the context with just a URL 27 | and not an Engine, though an Engine is acceptable 28 | here as well. By skipping the Engine creation 29 | we don't even need a DBAPI to be available. 30 | 31 | Calls to context.execute() here emit the given string to the 32 | script output. 33 | 34 | """ 35 | url = config.get_main_option("sqlalchemy.url") 36 | context.configure( 37 | url=url, 38 | target_metadata=target_metadata, 39 | literal_binds=True, 40 | dialect_opts={"paramstyle": "named"}, 41 | ) 42 | 43 | with context.begin_transaction(): 44 | context.run_migrations() 45 | 46 | 47 | def run_migrations_online(): 48 | """Run migrations in 'online' mode. 49 | 50 | In this scenario we need to create an Engine 51 | and associate a connection with the context. 52 | 53 | """ 54 | connectable = engine_from_config( 55 | config.get_section(config.config_ini_section), 56 | prefix="sqlalchemy.", 57 | poolclass=pool.NullPool, 58 | ) 59 | 60 | with connectable.connect() as connection: 61 | context.configure(connection=connection, target_metadata=target_metadata) 62 | 63 | with context.begin_transaction(): 64 | context.run_migrations() 65 | 66 | 67 | if context.is_offline_mode(): 68 | run_migrations_offline() 69 | else: 70 | run_migrations_online() 71 | -------------------------------------------------------------------------------- /alembic/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 | -------------------------------------------------------------------------------- /alembic/versions/txt.txt.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT_FastAPI_Beginner_Fast-Track-GraphQL/e0cbb73e9f05d40ee6a11efda7d2b72159aec70e/alembic/versions/txt.txt.txt -------------------------------------------------------------------------------- /commands.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | fastapi uvicorn sqlalchemy graphene graphene-sqlalchemy alembic psycopg2 black python-dotenv 4 | 5 | alembic init alembic 6 | 7 | docker-compose run app alembic revision --autogenerate -m "New Migration" 8 | docker-compose run app alembic upgrade head 9 | 10 | mutation CreateNewPost{ 11 | createNewPost(title:"new title1", content:"new content") { 12 | ok 13 | } 14 | } 15 | 16 | query{ 17 | allPosts{ 18 | title 19 | } 20 | } 21 | 22 | query{ 23 | postById(postId:2){ 24 | id 25 | title 26 | content 27 | } 28 | } -------------------------------------------------------------------------------- /db_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.ext.declarative import declarative_base 6 | from sqlalchemy.orm import scoped_session, sessionmaker 7 | 8 | load_dotenv(".env") 9 | 10 | SQLALCHEMY_DATABASE_URL = os.environ["DATABASE_URL"] 11 | 12 | engine = create_engine( 13 | SQLALCHEMY_DATABASE_URL, 14 | ) 15 | 16 | db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) 17 | Base = declarative_base() 18 | Base.query = db_session.query_property() 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | 5 | db: 6 | container_name: postgresql_db 7 | image: postgres 8 | restart: always 9 | ports: 10 | - 5432:5432 11 | environment: 12 | - POSTGRES_USER=${DB_USER} 13 | - POSTGRES_PASSWORD=${DB_PASSWORD} 14 | - POSTGRES_DB=${DB_NAME} 15 | 16 | pgadmin: 17 | container_name: pgadmin 18 | image: dpage/pgadmin4 19 | environment: 20 | - PGADMIN_DEFAULT_EMAIL=${PGADMIN_EMAIL} 21 | - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PASSWORD} 22 | ports: 23 | - 5050:80 24 | depends_on: 25 | - db 26 | app: 27 | container_name: app 28 | build: . 29 | command: bash -c "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload" 30 | volumes: 31 | - .:/app 32 | ports: 33 | - 8000:8000 34 | restart: always 35 | depends_on: 36 | - db 37 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import graphene 2 | from fastapi import FastAPI 3 | from starlette.graphql import GraphQLApp 4 | 5 | import models 6 | from db_conf import db_session 7 | from schemas import PostModel, PostSchema 8 | 9 | db = db_session.session_factory() 10 | 11 | app = FastAPI() 12 | 13 | 14 | class Query(graphene.ObjectType): 15 | 16 | all_posts = graphene.List(PostModel) 17 | post_by_id = graphene.Field(PostModel, post_id=graphene.Int(required=True)) 18 | 19 | def resolve_all_posts(self, info): 20 | query = PostModel.get_query(info) 21 | return query.all() 22 | 23 | def resolve_post_by_id(self, info, post_id): 24 | return db.query(models.Post).filter(models.Post.id == post_id).first() 25 | 26 | 27 | class CreateNewPost(graphene.Mutation): 28 | class Arguments: 29 | title = graphene.String(required=True) 30 | content = graphene.String(required=True) 31 | 32 | ok = graphene.Boolean() 33 | 34 | @staticmethod 35 | def mutate(root, info, title, content): 36 | post = PostSchema(title=title, content=content) 37 | db_post = models.Post(title=post.title, content=post.content) 38 | db.add(db_post) 39 | db.commit() 40 | db.refresh(db_post) 41 | ok = True 42 | return CreateNewPost(ok=ok) 43 | 44 | 45 | class PostMutations(graphene.ObjectType): 46 | create_new_post = CreateNewPost.Field() 47 | 48 | 49 | app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query, mutation=PostMutations))) 50 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, DateTime, Integer, String 2 | from sqlalchemy.sql import func 3 | 4 | from db_conf import Base 5 | 6 | 7 | class Post(Base): 8 | __tablename__ = "post" 9 | 10 | id = Column(Integer, primary_key=True, index=True) 11 | title = Column(String) 12 | author = Column(String) 13 | content = Column(String) 14 | time_created = Column(DateTime(timezone=True), server_default=func.now()) 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.6.5 2 | aniso8601==7.0.0 3 | appdirs==1.4.4 4 | asgiref==3.4.1 5 | black==21.6b0 6 | click==8.0.1 7 | colorama==0.4.4 8 | fastapi==0.66.0 9 | graphene==2.1.8 10 | graphene-sqlalchemy==2.3.0 11 | graphql-core==2.3.2 12 | graphql-relay==2.0.1 13 | greenlet==1.1.0 14 | h11==0.12.0 15 | Mako==1.1.4 16 | MarkupSafe==2.0.1 17 | mypy-extensions==0.4.3 18 | pathspec==0.8.1 19 | promise==2.3 20 | psycopg2==2.9.1 21 | pydantic==1.8.2 22 | python-dateutil==2.8.1 23 | python-dotenv==0.18.0 24 | python-editor==1.0.4 25 | regex==2021.7.1 26 | Rx==1.6.1 27 | singledispatch==3.6.2 28 | six==1.16.0 29 | SQLAlchemy==1.4.20 30 | starlette==0.14.2 31 | toml==0.10.2 32 | typing-extensions==3.10.0.0 33 | uvicorn==0.14.0 34 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | from graphene_sqlalchemy import SQLAlchemyObjectType 2 | from pydantic import BaseModel 3 | 4 | from models import Post 5 | 6 | 7 | class PostSchema(BaseModel): 8 | title: str 9 | content: str 10 | 11 | 12 | class PostModel(SQLAlchemyObjectType): 13 | class Meta: 14 | model = Post 15 | --------------------------------------------------------------------------------