├── src └── api │ ├── __init__.py │ ├── database.py │ ├── schemas.py │ ├── models.py │ ├── services.py │ └── main.py ├── README.md ├── LICENSE └── .gitignore /src/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastapi-basics -------------------------------------------------------------------------------- /src/api/database.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy as _sql 2 | import sqlalchemy.ext.declarative as _declarative 3 | import sqlalchemy.orm as _orm 4 | 5 | SQLALCHEMY_DATABASE_URL = "sqlite:///./database.db" 6 | 7 | engine = _sql.create_engine( 8 | SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} 9 | ) 10 | 11 | SessionLocal = _orm.sessionmaker(autocommit=False, autoflush=False, bind=engine) 12 | 13 | Base = _declarative.declarative_base() -------------------------------------------------------------------------------- /src/api/schemas.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import datetime as _dt 3 | import pydantic as _pydantic 4 | 5 | 6 | class _PostBase(_pydantic.BaseModel): 7 | title: str 8 | content: str 9 | 10 | 11 | class PostCreate(_PostBase): 12 | pass 13 | 14 | 15 | class Post(_PostBase): 16 | id: int 17 | owner_id: int 18 | date_created: _dt.datetime 19 | date_last_updated: _dt.datetime 20 | 21 | class Config: 22 | orm_mode = True 23 | 24 | 25 | class _UserBase(_pydantic.BaseModel): 26 | email: str 27 | 28 | 29 | class UserCreate(_UserBase): 30 | password: str 31 | 32 | 33 | class User(_UserBase): 34 | id: int 35 | is_active: bool 36 | posts: List[Post] = [] 37 | 38 | class Config: 39 | orm_mode = True -------------------------------------------------------------------------------- /src/api/models.py: -------------------------------------------------------------------------------- 1 | import datetime as _dt 2 | import sqlalchemy as _sql 3 | import sqlalchemy.orm as _orm 4 | 5 | import database as _database 6 | 7 | 8 | class User(_database.Base): 9 | __tablename__ = "users" 10 | id = _sql.Column(_sql.Integer, primary_key=True, index=True) 11 | email = _sql.Column(_sql.String, unique=True, index=True) 12 | hashed_password = _sql.Column(_sql.String) 13 | is_active = _sql.Column(_sql.Boolean, default=True) 14 | 15 | posts = _orm.relationship("Post", back_populates="owner") 16 | 17 | 18 | class Post(_database.Base): 19 | __tablename__ = "posts" 20 | id = _sql.Column(_sql.Integer, primary_key=True, index=True) 21 | title = _sql.Column(_sql.String, index=True) 22 | content = _sql.Column(_sql.String, index=True) 23 | owner_id = _sql.Column(_sql.Integer, _sql.ForeignKey("users.id")) 24 | date_created = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow) 25 | date_last_updated = _sql.Column(_sql.DateTime, default=_dt.datetime.utcnow) 26 | 27 | owner = _orm.relationship("User", back_populates="posts") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Francis Ali 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 | -------------------------------------------------------------------------------- /src/api/services.py: -------------------------------------------------------------------------------- 1 | import sqlalchemy.orm as _orm 2 | 3 | import models as _models, schemas as _schemas, database as _database 4 | 5 | 6 | def create_database(): 7 | return _database.Base.metadata.create_all(bind=_database.engine) 8 | 9 | 10 | def get_db(): 11 | db = _database.SessionLocal() 12 | try: 13 | yield db 14 | finally: 15 | db.close() 16 | 17 | 18 | def get_user(db: _orm.Session, user_id: int): 19 | return db.query(_models.User).filter(_models.User.id == user_id).first() 20 | 21 | 22 | def get_user_by_email(db: _orm.Session, email: str): 23 | return db.query(_models.User).filter(_models.User.email == email).first() 24 | 25 | 26 | def get_users(db: _orm.Session, skip: int = 0, limit: int = 100): 27 | return db.query(_models.User).offset(skip).limit(limit).all() 28 | 29 | 30 | def create_user(db: _orm.Session, user: _schemas.UserCreate): 31 | fake_hashed_password = user.password + "thisisnotsecure" 32 | db_user = _models.User(email=user.email, hashed_password=fake_hashed_password) 33 | db.add(db_user) 34 | db.commit() 35 | db.refresh(db_user) 36 | return db_user 37 | 38 | 39 | def get_posts(db: _orm.Session, skip: int = 0, limit: int = 10): 40 | return db.query(_models.Post).offset(skip).limit(limit).all() 41 | 42 | 43 | def create_post(db: _orm.Session, post: _schemas.PostCreate, user_id: int): 44 | post = _models.Post(**post.dict(), owner_id=user_id) 45 | db.add(post) 46 | db.commit() 47 | db.refresh(post) 48 | return post 49 | 50 | 51 | def get_post(db: _orm.Session, post_id: int): 52 | return db.query(_models.Post).filter(_models.Post.id == post_id).first() 53 | 54 | 55 | def delete_post(db: _orm.Session, post_id: int): 56 | db.query(_models.Post).filter(_models.Post.id == post_id).delete() 57 | db.commit() 58 | 59 | 60 | def update_post(db: _orm.Session, post_id: int, post: _schemas.PostCreate): 61 | db_post = get_post(db=db, post_id=post_id) 62 | db_post.title = post.title 63 | db_post.content = post.content 64 | db.commit() 65 | db.refresh(db_post) 66 | return db_post -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 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 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | database.db -------------------------------------------------------------------------------- /src/api/main.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import fastapi as _fastapi 3 | import sqlalchemy.orm as _orm 4 | import services as _services, schemas as _schemas 5 | 6 | app = _fastapi.FastAPI() 7 | 8 | _services.create_database() 9 | 10 | 11 | @app.post("/users/", response_model=_schemas.User) 12 | def create_user( 13 | user: _schemas.UserCreate, db: _orm.Session = _fastapi.Depends(_services.get_db) 14 | ): 15 | db_user = _services.get_user_by_email(db=db, email=user.email) 16 | if db_user: 17 | raise _fastapi.HTTPException( 18 | status_code=400, detail="woops the email is in use" 19 | ) 20 | return _services.create_user(db=db, user=user) 21 | 22 | 23 | @app.get("/users/", response_model=List[_schemas.User]) 24 | def read_users( 25 | skip: int = 0, 26 | limit: int = 10, 27 | db: _orm.Session = _fastapi.Depends(_services.get_db), 28 | ): 29 | users = _services.get_users(db=db, skip=skip, limit=limit) 30 | return users 31 | 32 | 33 | @app.get("/users/{user_id}", response_model=_schemas.User) 34 | def read_user(user_id: int, db: _orm.Session = _fastapi.Depends(_services.get_db)): 35 | db_user = _services.get_user(db=db, user_id=user_id) 36 | if db_user is None: 37 | raise _fastapi.HTTPException( 38 | status_code=404, detail="sorry this user does not exist" 39 | ) 40 | return db_user 41 | 42 | 43 | @app.post("/users/{user_id}/posts/", response_model=_schemas.Post) 44 | def create_post( 45 | user_id: int, 46 | post: _schemas.PostCreate, 47 | db: _orm.Session = _fastapi.Depends(_services.get_db), 48 | ): 49 | db_user = _services.get_user(db=db, user_id=user_id) 50 | if db_user is None: 51 | raise _fastapi.HTTPException( 52 | status_code=404, detail="sorry this user does not exist" 53 | ) 54 | return _services.create_post(db=db, post=post, user_id=user_id) 55 | 56 | 57 | @app.get("/posts/", response_model=List[_schemas.Post]) 58 | def read_posts( 59 | skip: int = 0, 60 | limit: int = 10, 61 | db: _orm.Session = _fastapi.Depends(_services.get_db), 62 | ): 63 | posts = _services.get_posts(db=db, skip=skip, limit=limit) 64 | return posts 65 | 66 | 67 | @app.get("/posts/{post_id}", response_model=_schemas.Post) 68 | def read_post(post_id: int, db: _orm.Session = _fastapi.Depends(_services.get_db)): 69 | post = _services.get_post(db=db, post_id=post_id) 70 | if post is None: 71 | raise _fastapi.HTTPException( 72 | status_code=404, detail="sorry this post does not exist" 73 | ) 74 | 75 | return post 76 | 77 | 78 | @app.delete("/posts/{post_id}") 79 | def delete_post(post_id: int, db: _orm.Session = _fastapi.Depends(_services.get_db)): 80 | _services.delete_post(db=db, post_id=post_id) 81 | return {"message": f"successfully deleted post with id: {post_id}"} 82 | 83 | 84 | @app.put("/posts/{post_id}", response_model=_schemas.Post) 85 | def update_post( 86 | post_id: int, 87 | post: _schemas.PostCreate, 88 | db: _orm.Session = _fastapi.Depends(_services.get_db), 89 | ): 90 | return _services.update_post(db=db, post=post, post_id=post_id) --------------------------------------------------------------------------------