Skip to content

Commit

Permalink
💄 Update docs and fix some bug
Browse files Browse the repository at this point in the history
  • Loading branch information
lleans committed Mar 13, 2024
1 parent 8c62e77 commit 67cd45c
Showing 1 changed file with 100 additions and 40 deletions.
140 changes: 100 additions & 40 deletions router.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from os import environ
from typing import Annotated
from contextlib import asynccontextmanager

from aiohttp import ClientSession

from pydantic import BaseModel, Field
from pydantic import BaseModel

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from fastapi import FastAPI, Request, Query, Path
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.encoders import jsonable_encoder
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import RequestValidationError

from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
Expand All @@ -28,8 +29,56 @@ async def lifespan(_):
FastAPICache.init(RedisBackend(redis), prefix="muslimproapi-cache")
yield

app = FastAPI(title='MuslimPro API', lifespan=lifespan, version="2.0")
app.mount("/static", StaticFiles(directory="static"), name="static")
description: str = """
# Muslim Pro Scrapper 🕌
Simple scrapper using aiohttp and pyquery to get Praytime for moslem people
## Calculation Option 🕹
You can see the option by importing class with name ```CalculationMethod``` and ```AsrjuristicMethod```, same with the api request you need to pass **The Variable Name on Const Class** on params ```calcMethod``` ```asrjurMethod```, here the list of it
```python
class CalculationMethod(Enum):
DEFAULT = "Precalc"
ALGERIAN_MINISTER_OF_RELIGIOUS_AND_WAKFS = "Algeria"
DIYANET_İŞLERI_BAŞKANLIĞI = "Diyanet"
EGYPTIAN_GENERAL_AUTHORITY = "Egypt"
EGYPTIAN_GENERAL_AUTHORITY_BIS = "EgyptBis"
FIXED_ISHA_ANGLE_INTERVAL = "FixedIsha"
FRANCE_UOIF_ANGLE_12DEG = "UOIF"
FRANCE_ANGLE_15DEG = "Fr15"
FRANCE_ANGLE_18DEG = "Fr18"
ISLAMIC_UNIVERSITY_KARACHI = "Karachi"
JAKIM_JABATAN_KEMAJUAN_ISLAM_MALAYSIA = "JAKIM"
LONDON_UNIFIED_ISLAMIC_PRAYER_TIMETABLE = "UIPTL"
MUIS_MAJLIS_UGAMA_ISLAM_SINGAPURA = "MUIS"
MUSLIM_WORLD_LEAGUE_MWL = "MWL"
NORTH_AMERICA_ISNA = "ISNA"
SHIA_ITHNA_ASHARI_JAFARI = "Jafari"
SIHAT_KEMENAG_KEMENTERIAN_AGAMA_RI = "KEMENAG"
TUNISIAN_MINISTRY_OF_RELIGIOUS_AFFAIRS = "Tunisia"
UAE_GENERAL_AUTHORITY_OF_ISLAMIC_AFFAIRS_AND_ENDOWMENTS = "AwqafUAE"
UMM_AL_QURA_MAKKAH = "Makkah"
UNIVERSITY_OF_TEHRAN = "Tehran"
FEDERATION_OF_ISLAMIC_ASSOCIATIONS_IN_BASQUE_COUNTRY = "BASQUE"
class AsrjuristicMethod(Enum):
STANDARD_SHAFI_MALIKI_HANBALI = "Standard"
HANAFI = "Hanafi"
```
Here some reference for calculation method<br />
- [Prayer Time Calculation - PrayTimes.org 📚](http://praytimes.org/calculation)
- [California IslamiC University 🎓](https://www.calislamic.com/fifteen-or-eighteen-degrees-calculating-prayer-and-fasting-times-in-islam/)
"""

app = FastAPI(
title='MuslimPro API',
lifespan=lifespan,
version="2.0",
description=description,
summary="MuslimPro Scrapper cache based API"
)


async def where_ip(session: ClientSession, ip_address: str) -> str:
Expand All @@ -50,31 +99,24 @@ async def where_ip(session: ClientSession, ip_address: str) -> str:


class ModelResponse(BaseModel):
location: str = Field(default="")
calculationMethod: str = Field(default="")
asrjuristicMethod: str = Field(default="")
praytimes: dict = Field(default=dict())
ramadhan: dict | str = Field(default="")
location: str
calculationMethod: str
asrjuristicMethod: str
praytimes: dict
ramadhan: dict | str


class BaseResponse(BaseModel):
status_code: int = Field(default=200)
message: str = Field(default="OK")
data: ModelResponse = Field(default=ModelResponse())
status: int
message: str
data: ModelResponse | dict


class CustomException(Exception):

def __init__(self, status_code: int, message: str) -> None:
self.body: BaseResponse = BaseResponse()
self.body.status_code = status_code
self.body.message = message
self.body.data.location = ""


@app.get('/favicon.ico', response_class=FileResponse, include_in_schema=False)
def favicon() -> FileResponse:
return FileResponse("static/favicon.ico")
def __init__(self, status: int, message: str) -> None:
self.status: int = status
self.message: str = message


@app.get('/', response_class=RedirectResponse, responses={
Expand All @@ -94,19 +136,23 @@ async def get_location_based(request: Request) -> RedirectResponse:
redirect_url = request.url_for('main_app', **{'query': location})
return RedirectResponse(redirect_url)

except (IndexError, KeyError):
raise CustomException(
status_code=404, message="Location was not found!!")
except:
except KeyError:
raise CustomException(
status_code=500, message="Something went wrong!?")
status=404, message="Location was not found!!")


@app.get('/{query}', response_model=BaseResponse, responses={
200: {'description': "Will return the data, as the model", 'model': BaseResponse}
200: {'description': "Will return the data, as the model", 'model': BaseResponse},
404: {'description': "Will return when data is not found", 'model': BaseResponse},
422: {'description': "Will return when peforming bad request", 'model': BaseResponse}
})
@cache(expire=86400)
async def main_app(query: str, calcMethod: str = CalculationMethod.DEFAULT.name, asjurMethod: str = AsrjuristicMethod.STANDARD_SHAFI_MALIKI_HANBALI.name) -> JSONResponse:
async def main_app(
query: Annotated[str, Path(
title="Location query", description="Pass your city/country name")],
calcMethod: Annotated[str, Query(title="Calculation method params", description="Read the source code for more info(CalculationMethod)",
pattern="^[a-zA-Z_]+$")] = CalculationMethod.DEFAULT.name,
asjurMethod: Annotated[str, Query(title="Asrjuristic method params", description="Read the source code for more info(AsrjuristicMethod)", pattern="^[a-zA-Z_]+$")] = AsrjuristicMethod.STANDARD_SHAFI_MALIKI_HANBALI.name) -> JSONResponse:
"""
Main Appplication
Expand All @@ -116,7 +162,10 @@ async def main_app(query: str, calcMethod: str = CalculationMethod.DEFAULT.name,
:calcMethod[Optional] -> Put calculation method(read source code)\n
:asjurMethod[Optional] -> Put asrjuristic method(read source code)
"""
resp: BaseResponse = BaseResponse()
resp: BaseResponse = BaseResponse(status=200, message="OK",
data=ModelResponse(
location="", calculationMethod="", asrjuristicMethod="", praytimes={}, ramadhan={})
)

par1 = calcMethod.replace(' ', '_').upper()
par2 = asjurMethod.replace(' ', '_').upper()
Expand Down Expand Up @@ -152,23 +201,34 @@ async def main_app(query: str, calcMethod: str = CalculationMethod.DEFAULT.name,

except (IndexError, KeyError):
raise CustomException(
status_code=404, message="Location was not found!!")
except:
raise CustomException(
status_code=500, message="Something went wrong!?")
status=404, message="Location was not found!!")

return JSONResponse(content=jsonable_encoder(resp))


@app.exception_handler(RequestValidationError)
async def validation_handling(_, __) -> JSONResponse:
resp: BaseResponse = BaseResponse(
status=400, message="Bad request, please check your datatypes or make sure to fill all parameter", data={})
return JSONResponse(content=jsonable_encoder(resp), status_code=resp.status)


@app.exception_handler(404)
async def error_handling_lf(_, __) -> RedirectResponse:
return RedirectResponse(url='/docs')


@app.exception_handler(500)
@app.exception_handler(422)
@app.exception_handler(400)
@app.exception_handler(CustomException)
async def error_handling(_, exec: Exception) -> JSONResponse:
resp: BaseResponse = BaseResponse(
status=500, message="Something went wrong!! " + str(exec), data={})

if isinstance(exec, CustomException):
return JSONResponse(content=jsonable_encoder(exec.body), status_code=exec.body.status_code)
resp.status = exec.status or 500
resp.message = exec.message

return RedirectResponse(url='/docs')
return JSONResponse(content=jsonable_encoder(resp), status_code=resp.status)

if __name__ == "__main__":
uvicorn.run("router:app", host="0.0.0.0",
Expand Down

0 comments on commit 67cd45c

Please sign in to comment.