diff --git a/README.md b/README.md index d69c043..06a0551 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ $ source ./env/bin/activate $ pip install -r requirements.txt ``` +**NOTE:** Python 3.12 is not yet supported. + ## Setup ``` diff --git a/app/models.py b/app/models.py index 9643af1..841d57e 100644 --- a/app/models.py +++ b/app/models.py @@ -127,7 +127,7 @@ async def stats(db, query): ] ) - select = sqlalchemy.select(measurements_stats) + select = sqlalchemy.select(*measurements_stats) select = select.group_by( measurements.c.sensor, measurements.c.source, @@ -166,8 +166,9 @@ async def create_new_key(db: Database, provider: str) -> str: @staticmethod async def get_providers(db: Database) -> List[Tuple[str, int]]: query = sqlalchemy.select( - [providers.c.provider, sqlalchemy.func.count(providers.c.provider)] - ).group_by(providers.c.provider) + providers.c.provider, sqlalchemy.func.count(providers.c.provider) + ) + query = query.group_by(providers.c.provider) query = query.order_by(sqlalchemy.asc(providers.c.provider)) return await db.fetch_all(query) @@ -187,14 +188,14 @@ async def revoke_all_keys(db: Database, provider: str) -> bool: @staticmethod async def get_all_keys(db: Database) -> Set[str]: - query = sqlalchemy.select([providers.c.api_key_hash]) + query = sqlalchemy.select(providers.c.api_key_hash) query = query.order_by(sqlalchemy.asc(providers.c.provider)) keys = await db.fetch_all(query) return {k[0] for k in keys} @staticmethod async def get_provider_for_key(db: Database, api_key_hash: str) -> Union[str, None]: - query = sqlalchemy.select([providers.c.id, providers.c.api_key_hash]) + query = sqlalchemy.select(providers.c.id, providers.c.api_key_hash) query = query.where(providers.c.api_key_hash == api_key_hash) provider = await db.fetch_one(query) return provider[0] if provider else None diff --git a/app/reports.py b/app/reports.py index 93d551e..7613740 100644 --- a/app/reports.py +++ b/app/reports.py @@ -71,9 +71,11 @@ def get_quality(source): breakpoint_index = CONCENTRATIONS.index(concentration) breakpoint = BREAKPOINTS[breakpoint_index] - index = ( - (breakpoint[1] - breakpoint[0]) / (concentration[1] - concentration[0]) - ) * (source.pm2dot5_average - concentration[0]) + breakpoint[0] + index = int( + ((breakpoint[1] - breakpoint[0]) / (concentration[1] - concentration[0])) + * (source.pm2dot5_average - concentration[0]) + + breakpoint[0] + ) index_breakpoint = next( (b for b in BREAKPOINTS if index <= b[1]), BREAKPOINTS[-1], diff --git a/app/schemas.py b/app/schemas.py index 67d4840..24904d0 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -124,37 +124,37 @@ def to_orm(self, provider): return _dict class Config: - orm_mode = True + from_attributes = True @dataclass class QueryParams: - source: str = Query( + source: Optional[str] = Query( None, title="Source", description="Include measurements from this source only", ) - start: datetime = Query( + start: Optional[datetime] = Query( None, title="Start", description="Include measurements after this date and time", ) - end: datetime = Query( + end: Optional[datetime] = Query( None, title="End", description="Include measurements before this date and time", ) - longitude: float = Query( + longitude: Optional[float] = Query( None, title="Longitude", description="Target longitude coordinate", ) - latitude: float = Query( + latitude: Optional[float] = Query( None, title="Latitude", description="Target latitude coordinate", ) - distance: float = Query( + distance: Optional[float] = Query( None, title="Distance", description="Include measurements that are this kilometers far from the target", @@ -174,7 +174,7 @@ class Provider(BaseModel): ) class Config: - orm_mode = True + from_attributes = True class APIKey(BaseModel): @@ -233,7 +233,7 @@ class Report(BaseModel): title="Latitude", description="Target latitude coordinate", ) - quality: Quality = Field( + quality: Optional[Quality] = Field( None, title="Quality", description="Quality according to AQI", diff --git a/app/service.py b/app/service.py index 08178fd..ebb4bac 100644 --- a/app/service.py +++ b/app/service.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from contextlib import asynccontextmanager from fastapi import FastAPI, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.security.api_key import APIKey @@ -25,7 +26,14 @@ from .authentication import validate_api_key, validate_master_key -app = FastAPI() +@asynccontextmanager +async def lifespan(app: FastAPI): + await db.connect() + yield + await db.disconnect() + + +app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -35,16 +43,6 @@ ) -@app.on_event("startup") -async def startup(): - await db.connect() - - -@app.on_event("shutdown") -async def shutdown(): - await db.disconnect() - - @app.post("/api/v1/providers", response_model=schemas.APIKey) async def create_provider( provider: schemas.Provider, key: APIKey = Depends(validate_master_key) diff --git a/requirements.txt b/requirements.txt index 05c1055..0f96929 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -fastapi==0.94.1 +fastapi==0.114.1 httpx==0.23.3 uvicorn==0.21.0 gunicorn==20.1.0 @@ -9,8 +9,9 @@ black==23.1.0 aiosqlite==0.18.0 databases==0.7.0 SQLAlchemy==1.4.46 -asyncpg==0.27.0 -psycopg2-binary==2.9.5 +asyncpg==0.29.0 +psycopg2-binary==2.9.9 alembic==1.10.2 geopy==2.3.0 -pytest-dependency==0.5.1 \ No newline at end of file +pytest-dependency==0.5.1 +pydantic==2.9.1 diff --git a/tests/service.py b/tests/service.py index 845298c..47e0d70 100644 --- a/tests/service.py +++ b/tests/service.py @@ -43,7 +43,7 @@ "co2": 1000.0, "longitude": -57.521369, "latitude": -25.194156, - "recorded": "2020-10-24T20:47:57.370721+00:00", + "recorded": "2020-10-24T20:47:57.370721Z", }, { "sensor": "nullable", @@ -59,7 +59,7 @@ "co2": None, "longitude": -57.521369, "latitude": -25.194156, - "recorded": "2020-10-24T20:47:57.370721+00:00", + "recorded": "2020-10-24T20:47:57.370721Z", }, { "sensor": "test", @@ -75,7 +75,7 @@ "co2": 200.0, "longitude": -57.521369, "latitude": -25.194156, - "recorded": "2020-10-24T20:47:57.370721+00:00", + "recorded": "2020-10-24T20:47:57.370721Z", }, ] aqi = [ @@ -322,7 +322,7 @@ def test_delete_provider(client): @pytest.mark.dependency(depends=["test_delete_provider"]) def test_empty_measurements(client): query = { - "start": "1984-04-24T00:00:00", + "start": "2984-04-24T00:00:00", } response = client.get(f"/api/v1/measurements?{urlencode(query)}")