Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
__pycache__
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
## Fast Api Sqlachemy Media React App

### Using Sqlalchemy ImageAttach for attaching image file data
```
from sqlalchemy_imageattach.entity import Image, image_attachment
```
32 changes: 30 additions & 2 deletions backend/app/api/controller/customerController.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
from fastapi import APIRouter, status
from fastapi import APIRouter, status, Path, Field, Depends, Form
from fastapi.responses import Response, JSONResponse
from app.schema.customerSchema import (
CustomerCreateResponse,
CustomerUpdateResponse,
CustomerDeleteResponse,
CustomerQueryResponse,
CustomerCreate
)
from app.core.business.abstracts.customerService import customerService

customerroutes = APIRouter()

@customerroutes.post("/", response_model=)
@customerroutes.post("/", response_model=CustomerCreateResponse)
def create_customer(customer: CustomerCreate) -> Any:
create_customer = customerService.createCustomer(customer)
return JSONResponse(content=create_customer, status=status.HTTP_201_CREATED, media_type='application/json')

@customerroutes.update("/{customer_id}", response_model=CustomerUpdateResponse)
def update_customer(customer_id: int = Path(..., title="The ID of the customer to get"), firstname: str = Form(...), lastname: str = Form(...), files: Form(...)) -> Any:
data = CustomerUpdate(firstname, lastname, files)
update_customer = customerService.updateCustomer(customer_id, data, files)
return JSONResponse(content=update_customer, status=status.HTTP_200_OK, media_type='application/json')

@customerroutes.delete("/{customer_id}", response_model=CustomerDeleteResponse)
def delete_customer(customer_id: int = Path(...)):
delete_customer = customerService.deleteCustomer(customer_id)
return JSONResponse(content=delete_customer, status=status.HTTP_204_NO_CONTENT, media_type='application/json')

@customerroutes.get("/{customer_id}", response_model=CustomerDeleteResponse)
def get_customer(customer_id: int = Path(...)):
query_customer = customerService.getCustomerById(customer_id)
if query_customer is not None:
return JSONResponse(content=query_customer, status=status.HTTP_200_OK, media_type='application/json')
return JSONResponse(content=query_customer, status=status.HTTP_404_NOT_FOUND, media_type='application/json')

2 changes: 1 addition & 1 deletion backend/app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from app.api.controller.customerController import customerroutes

api_router = APIRouter()
api_router.include_router(customerroutes, prefix="/customer", tags=["customer"])
api_router.include_router(customerroutes, prefix="customer", tags=["customer"])

24 changes: 24 additions & 0 deletions backend/app/core/business/abstracts/customerService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from app.crud.repository.customerRepository import customerRepository
from typing import Optional
from app.models.customer import Customer

class CustomerService:
def __init__(self, customerRepository):
self.customerRepository = customerRepository

def saveCustomer(self, customer_create: CustomerCreate) -> Customer:
return self.customerRepository.saveCustomer(customer_create)

def updateCustomer(self, customer_id, data, files) -> Optional[Customer]:
return self.customerRepository.update(customer_id, data, files)

def getCustomerById(self, customer_id: int):
return self.customerRepository.getById(customer_id)

def getCustomerByEmail(self, customer_email: str):
return self.customerRepository.getByEmail(customer_email)

def deleteCustomer(self, customer_id: int):
return self.customerRepository.delete(customer_id)

customerService = CustomerService(customerRepository)
12 changes: 12 additions & 0 deletions backend/app/core/config/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseSettings
from typing import Any, Optional

class Settings(BaseSettings):
POSTGRESQL_DATABASE_URL: Optional[str] = None
SERVER_NAME: Optional[str] = None
LOCAL_VAR_APP_IMAGES: Optional[str] = None

class Meta:
config = '.env'

settings = Settings()
28 changes: 28 additions & 0 deletions backend/app/core/utils/imageUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import shutil
from sqlalchemy_imageattach import store
from app.db.getDb import get_db
from fastapi import Depends
from sqlalchemy_imageattach.context import store_context
from urllib2 import urlopen
from sqlalchemy.orm import Session

class ImageUtils:
def __init__(self):
self.db: Session = Depends(get_db)

def set_picture_url_data(self, customer, files):
try:
with store_context(store):
customer.picture.from_file(files)
except Exception:
self.db.rollback()
raise
self.db.commit()
return customer

def get_image_urls(self, customer):
picture_url = None
with store_context(store):
picture_url = customer.picture.locate()
return picture_url

7 changes: 7 additions & 0 deletions backend/app/core/utils/uuidUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from uuid import uuid4, UUID
from typing import Any

def defaultUUid() -> Any:
return uuid4()


79 changes: 71 additions & 8 deletions backend/app/crud/repository/customerRepository.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,81 @@
from sqlalchemy.orm import Session
from app.db.getDb import get_db
from app.models.customer import Customer
from fastapi.encoders import jsonable_encoders
from typing import Union, Dict, Any, Optional


class CustomerRepository:
def __init__(self):
self.db: Session = Depends(get_db)

def saveCustomer(self, customer):
pass
def save(self, customer: CustomerCreate) -> Customer:
customer_db = Customer(
customer.getFirstName(),
customer.getLastName(),
customer.getEmail(),
customer.getAge()
)
self.db.add(customer_db)
self.db.commit()
self.db.refresh(customer_db)
return customer_db

def delete(self, *, customer_id: int) -> Customer:
customer_obj = self.db.query(Customer).get(customer_id)
self.db.delete(customer_obj)
self.db.commit()
return customer_db

def updateCustomerCustom(self, customer_id: int, customer_update: Union[UpdateCustomer, Dict[str, Any]], files: Any) -> Customer:
customer_to_update = self.getCustomerById(customer_id)
if customer_to_update is None:
return None
customer_obj_data = jsonable_encoders(customer_to_update)
if isinstance(customer_update, dict):
update_data = customer_update
else:
update_data = customer_update.dict(exclude_unset=True)

for field in customer_obj_data:
if field in update_data:
setattr(customer_obj_data, field, update_data[field])
for field in customer_obj_data:
if field in update_data:
setattr(customer_obj_data, field, update_data[field])
self.db.add(customer_obj_data)
self.db.commit()
self.db.refresh(customer_obj_data)



def update(self, customer_id: int, customer_update: Union[UpdateCustomer, Dict[str, Any]], files: Any) -> Customer:
customer_to_update = self.getCustomerById(customer_id)
customer_obj_data = jsonable_encoders(customer_to_update)
if isinstance(customer_update, dict):
update_data = customer_update
else:
update_data = customer_update.dict(exclude_unset=True)
if customer_to_update is None:
return None
customer_to_update.setFirstName(customer_update.getFirstName())
customer_to_update.setLastName(customer_update.getLastName())
try:
with store_context(store):
customer_to_update.picture.from_file(files)
except Exception:
self.db.rollback()
raise
self.db.add(customer_to_update)
self.db.commit()
self.db.refresh(customer_to_update)
return customer_to_update


def deleteCustomer(self, customer_id):
pass
def getById(self, customer_id: int) -> Optional[Customer]:
return self.db.query(Customer).filter(Customer.getId() == customer_id).first()

def updateCustomer(self, customer, customer_data):
pass
def getByEmail(self, customer_email: str) -> Optional[Customer]
return self.db.query(Customer).filter(Customer.getEmail() == customer_id).first()

def getCustomerById(self, customer_id):
pass
customerRepository = CustomerRepository()
2 changes: 2 additions & 0 deletions backend/app/db/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from app.db.base_class import Base

11 changes: 11 additions & 0 deletions backend/app/db/base_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Any
from sqlalchemy.ext.declarative import declarative_base, declared_attr

@declarative_base()
class Base:
id: Any
__name__: str
# Generated __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
10 changes: 10 additions & 0 deletions backend/app/db/getDb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Generator
from app.db.session import SessionLocal


def get_db() -> None:
db = SessionLocal()
try:
yield db
finally:
db.close()
6 changes: 6 additions & 0 deletions backend/app/db/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from app.core.config.settings import settings

engine = create_engine(settings.POSTGRESQL_DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
8 changes: 8 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from fastapi import FastAPI
from fastapi.responses import Response
from fastapi.middleware.wsgi import WSGIMiddleware
from sqlalchemy_imageattach.stores.fs import HttpExposedFileSystemStore, FileSystemStore
from app.core.config.settings import settings
from app.api.routes import api_router

app = FastAPI()
fs_store = FileSystemStore(path=settings.LOCAL_VAR_APP_IMAGES, base_url=settings.SERVER_NAME + '/')
fs_store.wsgi_middleware(app)

app.include_router(api_router, prefix="/api/v1/", tags=['app'])
40 changes: 39 additions & 1 deletion backend/app/models/customer.py
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
import os
from app.db.base import Base
from typing import Any
from sqlalchemy import String, Column, Integer
from sqlalchemy.dialects.postgresql import UUID
from app.core.config.utils.uuidUtils import defaultUUid
from app.models.customerPicture import CustomerPicture
from sqlalchemy_imageattach.entity import image_attachment
import hashlib


class Customer(Base):
id = Column(UUID(as_uuid=True), primary_key=True, default=defaultUUid)
firstname = Column(String, nullable=False)
lastname = Column(String, nullable=False)
email = Column(String, nullable=False)
age = Column(Integer)
picture = image_attachment('CustomerPicture')

def getFirstName(self) -> str:
return self.firstname

def getLastName(self) -> str:
return self.lastname

def getEmail(self) -> str:
return self.email

def getAge(self) -> int:
return self.age

def getPicture(self) -> Any
return self.picture


@property
def object_id(self):
return int(hashlib.sha1(self.id).hexdigest(), 16)


13 changes: 13 additions & 0 deletions backend/app/models/customerPicture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from app.db.base import Base
from sqlalchemy_imageattach.entity import Image
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from typing import Any

class CustomerPicture(Base, Image):
"""User picture model."""
customer_id = Column(Integer, ForeignKey('customer.id'), primary_key=True)
customer = relationship('Customer')

def getCustomer(self) -> Any:
return self.customer
49 changes: 46 additions & 3 deletions backend/app/schema/customerSchema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
from pydantic import BaseModel
from pydantic import BaseModel, EmailStr, Field
from typing import Optional

class CustomerImage(BaseModel):
url: str

class CustomerInDB(BaseModel):
pass
id: Optional[int] = None
firstname: Optional[str] = Field(None, title="customer first name data")
lastname: Optional[str] = None
email: Optional[EmailStr] = None
age: Optional[int] = None
picture: Optional[Any] = None

class CustomerCreate(BaseModel):
firstname: Optional[str] = None
lastname: Optional[str] = None
email: Optional[str] = None
age: Optional[int] = None

class CustomerUpdate(BaseModel):
firstname: Optional[str] = None
lastname: Optional[str] = None
image: Optional[CustomerImage] = None

class CustomerCreateResponse(CustomerInDB):
firstname: Optional[str] = None
lastname: Optional[str] = None
email: Optional[EmailStr] = None
age: Optional[int] = None
picture: Optional[Any] = None

class CustomerUpdateResponse(CustomerInDB):
firstname: Optional[str] = None
lastname: Optional[str] = None
email: Optional[EmailStr] = None
age: Optional[int] = None
picture: Optional[Any] = None

class CustomerDeleteResponse(CustomerInDB):
success_data: Optional[str] = None


class CustomerQueryResponse(CustomerInDB):
id: Optional[int] = None
lastname: Optional[str] = None
email: Optional[str] = None


class
2 changes: 0 additions & 2 deletions models.txt

This file was deleted.