From 2636d1f5c92ef8545dd9ab92f20f0960e22aadbd Mon Sep 17 00:00:00 2001 From: Kelvin Nicholson Date: Sun, 29 Oct 2023 15:11:52 +1100 Subject: [PATCH] Expose via api --- app/__init__.py | 0 app/crud.py | 10 ++++++ app/main.py | 27 +++++++++++++++ app/schemas.py | 24 +++++++++++++ app/test_main.py | 42 +++++++++++++++++++++++ database.py | 13 +++++++ fly.toml | 8 +++++ models.py | 4 +-- tests.py => price_monitor/test_spiders.py | 0 requirements.txt | 5 ++- scripts/test.sh | 2 +- 11 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/crud.py create mode 100644 app/main.py create mode 100644 app/schemas.py create mode 100644 app/test_main.py create mode 100644 database.py rename tests.py => price_monitor/test_spiders.py (100%) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/crud.py b/app/crud.py new file mode 100644 index 0000000..553243a --- /dev/null +++ b/app/crud.py @@ -0,0 +1,10 @@ +from sqlalchemy.orm import Session + +import models +from app import schemas + + +def get_product(db: Session, product_gid: int): + product = db.query(models.Product).filter(models.Product.gid ==product_gid).first() + + return product diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..3b97814 --- /dev/null +++ b/app/main.py @@ -0,0 +1,27 @@ +from fastapi import Depends, FastAPI, HTTPException +from sqlalchemy.orm import Session + +from app import crud, schemas +import models +from database import SessionLocal, engine + +models.Base.metadata.create_all(bind=engine) + +app = FastAPI() + + +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@app.get("/products/{product_gid}/", response_model=schemas.Product) +def read_product(product_gid: int, db: Session = Depends(get_db)): + db_product = crud.get_product(db, product_gid=product_gid) + if db_product is None: + raise HTTPException(status_code=404, detail="Product not found") + return db_product diff --git a/app/schemas.py b/app/schemas.py new file mode 100644 index 0000000..f298673 --- /dev/null +++ b/app/schemas.py @@ -0,0 +1,24 @@ +from typing import List +from pydantic import BaseModel, ConfigDict +from datetime import datetime + + +class Price(BaseModel): + model_config = ConfigDict(from_attributes=True) + + gid: int + amount: float = None + product_gid: int + created: datetime + + +class Product(BaseModel): + model_config = ConfigDict(from_attributes=True) + + gid: int + name: str = None + url: str = None + prices: List[Price] + created: datetime + last_updated: datetime + diff --git a/app/test_main.py b/app/test_main.py new file mode 100644 index 0000000..eff36dc --- /dev/null +++ b/app/test_main.py @@ -0,0 +1,42 @@ +from fastapi.testclient import TestClient +from fastapi import Depends +import models +from database import SessionLocal, engine +from sqlalchemy.orm import Session + +models.Base.metadata.create_all(bind=engine) + +from .main import app +from datetime import datetime + +client = TestClient(app) + + +def test_read_main(): + + name = "Sample Shirt" + url = "https://www.outdoorstore.com" + amount = 123.45 + + db = SessionLocal() + + product = db.query(models.Product).filter_by(name=name).one_or_none() + if not product: + # Add a test product + product = models.Product(name=name, url=url, created=datetime.now(), last_updated=datetime.now()) + db.add(product) + db.commit() + db.refresh(product) + + # Add a test price + price = models.Price(amount=amount, product=product, created=datetime.now()) + db.add(price) + db.commit() + + response = client.get(f'/products/{product.gid}/') + assert response.status_code == 200 + data = response.json() + + assert data['name'] == name + assert data['url'] == url + assert data['prices'][0]['amount'] == amount diff --git a/database.py b/database.py new file mode 100644 index 0000000..4f40bd7 --- /dev/null +++ b/database.py @@ -0,0 +1,13 @@ +from os import getenv +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +SQLALCHEMY_DATABASE_URL = getenv('DATABASE_URL', 'postgresql://postgres:postgres@localhost/changedetection') +# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() diff --git a/fly.toml b/fly.toml index d7df977..d141334 100644 --- a/fly.toml +++ b/fly.toml @@ -12,6 +12,7 @@ primary_region = "syd" release_command = "alembic upgrade head" [processes] + app = "uvicorn main:app --host 0.0.0.0 --port 3000" worker = "python main.py" [[services]] @@ -21,3 +22,10 @@ primary_region = "syd" min_machines_running = 0 processes = ["worker"] +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ["app"] diff --git a/models.py b/models.py index c368a29..b94ee2c 100644 --- a/models.py +++ b/models.py @@ -2,7 +2,7 @@ from sqlalchemy import Column, Integer, String, ForeignKey, Table, Float, DateTime from sqlalchemy.orm import relationship, mapped_column -Base = declarative_base() +from database import Base class Store(Base): @@ -26,6 +26,6 @@ class Product(Base): gid = Column(Integer, primary_key=True) name = Column(String, unique=False) url = Column(String, unique=True) - prices = relationship("Price", back_populates="product") + prices = relationship("Price", back_populates="product", lazy=False) created = Column(DateTime) last_updated = Column(DateTime) diff --git a/tests.py b/price_monitor/test_spiders.py similarity index 100% rename from tests.py rename to price_monitor/test_spiders.py diff --git a/requirements.txt b/requirements.txt index 93e997d..b815722 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,6 @@ psycopg2 alembic sentry-sdk - - # This is for testing -pytest \ No newline at end of file +pytest +httpx diff --git a/scripts/test.sh b/scripts/test.sh index f1384c3..a670c86 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -5,5 +5,5 @@ set -e alembic upgrade head -pytest tests.py +pytest scrapy check