(fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip install passlib Collecting passlib Downloading passlib-1.7.4-py2.py3-none-any.whl.metadata (1.7 kB) Downloading passlib-1.7.4-py2.py3-none-any.whl (525 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 525.6/525.6 kB 1.7 MB/s 0:00:00 Installing collected packages: passlib Successfully installed passlib-1.7.4 (fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip install bcrypt Collecting bcrypt Downloading bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl.metadata (10 kB) Downloading bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl (278 kB) Installing collected packages: bcrypt Successfully installed bcrypt-5.0.0
Got below error when creating users:
ValueError: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72])
Uninstall bcrypt and install an older version:
(fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip uninstall bcrypt Found existing installation: bcrypt 5.0.0 Uninstalling bcrypt-5.0.0: Would remove: /home/frank/fastapi-venv/lib/python3.13/site-packages/bcrypt-5.0.0.dist-info/* /home/frank/fastapi-venv/lib/python3.13/site-packages/bcrypt/* Proceed (Y/n)? Y Successfully uninstalled bcrypt-5.0.0 (fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip install bcrypt==4.0.1 Collecting bcrypt==4.0.1 Downloading bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl.metadata (9.0 kB) Downloading bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl (593 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 593.7/593.7 kB 568.9 kB/s 0:00:01 Installing collected packages: bcrypt Successfully installed bcrypt-4.0.1
(fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ uvicorn main:app --reload
INFO: Will watch for changes in these directories: ['/home/frank/zproject/TodoApp']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [103674] using StatReload
Form data requires "python-multipart" to be installed.
You can install "python-multipart" with:
pip install python-multipart
(Many errors occurred)
(fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip install python-multipart Collecting python-multipart Downloading python_multipart-0.0.22-py3-none-any.whl.metadata (1.8 kB) Downloading python_multipart-0.0.22-py3-none-any.whl (24 kB) Installing collected packages: python-multipart Successfully installed python-multipart-0.0.22 (fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ uvicorn main:app --reload INFO: Will watch for changes in these directories: ['/home/frank/zproject/TodoApp'] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [104553] using StatReload INFO: Started server process [104561] INFO: Waiting for application startup. INFO: Application startup complete.
(No error)







(fastapi-venv) frank@ZZHPC:~/zproject/TodoApp$ pip install "python-jose[cryptography]"
Collecting python-jose[cryptography]
Downloading python_jose-3.5.0-py2.py3-none-any.whl.metadata (5.5 kB)
Collecting ecdsa!=0.15 (from python-jose[cryptography])
Downloading ecdsa-0.19.1-py2.py3-none-any.whl.metadata (29 kB)
Collecting rsa!=4.1.1,!=4.4,<5.0,>=4.0 (from python-jose[cryptography])
Downloading rsa-4.9.1-py3-none-any.whl.metadata (5.6 kB)
Collecting pyasn1>=0.5.0 (from python-jose[cryptography])
Downloading pyasn1-0.6.2-py3-none-any.whl.metadata (8.4 kB)
Collecting cryptography>=3.4.0 (from python-jose[cryptography])
Downloading cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl.metadata (5.7 kB)
Collecting cffi>=2.0.0 (from cryptography>=3.4.0->python-jose[cryptography])
Downloading cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB)
Collecting pycparser (from cffi>=2.0.0->cryptography>=3.4.0->python-jose[cryptography])
Downloading pycparser-3.0-py3-none-any.whl.metadata (8.2 kB)
Collecting six>=1.9.0 (from ecdsa!=0.15->python-jose[cryptography])
Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)
Downloading python_jose-3.5.0-py2.py3-none-any.whl (34 kB)
Downloading rsa-4.9.1-py3-none-any.whl (34 kB)
Downloading cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl (4.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 3.3 MB/s 0:00:01
Downloading cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB)
Downloading ecdsa-0.19.1-py2.py3-none-any.whl (150 kB)
Downloading pyasn1-0.6.2-py3-none-any.whl (83 kB)
Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
Downloading pycparser-3.0-py3-none-any.whl (48 kB)
Installing collected packages: six, pycparser, pyasn1, rsa, ecdsa, cffi, python-jose, cryptography
Successfully installed cffi-2.0.0 cryptography-46.0.5 ecdsa-0.19.1 pyasn1-0.6.2 pycparser-3.0 python-jose-3.5.0 rsa-4.9.1 six-1.17.0



main.py:
from fastapi import FastAPI import models from database import engine from routers import auth, todos, admin, user app = FastAPI() models.Base.metadata.create_all(bind=engine) app.include_router(auth.router) app.include_router(todos.router) app.include_router(admin.router) app.include_router(user.router)
database.py:
from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session from sqlalchemy.ext.declarative import declarative_base DATABASE_URL = 'sqlite:///./todosapp.db' engine = create_engine(DATABASE_URL, connect_args={'check_same_thread': False}) # Well, we want to say check same thread of type false. # And now by default, SQLite will only allow one thread to communicate with it. Assuming that each thread will handle an independent request. # This is to prevent any kind of accident sharing of the same connection for different kind of requests. # But in fast API it's very normal to have more than one thread that could interact with the database at the same time. # So we just need to make sure SQLite knows that hey, we don't want to be checking the same thread all the time because there could be multiple threads happening to our SQLite database. SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # Now we need to create a SessionLocal and each instance of the SessionLocal will have a database session. # The class itself is not a database session yet. We will add that later on. # But right now we just need to be able to create an instance of SessionLocal that will be able to become an actual database in the future. Base = declarative_base() # create an object of our database which will then be able to interact with the tables that we create in the future. def get_db(): db = SessionLocal() try: yield db finally: db.close()
models.py:
from database import Base from sqlalchemy import Column, Integer, String, Boolean, ForeignKey class Users(Base): __tablename__ = 'users' # To make a column auto-increment, you typically need to define it as a Primary Key with an integer type. id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True) username = Column(String, unique=True) first_name = Column(String) last_name = Column(String) hashed_password = Column(String) is_active = Column(Boolean, default=True) role = Column(String) class Todos(Base): __tablename__ = 'todos' # To make a column auto-increment, you typically need to define it as a Primary Key with an integer type. id = Column(Integer, primary_key=True, index=True) title = Column(String) description = Column(String) description = Column(String) priority = Column(Integer) complete = Column(Boolean, default=False) owner_id = Column(Integer, ForeignKey("users.id"))
routers/auth.py:
from typing import Annotated from datetime import timedelta, datetime, timezone from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from starlette import status from passlib.context import CryptContext from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer from jose import jwt, JWTError from database import Session, get_db from models import Users router = APIRouter( prefix='/auth', tags=['auth'] ) SECRET_KEY = '197b2c37c391bed93fe80344fe73b806947a65e36206e05a1a23c2fa12702fe3' ALGORITHM = 'HS256' bcrypt_context = CryptContext(schemes=['bcrypt'], deprecated='auto') oauth2_bearer = OAuth2PasswordBearer(tokenUrl='auth/token') # Dependency injection db_dependency = Annotated[Session, Depends(get_db)] token_dependency = Annotated[str, Depends(oauth2_bearer)] class CreateUserRequest(BaseModel): username: str email: str first_name: str last_name: str password: str role: str class Token(BaseModel): access_token: str token_type: str def authenticate_user(username: str, password: str, db): user = db.query(Users).filter(Users.username == username).first() if not user: return False if not bcrypt_context.verify(password, user.hashed_password): return False return user def create_access_token(username: str, user_id: int, role: str, expires_delta: timedelta): encode = {'sub': username, 'id': user_id, 'role': role} expires = datetime.now(timezone.utc) + expires_delta encode.update({'exp': expires}) return jwt.encode(encode, SECRET_KEY, algorithm=ALGORITHM) async def get_current_user(token: token_dependency): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get('sub') user_id: int = payload.get('id') user_role: str = payload.get('role') if username is None or user_id is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Could not validate user.') return {'username': username, 'id': user_id, 'user_role': user_role} except JWTError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Could not validate user.') @router.post("/", status_code=status.HTTP_201_CREATED) async def create_user(db: db_dependency, create_user_request: CreateUserRequest): create_user_model = Users( email=create_user_request.email, username=create_user_request.username, first_name=create_user_request.first_name, last_name=create_user_request.last_name, role=create_user_request.role, hashed_password=bcrypt_context.hash(create_user_request.password), is_active=True ) db.add(create_user_model) db.commit() @router.post("/token", response_model=Token) async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: db_dependency): user = authenticate_user(form_data.username, form_data.password, db) if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Could not validate user.') token = create_access_token(user.username, user.id, user.role, timedelta(minutes=20)) return {'access_token': token, 'token_type': 'bearer'}
routers/user.py:
from typing import Annotated from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from starlette import status from database import Session, get_db from models import Users from .auth import get_current_user, bcrypt_context router = APIRouter( prefix='/user', tags=['user'] ) # Dependency injection db_dependency = Annotated[Session, Depends(get_db)] user_dependency = Annotated[dict, Depends(get_current_user)] class UserVerification(BaseModel): password: str new_password: str = Field(min_length=6) @router.get('/', status_code=status.HTTP_200_OK) async def get_user(user: user_dependency, db: db_dependency): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') return db.query(Users).filter(Users.id == user.get('id')).first() @router.put('/password', status_code=status.HTTP_204_NO_CONTENT) async def change_password(user: user_dependency, db: db_dependency, user_verification: UserVerification): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') user_model = db.query(Users).filter(Users.id == user.get('id')).first() if not bcrypt_context.verify(user_verification.password, user_model.hashed_password): raise HTTPException(status_code=401, detail='Authentication Failed') user_model.hashed_password = bcrypt_context.hash(user_verification.new_password) db.add(user_model) db.commit()
routers/admin.py:
from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Path from starlette import status from database import Session, get_db from models import Todos from .auth import get_current_user router = APIRouter( prefix='/admin', tags=['admin'] ) # Dependency injection db_dependency = Annotated[Session, Depends(get_db)] user_dependency = Annotated[dict, Depends(get_current_user)] @router.get("/todo", status_code=status.HTTP_200_OK) async def read_all(user: user_dependency, db: db_dependency): if user is None or user.get('user_role') != 'admin': raise HTTPException(status_code=401, detail='Authentication Failed') return db.query(Todos).all() @router.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_todo(user: user_dependency, db: db_dependency, todo_id: int = Path(gt=0)): if user is None or user.get('user_role') != 'admin': raise HTTPException(status_code=401, detail='Authentication Failed') todo_model = db.query(Todos).filter(Todos.id == todo_id).first() if todo_model is None: raise HTTPException(status_code=404, detail='Todo not found.') db.query(Todos).filter(Todos.id == todo_id).delete() db.commit()
routers/todos.py:
from typing import Annotated from fastapi import APIRouter, Depends from fastapi import HTTPException, Path from pydantic import BaseModel, Field from starlette import status from database import Session, get_db from models import Todos from .auth import get_current_user router = APIRouter() # Dependency injection db_dependency = Annotated[Session, Depends(get_db)] user_dependency = Annotated[dict, Depends(get_current_user)] class TodoRequest(BaseModel): title: str = Field(min_length=3) description: str = Field(min_length=3, max_length=100) priority: int = Field(gt=0, lt=6) complete: bool @router.get("/", status_code=status.HTTP_200_OK) async def read_all(user: user_dependency, db: db_dependency): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') return db.query(Todos).filter(Todos.owner_id == user.get('id')).all() @router.get("/todo/{todo_id}", status_code=status.HTTP_200_OK) async def read_todo(user: user_dependency, db: db_dependency, todo_id: int = Path(gt=0)): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') todo_model = db.query(Todos).filter(Todos.id == todo_id)\ .filter(Todos.owner_id == user.get('id')).first() if todo_model is not None: return todo_model raise HTTPException(status_code=404, detail='Todo not found.') @router.post("/todo", status_code=status.HTTP_201_CREATED) async def create_todo(user: user_dependency, db: db_dependency, todo_request: TodoRequest): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') todo_model = Todos(**todo_request.model_dump(), owner_id=user.get('id')) db.add(todo_model) db.commit() @router.put("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT) async def update_todo(user: user_dependency, db: db_dependency, todo_request: TodoRequest, # TodoRequest must be above any Path parameter todo_id: int = Path(gt=0)): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') todo_model = db.query(Todos).filter(Todos.id == todo_id)\ .filter(Todos.owner_id == user.get('id')).first() if todo_model is None: raise HTTPException(status_code=404, detail='Todo not found.') todo_model.title = todo_request.title todo_model.description = todo_request.description todo_model.priority = todo_request.priority todo_model.complete = todo_request.complete db.add(todo_model) db.commit() @router.delete("/todo/{todo_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_todo(user: user_dependency, db: db_dependency, todo_id: int = Path(gt=0)): if user is None: raise HTTPException(status_code=401, detail='Authentication Failed') todo_model = db.query(Todos).filter(Todos.id == todo_id)\ .filter(Todos.owner_id == user.get('id')).first() if todo_model is None: raise HTTPException(status_code=404, detail='Todo not found.') db.query(Todos).filter(Todos.id == todo_id).filter(Todos.owner_id == user.get('id')).delete() db.commit()

浙公网安备 33010602011771号