Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api docs enhancements #231

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ebd093f
Added description that Provides a brief overview of the API's functio…
mmbogajemimah Mar 18, 2024
89c648a
Added description that Provides a brief overview of the API's functio…
mmbogajemimah Mar 18, 2024
d78246f
Removing trailing slash on Auth APIs
mmbogajemimah Mar 18, 2024
bc48838
Removing trailing slash on Custom Exports APIs
mmbogajemimah Mar 18, 2024
dd678c8
Removing trailing slash on HDX APIs
mmbogajemimah Mar 18, 2024
4a67deb
Removing trailing slash on Raw data APIs
mmbogajemimah Mar 18, 2024
5a1bb3c
Removing trailing slash on statistics for the specified polygon APIs
mmbogajemimah Mar 18, 2024
8954737
Removing trailing slash on tasks APIs
mmbogajemimah Mar 18, 2024
c328827
Removing trailing slash on S3 APIs
mmbogajemimah Mar 18, 2024
b4f59a7
Added description that Provides a brief overview of the API's functio…
mmbogajemimah Mar 18, 2024
0b7ef72
Documenting 403, 404 and 500 Responses on Auth APIs
mmbogajemimah Mar 20, 2024
3bd6f7c
Add changes that when the User is not found in the Database 404 Error…
mmbogajemimah Mar 20, 2024
6f32ae2
A File that contains all responses a user might get while sending a r…
mmbogajemimah Mar 20, 2024
9912908
Updated the Responses using a DRY Approach
mmbogajemimah Mar 20, 2024
fc6a117
Updated Tasks 404, 403, 500 Responses using a DRY Approach
mmbogajemimah Mar 20, 2024
293c87b
Added Docstrings to explain test functions, added exception 403 for a…
mmbogajemimah Mar 26, 2024
1a2f40a
Added docstrings to describe test_app.py test functions
mmbogajemimah Mar 26, 2024
742cac6
Add documentation on how to set env variables in docker and how to ru…
mmbogajemimah Mar 26, 2024
29765a8
How to run Tests in Docker Container
mmbogajemimah Mar 26, 2024
e3b6c81
Added 401 and 500 responses and content
mmbogajemimah Mar 29, 2024
12073ba
Added 401 and 500 responses and content
mmbogajemimah Mar 29, 2024
1de495b
Added 401 and 500 responses and content in routers.py
mmbogajemimah Mar 29, 2024
3d72f98
HDX file in black format
mmbogajemimah Apr 2, 2024
5d50d4b
tasks file in black format
mmbogajemimah Apr 2, 2024
8e3a666
S3 file in black format
mmbogajemimah Apr 2, 2024
556f36e
stats file in black format
mmbogajemimah Apr 2, 2024
2a302fb
routers file in black format
mmbogajemimah Apr 2, 2024
bf222dd
raw_data file in black format
mmbogajemimah Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion API/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ class AuthUser(BaseModel):

osm_auth = Auth(*get_oauth_credentials())


def get_user_from_db(osm_id: int):
auth = Users()
user = auth.read_user(osm_id)
# Add changes that when the User is not found in the Database 404 Error is raised
if not user:
raise HTTPException(status_code=404, detail="User not Found in the Database")
return user


Expand Down
24 changes: 24 additions & 0 deletions API/auth/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fastapi import HTTPException
from pydantic import BaseModel

# Define common error responses
common_error_responses = {
403: {"description": "Forbidden", "model": HTTPException},
404: {"description": "Not Found", "model": HTTPException},
500: {"description": "Internal Server Error", "model": HTTPException},
}

# Define shared error response models
class ErrorResponse(BaseModel):
detail: str

# Add a default response model for error responses
for status_code in common_error_responses:
if "model" not in common_error_responses[status_code]:
common_error_responses[status_code]["model"] = ErrorResponse

error_responses_with_examples = {
403: {"content": {"application/json": {"example": {"message": "Access forbidden"}}}},
404: {"content": {"application/json": {"example": {"error": "Not Found"}}}},
500: {"content": {"application/json": {"example": {"error": "Internal Server Error"}}}},
}
67 changes: 54 additions & 13 deletions API/auth/routers.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import json

from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, Request, HTTPException
from pydantic import BaseModel

from src.app import Users

from . import AuthUser, admin_required, login_required, osm_auth, staff_required
from .responses import common_error_responses, error_responses_with_examples

router = APIRouter(prefix="/auth", tags=["Auth"])


@router.get("/login/")
@router.get(
"/login", responses={**common_error_responses, **error_responses_with_examples}
)
def login_url(request: Request):
"""Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap.
Click on the download url returned to get access_token.
Expand All @@ -25,7 +28,9 @@ def login_url(request: Request):
return login_url


@router.get("/callback/")
@router.get(
"/callback", responses={**common_error_responses, **error_responses_with_examples}
)
def callback(request: Request):
"""Performs token exchange between OpenStreetMap and Raw Data API

Expand All @@ -37,12 +42,22 @@ def callback(request: Request):
Returns:
- access_token (string)
"""
access_token = osm_auth.callback(str(request.url))
try:
access_token = osm_auth.callback(str(request.url))
except Exception as ex:
raise HTTPException(
status_code=500,
detail="Internal Server Error occurred while performing token exchange between OpenStreetMap and Raw Data API",
)

return access_token


@router.get("/me/", response_model=AuthUser)
@router.get(
"/me",
response_model=AuthUser,
responses={**common_error_responses, **error_responses_with_examples},
)
def my_data(user_data: AuthUser = Depends(login_required)):
"""Read the access token and provide user details from OSM user's API endpoint,
also integrated with underpass .
Expand All @@ -64,7 +79,11 @@ class User(BaseModel):


# Create user
@router.post("/users/", response_model=dict)
@router.post(
"/users",
response_model=dict,
responses={**common_error_responses, **error_responses_with_examples},
)
async def create_user(params: User, user_data: AuthUser = Depends(admin_required)):
"""
Creates a new user and returns the user's information.
Expand All @@ -87,7 +106,11 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required


# Read user by osm_id
@router.get("/users/{osm_id}", response_model=dict)
@router.get(
"/users/{osm_id}",
response_model=dict,
responses={**common_error_responses, **error_responses_with_examples},
)
async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)):
"""
Retrieves user information based on the given osm_id.
Expand All @@ -103,15 +126,20 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)):
- Dict[str, Any]: A dictionary containing user information.

Raises:
- HTTPException: If the user with the given osm_id is not found.
- HTTPException 404: If the user with the given osm_id is not found.
- HTTPException 403: If the user is not a staff.
"""
auth = Users()

return auth.read_user(osm_id)


# Update user by osm_id
@router.put("/users/{osm_id}", response_model=dict)
@router.put(
"/users/{osm_id}",
response_model=dict,
responses={**common_error_responses, **error_responses_with_examples},
)
async def update_user(
osm_id: int, update_data: User, user_data: AuthUser = Depends(admin_required)
):
Expand All @@ -129,14 +157,19 @@ async def update_user(
- Dict[str, Any]: A dictionary containing the updated user information.

Raises:
- HTTPException: If the user with the given osm_id is not found.
- HTTPException 403: If the user is not an Admin.
- HTTPException 404: If the user with the given osm_id is not found.
"""
auth = Users()
return auth.update_user(osm_id, update_data)


# Delete user by osm_id
@router.delete("/users/{osm_id}", response_model=dict)
@router.delete(
"/users/{osm_id}",
response_model=dict,
responses={**common_error_responses, **error_responses_with_examples},
)
async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)):
"""
Deletes a user based on the given osm_id.
Expand All @@ -148,14 +181,19 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)
- Dict[str, Any]: A dictionary containing the deleted user information.

Raises:
- HTTPException: If the user with the given osm_id is not found.
- HTTPException 404: If the user with the given osm_id is not found.
- HTTPException 403: If the user is not an Admin.
"""
auth = Users()
return auth.delete_user(osm_id)


# Get all users
@router.get("/users/", response_model=list)
@router.get(
"/users",
response_model=list,
responses={**common_error_responses, **error_responses_with_examples},
)
async def read_users(
skip: int = 0, limit: int = 10, user_data: AuthUser = Depends(staff_required)
):
Expand All @@ -168,6 +206,9 @@ async def read_users(

Returns:
- List[Dict[str, Any]]: A list of dictionaries containing user information.

Raises:
- HTTPException 403: If the user is not a Staff.
"""
auth = Users()
return auth.read_users(skip, limit)
3 changes: 1 addition & 2 deletions API/custom_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

router = APIRouter(prefix="/custom", tags=["Custom Exports"])


@router.post("/snapshot/")
@router.post("/snapshot")
@limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute")
@version(1)
async def process_custom_requests(
Expand Down
2 changes: 1 addition & 1 deletion API/hdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ async def read_hdx_list(
return hdx_list


@router.get("/search/", response_model=List[dict])
@router.get("/search", response_model=List[dict])
@limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute")
@version(1)
async def search_hdx(
Expand Down
7 changes: 6 additions & 1 deletion API/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@

os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

app = FastAPI(title="Raw Data API ", swagger_ui_parameters={"syntaxHighlight": False})
# Provides a brief overview of the API's functionality for documentation purposes.
app = FastAPI(
title="Raw Data API",
description="Raw Data API is a set of high-performant APIs for transforming and exporting OpenStreetMap (OSM) data in different GIS file formats.",
swagger_ui_parameters={"syntaxHighlight": False}
)
app.include_router(auth_router)
app.include_router(raw_data_router)
app.include_router(tasks_router)
Expand Down
26 changes: 21 additions & 5 deletions API/raw_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from fastapi import APIRouter, Body, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi_versioning import version
from .auth.responses import common_error_responses, error_responses_with_examples

from src.app import RawData
from src.config import (
Expand All @@ -51,15 +52,23 @@
redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL)


@router.get("/status/", response_model=StatusResponse)
@router.get(
"/status",
response_model=StatusResponse,
responses={**common_error_responses, **error_responses_with_examples},
)
@version(1)
def check_database_last_updated():
"""Gives status about how recent the osm data is , it will give the last time that database was updated completely"""
result = RawData().check_status()
return {"last_updated": result}


@router.post("/snapshot/", response_model=SnapshotResponse)
@router.post(
"/snapshot",
response_model=SnapshotResponse,
responses={**common_error_responses, **error_responses_with_examples},
)
@limiter.limit(f"{export_rate_limit}/minute")
@version(1)
def get_osm_current_snapshot_as_file(
Expand Down Expand Up @@ -462,7 +471,10 @@ def get_osm_current_snapshot_as_file(
)


@router.post("/snapshot/plain/")
@router.post(
"/snapshot/plain",
responses={**common_error_responses, **error_responses_with_examples},
)
@version(1)
def get_osm_current_snapshot_as_plain_geojson(
request: Request,
Expand Down Expand Up @@ -494,14 +506,18 @@ def get_osm_current_snapshot_as_plain_geojson(
return result


@router.get("/countries/")
@router.get(
"/countries", responses={**common_error_responses, **error_responses_with_examples}
)
@version(1)
def get_countries(q: str = ""):
result = RawData().get_countries_list(q)
return result


@router.get("/osm_id/")
@router.get(
"/osm_id", responses={**common_error_responses, **error_responses_with_examples}
)
@version(1)
def get_osm_feature(osm_id: int):
return RawData().get_osm_feature(osm_id)
2 changes: 1 addition & 1 deletion API/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
paginator = s3.get_paginator("list_objects_v2")


@router.get("/files/")
@router.get("/files")
@limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute")
@version(1)
async def list_s3_files(
Expand Down
2 changes: 1 addition & 1 deletion API/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
router = APIRouter(prefix="/stats", tags=["Stats"])


@router.post("/polygon/")
@router.post("/polygon")
@limiter.limit(f"{POLYGON_STATISTICS_API_RATE_LIMIT}/minute")
@version(1)
async def get_polygon_stats(
Expand Down