Refactored app structure.

This commit is contained in:
Kamil 2021-11-28 00:11:39 +01:00
parent e3ed71ac2a
commit 80c7751b5a
28 changed files with 309 additions and 252 deletions

View File

@ -7,9 +7,10 @@ from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
import config
from .schemas import TokenData
from db import models, schemas
from app.config import ALGORITHM, SECRET_KEY, TOKEN_EXPIRE_DAYS
from app.models.user import User
from app.schemas.token import TokenData
from app.schemas.user import UserIn
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
@ -24,9 +25,9 @@ def get_password_hash(password):
def get_user(db: Session, username: str):
user = db.query(models.User).filter(models.User.username == username).first()
user = db.query(User).filter(User.username == username).first()
if user:
return schemas.UserIn(
return UserIn(
**{'username': user.username, 'hashed_password': user.hashed_password}
)
@ -47,7 +48,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({'exp': expire})
encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=config.ALGORITHM)
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@ -59,7 +60,7 @@ def get_token(db: Session, form_data):
detail='Incorrect username or password',
headers={'WWW-Authenticate': 'Bearer'}
)
access_token_expires = timedelta(days=config.TOKEN_EXPIRE_DAYS)
access_token_expires = timedelta(days=TOKEN_EXPIRE_DAYS)
access_token = create_access_token(
data={'sub': user.username},
expires_delta=access_token_expires
@ -74,7 +75,7 @@ def get_current_user(db: Session, token: str = Depends(oauth2_scheme)):
headers={'WWW-Authenticate': 'Bearer'}
)
try:
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[config.ALGORITHM])
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get('sub')
if username is None:
raise credentials_exception

View File

@ -2,10 +2,10 @@ from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import config
from app.config import SQLALCHEMY_DATABASE_URL
engine = create_engine(
config.SQLALCHEMY_DATABASE_URL,
SQLALCHEMY_DATABASE_URL,
connect_args={'check_same_thread': False}
)

39
app/main.py Normal file
View File

@ -0,0 +1,39 @@
import schedule
import uvicorn
from fastapi import FastAPI
from fastapi_utils.tasks import repeat_every
from app.config import ENV, HOST, PORT
from app.db import Base, engine
from app.readings.scheduler import clear_temp_task, init_temp_task
from app.routers import owner, reading, token
Base.metadata.create_all(bind=engine)
app = FastAPI()
app.include_router(owner.router, tags=['Owners'])
app.include_router(reading.router, tags=['Readings'])
app.include_router(token.router, tags=['Token'])
@app.on_event('startup')
def startup_event():
init_temp_task()
schedule.run_all()
@app.on_event('startup')
@repeat_every(seconds=60)
def run_pending():
schedule.run_pending()
@app.on_event('shutdown')
def close_event():
clear_temp_task()
if __name__ == '__main__':
reload = ENV == 'dev'
uvicorn.run('main:app', host=HOST, port=PORT, reload=reload)

14
app/models/owner.py Normal file
View File

@ -0,0 +1,14 @@
from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import relationship
from app.db import Base
class Owner(Base):
__tablename__ = 'owners'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
readings = relationship('Reading', back_populates='owner_system')

15
app/models/reading.py Normal file
View File

@ -0,0 +1,15 @@
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer
from sqlalchemy.orm import relationship
from app.db import Base
class Reading(Base):
__tablename__ = 'readings'
id = Column(Integer, primary_key=True, index=True)
timestamp = Column(DateTime, unique=True)
temperature = Column(Float, default=10.0)
owner_id = Column(Integer, ForeignKey('owners.id'))
owner_system = relationship('Owner', back_populates='readings')

11
app/models/user.py Normal file
View File

@ -0,0 +1,11 @@
from sqlalchemy import Column, Integer, String
from app.db import Base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)

View File

@ -1,6 +1,6 @@
import schedule
from .temperature import save_temperature
from app.readings.temperature import save_temperature
def init_temp_task():
@ -8,4 +8,4 @@ def init_temp_task():
def clear_temp_task():
schedule.clear('5_min_temp')
schedule.clear('5_min_temp')

View File

@ -3,9 +3,9 @@ import json
import os
import subprocess
from db.crud import create_owner_reading, get_owner_by_name
from db.database import get_db
from db.schemas import ReadingIn
from app.db import get_db
from app.schemas.reading import ReadingIn
from app.services.owner import create_owner_reading, get_owner_by_name
commands = (
# Manjaro
@ -25,9 +25,7 @@ def read_temperature() -> float:
try:
proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
stdout = proc.stdout.decode('utf-8')
# print(f'{stdout=}')
# stderr = proc.stderr.decode('utf-8')
# print(f'{stderr=}')
# output = json.loads(stdout)
# temp = round(output['nvme-pci-0500']['Composite']['temp1_input'], 1)
temp = round(float(stdout) / 1000, 1)

0
app/routers/__init__.py Normal file
View File

61
app/routers/owner.py Normal file
View File

@ -0,0 +1,61 @@
from typing import List
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, status
from sqlalchemy.orm import Session
from app.authenticate import oauth2_scheme
from app.db import get_db
from app.schemas.owner import Owner, OwnerIn
from app.schemas.reading import Reading, ReadingIn
from app.services.owner import (
create_owner,
get_owner,
get_owner_by_name,
get_owners,
create_owner_reading
)
router = APIRouter(prefix='/owners')
@router.post('/', response_model=Owner)
def create_owner(owner: OwnerIn = Body(...),
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
db_owner = get_owner_by_name(db, name=owner.name)
if db_owner:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Owner already exists.')
return create_owner(db=db, owner=owner)
@router.get('', response_model=List[Owner])
def read_owners(skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1),
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
owners = get_owners(db, skip=skip, limit=limit)
return owners
@router.get('/{owner_id}', response_model=Owner)
def read_owner(owner_id: int = Path(..., ge=0),
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
db_owner = get_owner(db, owner_id=owner_id)
if not db_owner:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Owner not found.')
return db_owner
@router.post('/{owner_id}/readings/',
response_model=Reading,
status_code=status.HTTP_201_CREATED)
def create_reading_for_owner(owner_id: int = Path(..., ge=0),
reading: ReadingIn = Body(...),
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
return create_owner_reading(db=db, reading=reading, owner_id=owner_id)

30
app/routers/reading.py Normal file
View File

@ -0,0 +1,30 @@
from datetime import datetime
from typing import List
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.authenticate import oauth2_scheme
from app.db import get_db
from app.schemas.reading import Reading
from app.services.reading import get_readings, get_readings_by_date
router = APIRouter(prefix='/readings')
@router.get('', response_model=List[Reading])
def read_readings(skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1),
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
readings = get_readings(db, skip=skip, limit=limit)
return readings
@router.get('/', response_model=List[Reading])
def read_readings(start: datetime = Query(...),
end: datetime = Query(...),
skip: int = 0, limit: int = 100,
db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
readings = get_readings_by_date(db, start, end, skip=skip, limit=limit)
return readings

14
app/routers/token.py Normal file
View File

@ -0,0 +1,14 @@
from fastapi import APIRouter, Depends
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from app.authenticate import get_token
from app.db import get_db
router = APIRouter(prefix='/token')
@router.post('')
def login(form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)):
return get_token(db, form_data)

0
app/schemas/__init__.py Normal file
View File

22
app/schemas/owner.py Normal file
View File

@ -0,0 +1,22 @@
from typing import List
from pydantic import BaseModel
from app.schemas.reading import Reading
class OwnerBase(BaseModel):
name: str
class OwnerIn(OwnerBase):
pass
class Owner(OwnerBase):
id: int
is_active: bool
readings: List[Reading] = []
class Config:
orm_mode = True

20
app/schemas/reading.py Normal file
View File

@ -0,0 +1,20 @@
from datetime import datetime
from pydantic import BaseModel
class ReadingBase(BaseModel):
timestamp: datetime
temperature: float
class ReadingIn(ReadingBase):
pass
class Reading(ReadingBase):
id: int
owner_id: int
class Config:
orm_mode = True

17
app/schemas/user.py Normal file
View File

@ -0,0 +1,17 @@
from pydantic import BaseModel
class UserBase(BaseModel):
username: str
class UserIn(UserBase):
hashed_password: str
class User(UserBase):
id: int
hashed_password: str
class Config:
orm_mode = True

0
app/services/__init__.py Normal file
View File

34
app/services/owner.py Normal file
View File

@ -0,0 +1,34 @@
from sqlalchemy.orm import Session
from app.models.owner import Owner
from app.models.reading import Reading
from app.schemas.owner import OwnerIn
from app.schemas.reading import ReadingIn
def create_owner(db: Session, owner: OwnerIn):
db_owner = Owner(name=owner.name)
db.add(db_owner)
db.commit()
db.refresh(db_owner)
return db_owner
def create_owner_reading(db: Session, reading: ReadingIn, owner_id: int):
db_reading = Reading(**reading.dict(), owner_id=owner_id)
db.add(db_reading)
db.commit()
db.refresh(db_reading)
return db_reading
def get_owner(db: Session, owner_id: int):
return db.query(Owner).filter(Owner.id == owner_id).first()
def get_owner_by_name(db: Session, name: str):
return db.query(Owner).filter(Owner.name == name).first()
def get_owners(db: Session, skip: int = 0, limit: int = 100):
return db.query(Owner).offset(skip).limit(limit).all()

16
app/services/reading.py Normal file
View File

@ -0,0 +1,16 @@
from datetime import datetime
from sqlalchemy.orm import Session
from app.models.reading import Reading
def get_readings(db: Session, skip: int = 0, limit: int = 100):
return db.query(Reading).offset(skip).limit(limit).all()
def get_readings_by_date(db: Session, start: datetime, end: datetime,
skip: int = 0, limit: int = 100):
return db.query(Reading).filter(
Reading.timestamp >= start, Reading.timestamp <= end
).offset(skip).limit(limit).all()

View File

@ -1,45 +0,0 @@
from datetime import datetime
from sqlalchemy.orm import Session
from . import models, schemas
def create_owner(db: Session, owner: schemas.OwnerIn):
db_owner = models.Owner(name=owner.name)
db.add(db_owner)
db.commit()
db.refresh(db_owner)
return db_owner
def create_owner_reading(db: Session, reading: schemas.ReadingIn, owner_id: int):
db_reading = models.Reading(**reading.dict(), owner_id=owner_id)
db.add(db_reading)
db.commit()
db.refresh(db_reading)
return db_reading
def get_owner(db: Session, owner_id: int):
return db.query(models.Owner).filter(models.Owner.id == owner_id).first()
def get_owner_by_name(db: Session, name: str):
return db.query(models.Owner).filter(models.Owner.name == name).first()
def get_owners(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Owner).offset(skip).limit(limit).all()
def get_readings(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Reading).offset(skip).limit(limit).all()
def get_readings_by_date(db: Session, start: datetime, end: datetime,
skip: int = 0, limit: int = 100):
return db.query(models.Reading).filter(
models.Reading.timestamp >= start,
models.Reading.timestamp <= end
).offset(skip).limit(limit).all()

View File

@ -1,33 +0,0 @@
from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
class Owner(Base):
__tablename__ = 'owners'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
readings = relationship('Reading', back_populates='owner_system')
class Reading(Base):
__tablename__ = 'readings'
id = Column(Integer, primary_key=True, index=True)
timestamp = Column(DateTime, unique=True)
temperature = Column(Float, default=10.0)
owner_id = Column(Integer, ForeignKey('owners.id'))
owner_system = relationship('Owner', back_populates='readings')

View File

@ -1,54 +0,0 @@
from datetime import datetime
from typing import List
from pydantic import BaseModel
class ReadingBase(BaseModel):
timestamp: datetime
temperature: float
class ReadingIn(ReadingBase):
pass
class Reading(ReadingBase):
id: int
owner_id: int
class Config:
orm_mode = True
class OwnerBase(BaseModel):
name: str
class OwnerIn(OwnerBase):
pass
class Owner(OwnerBase):
id: int
is_active: bool
readings: List[Reading] = []
class Config:
orm_mode = True
class UserBase(BaseModel):
username: str
class UserIn(UserBase):
hashed_password: str
class User(UserBase):
id: int
hashed_password: str
class Config:
orm_mode = True

103
main.py
View File

@ -1,103 +0,0 @@
from datetime import datetime
import schedule
from typing import List
import uvicorn
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_utils.tasks import repeat_every
from sqlalchemy.orm import Session
from secure.authenticate import oauth2_scheme, get_token
import config
from db import crud, models, schemas
from db.database import engine, get_db
from readings.scheduler import clear_temp_task, init_temp_task
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.on_event('startup')
def startup_event():
init_temp_task()
schedule.run_all()
@app.on_event('startup')
@repeat_every(seconds=60)
def run_pending():
schedule.run_pending()
@app.on_event('shutdown')
def close_event():
clear_temp_task()
@app.post('/token')
def login(form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)):
return get_token(db, form_data)
@app.post('/owners/', response_model=schemas.Owner)
def create_owner(owner: schemas.OwnerIn,
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
db_owner = crud.get_owner_by_name(db, name=owner.name)
if db_owner:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Owner already exists.')
return crud.create_owner(db=db, owner=owner)
@app.get('/owners/', response_model=List[schemas.Owner])
def read_owners(skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
owners = crud.get_owners(db, skip=skip, limit=limit)
return owners
@app.get('/owners/{owner_id}', response_model=schemas.Owner)
def read_owner(owner_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
db_owner = crud.get_owner(db, owner_id=owner_id)
if not db_owner:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Owner not found.')
return db_owner
@app.post('/owners/{owner_id}/readings/', response_model=schemas.Reading)
def create_reading_for_owner(owner_id: int,
reading: schemas.ReadingIn,
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
return crud.create_owner_reading(db=db, reading=reading, owner_id=owner_id)
@app.get('/readings/', response_model=List[schemas.Reading])
def read_readings(skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
readings = crud.get_readings(db, skip=skip, limit=limit)
return readings
@app.get('/readings', response_model=List[schemas.Reading])
def read_readings(start: datetime, end: datetime,
skip: int = 0, limit: int = 100,
db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
readings = crud.get_readings_by_date(db, start, end, skip=skip, limit=limit)
return readings
if __name__ == '__main__':
reload = config.ENV == 'dev'
uvicorn.run('main:app', host=config.HOST, port=config.PORT, reload=reload)