@@ -33,7 +33,19 @@ OR | |||
``` | |||
python3 -m uvicorn main:app --reload | |||
``` | |||
## Explaining the file structure | |||
``` | |||
app //main app | |||
|-alembic //Alembic -- database migration manager. Helps keep databases in sync and changeable (handles all SQL logic and keeps everything up-to-date) | |||
| |- //All files irrelevant | |||
|-apis //Stores the actual API Endpoints. | |||
| |-v1 //Main API endpoints | |||
|-core //Core project components | |||
|-db //Stores database information and data | |||
| |-models //Stores database models (represents SQL Tables) | |||
| |-repository //Stores scripts to create, update and interact with the database | |||
|-schemas //Stores Pydantic data models (Kind of like db/models except for use in the API itself, not just the database. Uses for data validation and stuff) | |||
``` | |||
## Git Guide | |||
- [Git Reference](http://git.github.io/git-reference): despite its name it is more of a user guide. | |||
- ["Pro Git" Book](http://git-scm.com/book): by a core developer, also a thorough user guide. | |||
@@ -0,0 +1,116 @@ | |||
# A generic, single database configuration. | |||
[alembic] | |||
# path to migration scripts | |||
script_location = alembic | |||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s | |||
# Uncomment the line below if you want the files to be prepended with date and time | |||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file | |||
# for all available tokens | |||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s | |||
# sys.path path, will be prepended to sys.path if present. | |||
# defaults to the current working directory. | |||
prepend_sys_path = . | |||
# timezone to use when rendering the date within the migration file | |||
# as well as the filename. | |||
# If specified, requires the python-dateutil library that can be | |||
# installed by adding `alembic[tz]` to the pip requirements | |||
# string value is passed to dateutil.tz.gettz() | |||
# leave blank for localtime | |||
# timezone = | |||
# max length of characters to apply to the | |||
# "slug" field | |||
# truncate_slug_length = 40 | |||
# set to 'true' to run the environment during | |||
# the 'revision' command, regardless of autogenerate | |||
# revision_environment = false | |||
# set to 'true' to allow .pyc and .pyo files without | |||
# a source .py file to be detected as revisions in the | |||
# versions/ directory | |||
# sourceless = false | |||
# version location specification; This defaults | |||
# to alembic/versions. When using multiple version | |||
# directories, initial revisions must be specified with --version-path. | |||
# The path separator used here should be the separator specified by "version_path_separator" below. | |||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions | |||
# version path separator; As mentioned above, this is the character used to split | |||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. | |||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. | |||
# Valid values for version_path_separator are: | |||
# | |||
# version_path_separator = : | |||
# version_path_separator = ; | |||
# version_path_separator = space | |||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects. | |||
# set to 'true' to search source files recursively | |||
# in each "version_locations" directory | |||
# new in Alembic version 1.10 | |||
# recursive_version_locations = false | |||
# the output encoding used when revision files | |||
# are written from script.py.mako | |||
# output_encoding = utf-8 | |||
sqlalchemy.url = driver://user:pass@localhost/dbname | |||
[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 REVISION_SCRIPT_FILENAME | |||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary | |||
# hooks = ruff | |||
# ruff.type = exec | |||
# ruff.executable = %(here)s/.venv/bin/ruff | |||
# ruff.options = --fix REVISION_SCRIPT_FILENAME | |||
# Logging configuration | |||
[loggers] | |||
keys = root,sqlalchemy,alembic | |||
[handlers] | |||
keys = console | |||
[formatters] | |||
keys = generic | |||
[logger_root] | |||
level = WARN | |||
handlers = console | |||
qualname = | |||
[logger_sqlalchemy] | |||
level = WARN | |||
handlers = | |||
qualname = sqlalchemy.engine | |||
[logger_alembic] | |||
level = INFO | |||
handlers = | |||
qualname = alembic | |||
[handler_console] | |||
class = StreamHandler | |||
args = (sys.stderr,) | |||
level = NOTSET | |||
formatter = generic | |||
[formatter_generic] | |||
format = %(levelname)-5.5s [%(name)s] %(message)s | |||
datefmt = %H:%M:%S |
@@ -0,0 +1 @@ | |||
Generic single-database configuration. |
@@ -0,0 +1,83 @@ | |||
from logging.config import fileConfig | |||
from sqlalchemy import engine_from_config | |||
from sqlalchemy import pool | |||
from alembic import context | |||
from core.config import settings | |||
from db.base import Base | |||
# this is the Alembic Config object, which provides | |||
# access to the values within the .ini file in use. | |||
config = context.config | |||
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL) | |||
# Interpret the config file for Python logging. | |||
# This line sets up loggers basically. | |||
if config.config_file_name is not None: | |||
fileConfig(config.config_file_name) | |||
# add your model's MetaData object here | |||
# for 'autogenerate' support | |||
# from myapp import mymodel | |||
# target_metadata = mymodel.Base.metadata | |||
target_metadata = Base.metadata | |||
# other values from the config, defined by the needs of env.py, | |||
# can be acquired: | |||
# my_important_option = config.get_main_option("my_important_option") | |||
# ... etc. | |||
def run_migrations_offline() -> None: | |||
"""Run migrations in 'offline' mode. | |||
This configures the context with just a URL | |||
and not an Engine, though an Engine is acceptable | |||
here as well. By skipping the Engine creation | |||
we don't even need a DBAPI to be available. | |||
Calls to context.execute() here emit the given string to the | |||
script output. | |||
""" | |||
url = config.get_main_option("sqlalchemy.url") | |||
context.configure( | |||
url=url, | |||
target_metadata=target_metadata, | |||
literal_binds=True, | |||
dialect_opts={"paramstyle": "named"}, | |||
) | |||
with context.begin_transaction(): | |||
context.run_migrations() | |||
def run_migrations_online() -> None: | |||
"""Run migrations in 'online' mode. | |||
In this scenario we need to create an Engine | |||
and associate a connection with the context. | |||
""" | |||
connectable = engine_from_config( | |||
config.get_section(config.config_ini_section, {}), | |||
prefix="sqlalchemy.", | |||
poolclass=pool.NullPool, | |||
) | |||
with connectable.connect() as connection: | |||
context.configure(connection=connection, target_metadata=target_metadata) | |||
with context.begin_transaction(): | |||
context.run_migrations() | |||
if context.is_offline_mode(): | |||
run_migrations_offline() | |||
else: | |||
run_migrations_online() |
@@ -0,0 +1,26 @@ | |||
"""${message} | |||
Revision ID: ${up_revision} | |||
Revises: ${down_revision | comma,n} | |||
Create Date: ${create_date} | |||
""" | |||
from typing import Sequence, Union | |||
from alembic import op | |||
import sqlalchemy as sa | |||
${imports if imports else ""} | |||
# revision identifiers, used by Alembic. | |||
revision: str = ${repr(up_revision)} | |||
down_revision: Union[str, None] = ${repr(down_revision)} | |||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} | |||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} | |||
def upgrade() -> None: | |||
${upgrades if upgrades else "pass"} | |||
def downgrade() -> None: | |||
${downgrades if downgrades else "pass"} |
@@ -0,0 +1,60 @@ | |||
"""create User and Vehicle tables | |||
Revision ID: 9a0214838ac8 | |||
Revises: | |||
Create Date: 2023-09-01 13:31:01.324861 | |||
""" | |||
from typing import Sequence, Union | |||
from alembic import op | |||
import sqlalchemy as sa | |||
# revision identifiers, used by Alembic. | |||
revision: str = '9a0214838ac8' | |||
down_revision: Union[str, None] = None | |||
branch_labels: Union[str, Sequence[str], None] = None | |||
depends_on: Union[str, Sequence[str], None] = None | |||
def upgrade() -> None: | |||
# ### commands auto generated by Alembic - please adjust! ### | |||
op.create_table('users', | |||
sa.Column('Id', sa.Integer(), nullable=False), | |||
sa.Column('Name', sa.String(), nullable=False), | |||
sa.Column('MiddleName', sa.String(), nullable=True), | |||
sa.Column('LastName', sa.String(), nullable=False), | |||
sa.Column('BirthDate', sa.DateTime(), nullable=False), | |||
sa.Column('ContactNumber', sa.String(), nullable=False), | |||
sa.Column('Email', sa.String(), nullable=False), | |||
sa.Column('Role', sa.String(), nullable=False), | |||
sa.Column('DrivingLicenseNumber', sa.String(), nullable=True), | |||
sa.Column('HashedPassword', sa.String(), nullable=False), | |||
sa.PrimaryKeyConstraint('Id') | |||
) | |||
op.create_index(op.f('ix_users_Id'), 'users', ['Id'], unique=False) | |||
op.create_table('vehicles', | |||
sa.Column('Id', sa.Integer(), nullable=False), | |||
sa.Column('Model', sa.String(), nullable=False), | |||
sa.Column('Year', sa.Integer(), nullable=False), | |||
sa.Column('LicensePlate', sa.String(), nullable=False), | |||
sa.Column('Type', sa.String(), nullable=False), | |||
sa.Column('AssignedDriverIds', sa.ARRAY(sa.Integer()), nullable=True), | |||
sa.Column('CurrentLocation', sa.ARRAY(sa.String()), nullable=True), | |||
sa.Column('Fuel', sa.Integer(), nullable=False), | |||
sa.Column('Mileage', sa.Integer(), nullable=False), | |||
sa.Column('MaintenanceNotes', sa.ARRAY(sa.String()), nullable=True), | |||
sa.PrimaryKeyConstraint('Id') | |||
) | |||
op.create_index(op.f('ix_vehicles_Id'), 'vehicles', ['Id'], unique=False) | |||
# ### end Alembic commands ### | |||
def downgrade() -> None: | |||
# ### commands auto generated by Alembic - please adjust! ### | |||
op.drop_index(op.f('ix_vehicles_Id'), table_name='vehicles') | |||
op.drop_table('vehicles') | |||
op.drop_index(op.f('ix_users_Id'), table_name='users') | |||
op.drop_table('users') | |||
# ### end Alembic commands ### |
@@ -0,0 +1,8 @@ | |||
# Base API router -- collecting all APIs here to not clutter main.py | |||
from fastapi import APIRouter | |||
from apis.v1 import route_user, route_vehicle | |||
api_router = APIRouter() | |||
api_router.include_router(route_user.router, prefix="/user", tags=["users"]) | |||
api_router.include_router(route_vehicle.router, prefix="/vehicle", tags=["vehicles"]) |
@@ -0,0 +1,35 @@ | |||
# Routes for user. MAIN PART OF THE API | |||
from fastapi import APIRouter, HTTPException, status | |||
from sqlalchemy.orm import Session | |||
from fastapi import Depends | |||
from typing import List | |||
from schemas.user import UserCreate, ShowUser | |||
from db.session import get_db | |||
from db.repository.user import create_new_user, list_users, get_user_by_id | |||
router = APIRouter() | |||
@router.post("/", response_model=ShowUser, status_code=status.HTTP_201_CREATED) | |||
def create_user(user: UserCreate, db: Session = Depends(get_db)): | |||
user = create_new_user(user=user, db=db) | |||
return user | |||
@router.get("/", response_model=List[ShowUser], status_code=status.HTTP_200_OK) | |||
def get_all_users(db: Session = Depends(get_db), role: str = None): | |||
if role == None: | |||
users = list_users(db=db) | |||
return users | |||
users = list_users(db=db, role=role) | |||
return users | |||
@router.get("/{user_id}", response_model=ShowUser, status_code=status.HTTP_200_OK) | |||
def get_user(user_id: int, db: Session = Depends(get_db)): | |||
user = get_user_by_id(user_id=user_id, db=db) | |||
if not user: | |||
raise HTTPException(status_code=404, detail="User not found") | |||
return user |
@@ -0,0 +1,95 @@ | |||
from fastapi import APIRouter, status, HTTPException | |||
from sqlalchemy.orm import Session | |||
from fastapi import Depends | |||
from typing import List | |||
from db.session import get_db | |||
from schemas.vehicle import OutputVehicle, CreateVehicle, UpdateVehicle | |||
from db.repository.vehicle import ( | |||
create_new_vehicle, | |||
assign_vehicle_driver, | |||
list_vehicles, | |||
get_vehicle_by_id, | |||
replace_vehicle_data, | |||
) | |||
router = APIRouter() | |||
@router.post("/", response_model=OutputVehicle, status_code=status.HTTP_201_CREATED) | |||
async def create_vehicle(vehicle: CreateVehicle, db: Session = Depends(get_db)): | |||
vehicle = create_new_vehicle(vehicle=vehicle, db=db) | |||
return vehicle | |||
# @router.get( | |||
# "/assign/{vehicle_id}/{driver_id}", | |||
# response_model=OutputVehicle, | |||
# status_code=status.HTTP_200_OK, | |||
# ) | |||
# async def assign_drver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db)): | |||
# vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | |||
# if vehicle == "nodriver": | |||
# raise HTTPException( | |||
# status_code=404, detail=f"Driver with id {driver_id} not found" | |||
# ) | |||
# if vehicle == "novehicle": | |||
# raise HTTPException( | |||
# status_code=404, detail=f"Vehicle with id {vehicle_id} not found" | |||
# ) | |||
# if vehicle == "alreadyassigned": | |||
# raise HTTPException( | |||
# status_code=400, | |||
# detail=f"Driver with id {driver_id} is already assigned to vehicle with id {vehicle_id}", | |||
# ) | |||
# return vehicle | |||
@router.patch( | |||
"/{vehicle_id}/driver/{driver_id}", | |||
response_model=OutputVehicle, | |||
status_code=status.HTTP_200_OK, | |||
) | |||
async def assign_driver(vehicle_id: int, driver_id: int, db: Session = Depends(get_db)): | |||
vehicle = assign_vehicle_driver(vehicle_id=vehicle_id, driver_id=driver_id, db=db) | |||
if vehicle == "nodriver": | |||
raise HTTPException( | |||
status_code=404, detail=f"Driver with id {driver_id} not found" | |||
) | |||
if vehicle == "novehicle": | |||
raise HTTPException( | |||
status_code=404, detail=f"Vehicle with id {vehicle_id} not found" | |||
) | |||
if vehicle == "alreadyassigned": | |||
raise HTTPException( | |||
status_code=400, | |||
detail=f"Driver with id {driver_id} is already assigned to vehicle with id {vehicle_id}", | |||
) | |||
return vehicle | |||
@router.get("/", response_model=List[OutputVehicle], status_code=status.HTTP_200_OK) | |||
async def get_all_vehicles(db: Session = Depends(get_db)): | |||
vehicles = list_vehicles(db=db) | |||
return vehicles | |||
@router.get( | |||
"/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | |||
) | |||
async def get_vehicle(vehicle_id: int, db: Session = Depends(get_db)): | |||
vehicle = get_vehicle_by_id(vehicle_id=vehicle_id, db=db) | |||
if not vehicle: | |||
raise HTTPException(status_code=404, detail="Vehicle not found") | |||
return vehicle | |||
@router.put( | |||
"/{vehicle_id}", response_model=OutputVehicle, status_code=status.HTTP_200_OK | |||
) | |||
def update_vehicle( | |||
vehicle_id: int, vehicle: UpdateVehicle, db: Session = Depends(get_db) | |||
): | |||
vehicleRes = replace_vehicle_data(id=vehicle_id, vehicle=vehicle, db=db) | |||
if vehicleRes == "vehicleNotFound": | |||
raise HTTPException(status_code=404, detail="Vehicle not found") | |||
elif vehicleRes == "badreq": | |||
raise HTTPException(status_code=502, detail="Bad request") | |||
return vehicleRes |
@@ -0,0 +1,14 @@ | |||
# Hashing functions for passwords | |||
from passlib.context import CryptContext | |||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") | |||
class Hasher: | |||
@staticmethod | |||
def verify_password(plain_password, hashed_password): | |||
return pwd_context.verify(plain_password, hashed_password) | |||
@staticmethod | |||
def get_password_hash(plain_password): | |||
return pwd_context.hash(plain_password) |
@@ -0,0 +1,6 @@ | |||
# Base class for all sqlalchemy models | |||
from sqlalchemy.ext.declarative import declarative_base | |||
Base = declarative_base() | |||
from db.models.user import User | |||
from db.models.vehicle import Vehicle |
@@ -1,11 +0,0 @@ | |||
from typing import Any | |||
from sqlalchemy.ext.declarative import declared_attr | |||
from sqlalchemy.orm import as_declarative | |||
@as_declarative() | |||
class Base: | |||
id: Any | |||
__name__: str | |||
def __tablename__(cls) -> str: | |||
return cls.__name__.lower() |
@@ -0,0 +1,18 @@ | |||
# PostgreSQL table model for users | |||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, URL | |||
from sqlalchemy.orm import relationship | |||
from db.base import Base | |||
class User(Base): | |||
__tablename__ = "users" | |||
Id = Column(Integer, primary_key=True, index=True) | |||
Name = Column(String, nullable=False) | |||
MiddleName = Column(String, nullable=True) | |||
LastName = Column(String, nullable=False) | |||
BirthDate = Column(DateTime, nullable=False) | |||
ContactNumber = Column(String, nullable=False) | |||
Email = Column(String, nullable=False) | |||
Role = Column(String, nullable=False) | |||
DrivingLicenseNumber = Column(String, nullable=True) | |||
HashedPassword = Column(String, nullable=False) |
@@ -0,0 +1,27 @@ | |||
# Postgres table model for vehicles | |||
from sqlalchemy import ( | |||
Column, | |||
Integer, | |||
String, | |||
DateTime, | |||
Boolean, | |||
URL, | |||
ARRAY, | |||
ForeignKey, | |||
) | |||
from sqlalchemy.orm import relationship | |||
from db.base import Base | |||
class Vehicle(Base): | |||
__tablename__ = "vehicles" | |||
Id = Column(Integer, primary_key=True, index=True) | |||
Model = Column(String, nullable=False) | |||
Year = Column(Integer, nullable=False) | |||
LicensePlate = Column(String, nullable=False) | |||
Type = Column(String, nullable=False) | |||
AssignedDriverIds = Column(ARRAY(Integer), nullable=True) | |||
CurrentLocation = Column(ARRAY(String), nullable=True) | |||
Fuel = Column(Integer, nullable=False) | |||
Mileage = Column(Integer, nullable=False) | |||
MaintenanceNotes = Column(ARRAY(String), nullable=True) |
@@ -0,0 +1,42 @@ | |||
# Creating a new user in the database | |||
from sqlalchemy.orm import Session | |||
from schemas.user import UserCreate | |||
from db.models.user import User | |||
from core.hashing import Hasher | |||
def create_new_user(user: UserCreate, db: Session): | |||
user_object = User( | |||
Email=user.Email, | |||
Name=user.Name, | |||
MiddleName=user.MiddleName, | |||
LastName=user.LastName, | |||
BirthDate=user.BirthDate, | |||
ContactNumber=user.ContactNumber, | |||
Role=user.Role, | |||
HashedPassword=Hasher.get_password_hash(user.Password), | |||
) | |||
db.add(user_object) | |||
db.commit() | |||
db.refresh(user_object) | |||
return user_object | |||
def get_user_by_id(user_id: int, db: Session): | |||
user = db.query(User).filter(User.Id == user_id).first() | |||
return user | |||
def verify_driver_exists(driver_id: int, db: Session): | |||
driver = db.query(User).filter(User.Id == driver_id).first() | |||
if not driver: | |||
return False | |||
if driver.Role != "Driver": | |||
return False | |||
return True | |||
def list_users(db: Session, role: str = "Any"): | |||
users = db.query(User).filter((User.Role == role) | (role == "Any")).all() | |||
return users |
@@ -0,0 +1,67 @@ | |||
from sqlalchemy.orm import Session | |||
from schemas.vehicle import CreateVehicle, OutputVehicle, UpdateVehicle | |||
from db.models.vehicle import Vehicle | |||
from db.repository.user import verify_driver_exists | |||
def create_new_vehicle(vehicle: CreateVehicle, db: Session): | |||
vehicle_object = Vehicle( | |||
**vehicle.model_dump(), | |||
Fuel=0, | |||
AssignedDriverIds=[], | |||
CurrentLocation=[], | |||
MaintenanceNotes=[] | |||
) | |||
db.add(vehicle_object) | |||
db.commit() | |||
db.refresh(vehicle_object) | |||
return vehicle | |||
def assign_vehicle_driver(vehicle_id: int, driver_id: int, db: Session): | |||
vehicledb = db.query(Vehicle).filter(Vehicle.Id == vehicle_id) | |||
vehicle = vehicledb.first() | |||
if not vehicle: | |||
return "novehicle" | |||
if driver_id in vehicle.AssignedDriverIds: | |||
return "alreadyassigned" | |||
if verify_driver_exists(driver_id=driver_id, db=db): | |||
print(vehicle.AssignedDriverIds) | |||
vehicledb.update({"AssignedDriverIds": vehicle.AssignedDriverIds + [driver_id]}) | |||
print(vehicle.AssignedDriverIds) | |||
db.add(vehicle) | |||
db.commit() | |||
db.refresh(vehicle) | |||
return vehicle | |||
# return a 404 error if the driver does not exist | |||
return "nodriver" | |||
def list_vehicles(db: Session): | |||
vehicles = db.query(Vehicle).all() | |||
return vehicles | |||
def get_vehicle_by_id(vehicle_id: int, db: Session): | |||
vehicle = db.query(Vehicle).filter(Vehicle.Id == vehicle_id).first() | |||
return vehicle | |||
def replace_vehicle_data(id: int, vehicle: UpdateVehicle, db: Session): | |||
vehicle_db = db.query(Vehicle).filter(Vehicle.Id == id) | |||
vehicle_object = vehicle_db.first() | |||
if not vehicle_object: | |||
return "vehiclenotfound" | |||
vehicle_object.AssignedDriverIds = vehicle.AssignedDriverIds | |||
vehicle_object.CurrentLocation = vehicle.CurrentLocation | |||
vehicle_object.Fuel = vehicle.Fuel | |||
vehicle_object.LicensePlate = vehicle.LicensePlate | |||
vehicle_object.MaintenanceNotes = vehicle.MaintenanceNotes | |||
vehicle_object.Mileage = vehicle.Mileage | |||
vehicle_object.Model = vehicle.Model | |||
vehicle_object.Type = vehicle.Type | |||
vehicle_object.Year = vehicle.Year | |||
print(vehicle_object) | |||
db.add(vehicle_object) | |||
db.commit() | |||
return vehicle_object |
@@ -1,11 +1,21 @@ | |||
# Information about the database session is stored here | |||
from sqlalchemy import create_engine | |||
from sqlalchemy.orm import sessionmaker | |||
from core.config import settings | |||
from core.config import settings | |||
SQLALCHEMY_DATABASE_URL = settings.DATABASE_URL | |||
SQLALCHEMY_DATABASE_URL = ( | |||
settings.DATABASE_URL | |||
) # get the database url from core/config.py | |||
engine = create_engine(SQLALCHEMY_DATABASE_URL) | |||
print(SQLALCHEMY_DATABASE_URL) | |||
SessionLocal = sessionmaker(autocommit=False,autoflush=False,bind=engine) | |||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | |||
def get_db(): # get the database session (if we change the database, we can change it here) | |||
db = SessionLocal() | |||
try: | |||
yield db | |||
finally: | |||
db.close() |
@@ -1,25 +1,30 @@ | |||
from fastapi import FastAPI | |||
from core.config import settings | |||
from db.session import engine | |||
from db.base_model import Base | |||
from db.base import Base | |||
from apis.base import api_router | |||
def startup(): #start the project, and create the tables | |||
app = FastAPI(title=settings.PROJECT_NAME,version=settings.PROJECT_VERSION) | |||
def include_routes(app): # include all routes from our api/v1/ | |||
app.include_router(api_router) | |||
def startup(): # start the project, and create the tables | |||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION) | |||
Base.metadata.create_all(bind=engine) | |||
include_routes(app) | |||
return app | |||
app = startup() | |||
app = startup() | |||
# Testing stuff | |||
@app.get("/") | |||
def root(): | |||
return {"message": "Hello World!"} | |||
@app.get("/user") | |||
def get_users(): | |||
return { | |||
{"name": "almaz"}, | |||
{"name": "madi"} | |||
} | |||
return {{"name": "almaz"}, {"name": "madi"}} |
@@ -0,0 +1,30 @@ | |||
# Purpose: User schema for pydantic (validation, inside-api usage) | |||
from datetime import datetime | |||
from pydantic import BaseModel, EmailStr, Field | |||
class UserCreate(BaseModel): | |||
Email: EmailStr | |||
Password: str = Field(..., min_length=7, max_length=20) | |||
Name: str = Field(..., min_length=3, max_length=50) | |||
MiddleName: str = Field(None, min_length=3, max_length=50) | |||
LastName: str = Field(..., min_length=3, max_length=50) | |||
ContactNumber: str = Field(..., min_length=12, max_length=12) | |||
BirthDate: datetime = Field(...) | |||
Email: EmailStr = Field(...) | |||
Role: str = Field(..., min_length=3, max_length=50) | |||
class ShowUser(BaseModel): | |||
Id: int | |||
Name: str | |||
MiddleName: str | |||
LastName: str | |||
ContactNumber: str | |||
BirthDate: datetime | |||
Email: EmailStr | |||
Role: str | |||
class Config: | |||
orm_mode = True |
@@ -0,0 +1,37 @@ | |||
from typing import Optional | |||
from pydantic import BaseModel, root_validator | |||
from datetime import datetime | |||
class CreateVehicle(BaseModel): | |||
Id: int | |||
Model: str | |||
Year: int | |||
LicensePlate: str | |||
Type: str | |||
Mileage: int | |||
class OutputVehicle(BaseModel): | |||
Id: int | |||
Model: str | |||
Year: int | |||
LicensePlate: str | |||
Type: str | |||
Mileage: int | |||
CurrentLocation: Optional[list[str]] = None | |||
Fuel: Optional[int] = 0 | |||
MaintenanceNotes: Optional[list[str]] = None | |||
AssignedDriverIds: Optional[list[int]] = None | |||
class UpdateVehicle(BaseModel): | |||
Model: str | |||
Year: int | |||
LicensePlate: str | |||
Type: str | |||
Mileage: int | |||
CurrentLocation: Optional[list[str]] = None | |||
Fuel: Optional[int] = 0 | |||
MaintenanceNotes: Optional[list[str]] = None | |||
AssignedDriverIds: Optional[list[int]] = None |
@@ -1,3 +1,6 @@ | |||
fastapi[all] | |||
pydantic | |||
sqlalchemy | |||
psycopg2 | |||
psycopg2 | |||
alembic==1.12.0 | |||
passlib |