From a8712f8ecac25282059e0c103640a2d6acca5dd5 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Mon, 5 Sep 2022 20:42:51 +0300 Subject: [PATCH 1/9] [#H26].(added service skeleton) --- Shops/pizzeria/homeworks/fastapi_hw/main.py | 12 ++++ Shops/pizzeria/homeworks/fastapi_hw/models.py | 27 ++++++++ .../pizzeria/homeworks/fastapi_hw/services.py | 61 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 Shops/pizzeria/homeworks/fastapi_hw/main.py create mode 100644 Shops/pizzeria/homeworks/fastapi_hw/models.py create mode 100644 Shops/pizzeria/homeworks/fastapi_hw/services.py diff --git a/Shops/pizzeria/homeworks/fastapi_hw/main.py b/Shops/pizzeria/homeworks/fastapi_hw/main.py new file mode 100644 index 0000000..098cae9 --- /dev/null +++ b/Shops/pizzeria/homeworks/fastapi_hw/main.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from models import RequestInfo, ResponseInfo + +app = FastAPI(title='Funny facts about Cats and Dogs') + + +@app.post( + '/info', + description='Get funny facts about Cats nd Dogs', +) +async def get_info(request: RequestInfo) -> ResponseInfo: + pass diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py new file mode 100644 index 0000000..598747d --- /dev/null +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -0,0 +1,27 @@ +from pydantic import BaseModel, ValidationError, validator +from typing import Optional + + +class RequestInfo(BaseModel): + cats: int + dogs: int + + @validator('cats', 'dogs') + def num_of_facts(cls, num): + if num > 5: + raise ValidationError('Too many facts you want to know') + return num + + +class DogsFactsResponse(BaseModel): + facts: tuple[str, ...] + success: bool + + +class CatsFactsResponse(BaseModel): + data: tuple[str, ...] + + +class ResponseInfo(BaseModel): + cats: tuple[str, ...] + dogs: tuple[str, ...] diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py new file mode 100644 index 0000000..4cf0ead --- /dev/null +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -0,0 +1,61 @@ +import aiohttp +from models import CatsFactsResponse, DogsFactsResponse + +CATS_URL = 'https://cat-fact.herokuapp.com/facts' +DOGS_URL = 'http://dog-api.kinduff.com/api/facts' + +ANIMALS_CREDENTIALS = { + 'cats': { + 'url': 'https://cat-fact.herokuapp.com/facts', + 'param': 'count' + }, + 'dogs': { + 'url': 'http://dog-api.kinduff.com/api/facts', + 'param': 'number' + }, +} + + +# class CatsService: +# def __init__(self, service_url: str, facts_number_param: str): +# self.url = service_url +# self.facts_number_param = facts_number_param +# +# def get_facts(self, facts_number: int = None): +# async with aiohttp.ClientSession() as session: +# url = 'http://api.openweathermap.org/data/2.5/weather' +# params = {'q': city, 'APPID': '1f39ad38400b62f2f70eaa91ef87e50a'} +# +# async with session.get(url=url, params=params) as response: +# response = await response.json() +# return response +# +# +# class DogsService: +# def __init__(self, service_url: str): +# self.url = service_url +def get_facts(url: str, params: dict = None): + async with aiohttp.ClientSession() as session: + async with session.get(url=url, params=params) as response: + response = await response.json() + return response + + +def get_cats_facts(facts_number: int = None) -> CatsFactsResponse: + params = None + if facts_number: + params = {'count': facts_number} + response = await get_facts(CATS_URL, params) + return CatsFactsResponse(**response) + + +def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: + params = None + if facts_number: + params = {'number': facts_number} + response = await get_facts(DOGS_URL, params) + return DogsFactsResponse(**response) + + +def process_request(request: dict): + pass From 88dd282a85c92b928b108e75b7a527ca41e314d5 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Tue, 6 Sep 2022 21:00:44 +0300 Subject: [PATCH 2/9] [#H26].(implemented request processing) --- Shops/pizzeria/homeworks/fastapi_hw/main.py | 12 +++- .../pizzeria/homeworks/fastapi_hw/services.py | 62 ++++++++----------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/main.py b/Shops/pizzeria/homeworks/fastapi_hw/main.py index 098cae9..2d13fa3 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/main.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/main.py @@ -1,5 +1,7 @@ from fastapi import FastAPI from models import RequestInfo, ResponseInfo +from services import process_request, make_response +import uvicorn app = FastAPI(title='Funny facts about Cats and Dogs') @@ -9,4 +11,12 @@ description='Get funny facts about Cats nd Dogs', ) async def get_info(request: RequestInfo) -> ResponseInfo: - pass + data = await process_request(request=request.dict()) + return make_response(data=data) + +if __name__ == "__main__": + uvicorn.run( + app=app, + host='127.0.0.1', + port=8000 + ) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py index 4cf0ead..c505066 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/services.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -1,47 +1,18 @@ import aiohttp -from models import CatsFactsResponse, DogsFactsResponse +from models import CatsFactsResponse, DogsFactsResponse, ResponseInfo -CATS_URL = 'https://cat-fact.herokuapp.com/facts' +CATS_URL = 'https://meowfacts.herokuapp.com/' DOGS_URL = 'http://dog-api.kinduff.com/api/facts' -ANIMALS_CREDENTIALS = { - 'cats': { - 'url': 'https://cat-fact.herokuapp.com/facts', - 'param': 'count' - }, - 'dogs': { - 'url': 'http://dog-api.kinduff.com/api/facts', - 'param': 'number' - }, -} - -# class CatsService: -# def __init__(self, service_url: str, facts_number_param: str): -# self.url = service_url -# self.facts_number_param = facts_number_param -# -# def get_facts(self, facts_number: int = None): -# async with aiohttp.ClientSession() as session: -# url = 'http://api.openweathermap.org/data/2.5/weather' -# params = {'q': city, 'APPID': '1f39ad38400b62f2f70eaa91ef87e50a'} -# -# async with session.get(url=url, params=params) as response: -# response = await response.json() -# return response -# -# -# class DogsService: -# def __init__(self, service_url: str): -# self.url = service_url -def get_facts(url: str, params: dict = None): +async def get_facts(url: str, params: dict = None): async with aiohttp.ClientSession() as session: - async with session.get(url=url, params=params) as response: + async with session.get(url=url, params=params, timeout=5) as response: response = await response.json() return response -def get_cats_facts(facts_number: int = None) -> CatsFactsResponse: +async def get_cats_facts(facts_number: int = None) -> CatsFactsResponse: params = None if facts_number: params = {'count': facts_number} @@ -49,7 +20,7 @@ def get_cats_facts(facts_number: int = None) -> CatsFactsResponse: return CatsFactsResponse(**response) -def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: +async def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: params = None if facts_number: params = {'number': facts_number} @@ -57,5 +28,22 @@ def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: return DogsFactsResponse(**response) -def process_request(request: dict): - pass +facts_about_animals = { + 'cats': get_cats_facts, + 'dogs': get_dogs_facts +} + + +async def process_request(request: dict): + result = {} + for animal, facts_num in request.items(): + result[animal] = await facts_about_animals.get(animal)(facts_num) + return result + + +def make_response(data: dict) -> ResponseInfo: + result = { + 'cats': data.get('cats').data, + 'dogs': data.get('dogs').facts + } + return ResponseInfo(**result) From e20fe7cb524a43da4e8519091bf751a04bd8b7b7 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Wed, 7 Sep 2022 20:52:04 +0300 Subject: [PATCH 3/9] [#H26].(implemented request validation) --- Shops/pizzeria/homeworks/fastapi_hw/main.py | 1 + Shops/pizzeria/homeworks/fastapi_hw/models.py | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/main.py b/Shops/pizzeria/homeworks/fastapi_hw/main.py index 2d13fa3..714ba98 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/main.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/main.py @@ -14,6 +14,7 @@ async def get_info(request: RequestInfo) -> ResponseInfo: data = await process_request(request=request.dict()) return make_response(data=data) + if __name__ == "__main__": uvicorn.run( app=app, diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py index 598747d..e2304c0 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/models.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -1,16 +1,17 @@ from pydantic import BaseModel, ValidationError, validator -from typing import Optional class RequestInfo(BaseModel): cats: int dogs: int - @validator('cats', 'dogs') - def num_of_facts(cls, num): - if num > 5: + @validator('*') + def num_of_facts(cls, value): + if value > 5: raise ValidationError('Too many facts you want to know') - return num + elif value <= 0: + raise ValidationError('Facts number can`t be less or equal zero') + return value class DogsFactsResponse(BaseModel): From 43fcd21420cba7b3ce6256c4429333e2c140941e Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 16:20:38 +0300 Subject: [PATCH 4/9] [#H26].(refactored request processing) --- Shops/pizzeria/homeworks/fastapi_hw/main.py | 6 ++--- Shops/pizzeria/homeworks/fastapi_hw/models.py | 9 ++++---- .../pizzeria/homeworks/fastapi_hw/services.py | 22 +++++++------------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/main.py b/Shops/pizzeria/homeworks/fastapi_hw/main.py index 714ba98..9dd9f0a 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/main.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI from models import RequestInfo, ResponseInfo -from services import process_request, make_response +from services import process_request import uvicorn app = FastAPI(title='Funny facts about Cats and Dogs') @@ -10,9 +10,9 @@ '/info', description='Get funny facts about Cats nd Dogs', ) -async def get_info(request: RequestInfo) -> ResponseInfo: +async def get_info(request: RequestInfo) -> dict: data = await process_request(request=request.dict()) - return make_response(data=data) + return ResponseInfo(**data).dict(exclude_none=True) if __name__ == "__main__": diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py index e2304c0..8183b71 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/models.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -1,9 +1,10 @@ from pydantic import BaseModel, ValidationError, validator +from typing import Optional, Union class RequestInfo(BaseModel): - cats: int - dogs: int + cats: Optional[int] + dogs: Optional[int] @validator('*') def num_of_facts(cls, value): @@ -24,5 +25,5 @@ class CatsFactsResponse(BaseModel): class ResponseInfo(BaseModel): - cats: tuple[str, ...] - dogs: tuple[str, ...] + cats: Optional[tuple[str, ...]] + dogs: Union[tuple[str, ...], None] diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py index c505066..f4b2fed 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/services.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -1,31 +1,32 @@ import aiohttp +from typing import Optional from models import CatsFactsResponse, DogsFactsResponse, ResponseInfo CATS_URL = 'https://meowfacts.herokuapp.com/' DOGS_URL = 'http://dog-api.kinduff.com/api/facts' -async def get_facts(url: str, params: dict = None): +async def get_facts(url: str, params: Optional[dict]): async with aiohttp.ClientSession() as session: async with session.get(url=url, params=params, timeout=5) as response: response = await response.json() return response -async def get_cats_facts(facts_number: int = None) -> CatsFactsResponse: +async def get_cats_facts(facts_number: Optional[int]) -> tuple: params = None if facts_number: params = {'count': facts_number} response = await get_facts(CATS_URL, params) - return CatsFactsResponse(**response) + return CatsFactsResponse(**response).data -async def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: +async def get_dogs_facts(facts_number: Optional[int]) -> tuple: params = None if facts_number: params = {'number': facts_number} response = await get_facts(DOGS_URL, params) - return DogsFactsResponse(**response) + return DogsFactsResponse(**response).facts facts_about_animals = { @@ -37,13 +38,6 @@ async def get_dogs_facts(facts_number: int = None) -> DogsFactsResponse: async def process_request(request: dict): result = {} for animal, facts_num in request.items(): - result[animal] = await facts_about_animals.get(animal)(facts_num) + if facts_num is not None: + result[animal] = await facts_about_animals.get(animal)(facts_num) return result - - -def make_response(data: dict) -> ResponseInfo: - result = { - 'cats': data.get('cats').data, - 'dogs': data.get('dogs').facts - } - return ResponseInfo(**result) From 24dab90b5382047900364b2683bf982c64f9c0a2 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 16:22:14 +0300 Subject: [PATCH 5/9] [#H26].(refactored ResponseInfo model) --- Shops/pizzeria/homeworks/fastapi_hw/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py index 8183b71..97a09fa 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/models.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -26,4 +26,4 @@ class CatsFactsResponse(BaseModel): class ResponseInfo(BaseModel): cats: Optional[tuple[str, ...]] - dogs: Union[tuple[str, ...], None] + dogs: Optional[tuple[str, ...]] From 09c218ebdc7062ed475b13694622cf6711645020 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 16:39:45 +0300 Subject: [PATCH 6/9] [#H26].(deleted unused import) --- Shops/pizzeria/homeworks/fastapi_hw/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py index 97a09fa..b17e2dd 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/models.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -1,5 +1,5 @@ from pydantic import BaseModel, ValidationError, validator -from typing import Optional, Union +from typing import Optional class RequestInfo(BaseModel): From 41f3b6e58e144e83482b6b8e98786fa93f755518 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 19:09:18 +0300 Subject: [PATCH 7/9] [#H26].(added translation feature) --- Shops/pizzeria/homeworks/fastapi_hw/models.py | 6 +++- .../pizzeria/homeworks/fastapi_hw/services.py | 30 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/models.py b/Shops/pizzeria/homeworks/fastapi_hw/models.py index b17e2dd..d5c7089 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/models.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/models.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ValidationError, validator +from pydantic import BaseModel, ValidationError, validator, Field from typing import Optional @@ -24,6 +24,10 @@ class CatsFactsResponse(BaseModel): data: tuple[str, ...] +class TranslateResponse(BaseModel): + translated_text: str = Field(alias='translatedText') + + class ResponseInfo(BaseModel): cats: Optional[tuple[str, ...]] dogs: Optional[tuple[str, ...]] diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py index f4b2fed..ef3cf20 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/services.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -1,9 +1,10 @@ import aiohttp from typing import Optional -from models import CatsFactsResponse, DogsFactsResponse, ResponseInfo +from models import CatsFactsResponse, DogsFactsResponse, TranslateResponse CATS_URL = 'https://meowfacts.herokuapp.com/' DOGS_URL = 'http://dog-api.kinduff.com/api/facts' +TRANSLATION_URL = 'https://libretranslate.de/translate' async def get_facts(url: str, params: Optional[dict]): @@ -13,6 +14,27 @@ async def get_facts(url: str, params: Optional[dict]): return response +async def get_translation(url: str, json_data: dict): + async with aiohttp.ClientSession() as session: + async with session.post(url=url, json=json_data, timeout=5) as response: + response = await response.json() + return response + + +async def prepare_data_to_translation(animal_facts: tuple[str, ...], source: str, target: str): + result = [] + for fact in animal_facts: + params = { + 'q': fact, + 'source': source, + 'target': target, + 'format': 'text', + } + translated_response = await get_translation(url=TRANSLATION_URL, json_data=params) + result.append(TranslateResponse(**translated_response).translated_text) + return result + + async def get_cats_facts(facts_number: Optional[int]) -> tuple: params = None if facts_number: @@ -39,5 +61,9 @@ async def process_request(request: dict): result = {} for animal, facts_num in request.items(): if facts_num is not None: - result[animal] = await facts_about_animals.get(animal)(facts_num) + result[animal] = await prepare_data_to_translation( + await facts_about_animals.get(animal)(facts_num), + source='en', + target='ru', + ) return result From f9f54d951ac497f56e6d48c93794de6dbf3c7c75 Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 19:19:01 +0300 Subject: [PATCH 8/9] [#H26].(refactored ClientSession as singleton) --- .../pizzeria/homeworks/fastapi_hw/services.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py index ef3cf20..74c8bec 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/services.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -7,17 +7,14 @@ TRANSLATION_URL = 'https://libretranslate.de/translate' -async def get_facts(url: str, params: Optional[dict]): +async def get_data_from_api(url: str, params: Optional[dict], method: str): async with aiohttp.ClientSession() as session: - async with session.get(url=url, params=params, timeout=5) as response: - response = await response.json() - return response - - -async def get_translation(url: str, json_data: dict): - async with aiohttp.ClientSession() as session: - async with session.post(url=url, json=json_data, timeout=5) as response: - response = await response.json() + if method == 'get': + async with session.get(url=url, params=params, timeout=5) as response: + response = await response.json() + elif method == 'post': + async with session.post(url=url, json=params, timeout=5) as response: + response = await response.json() return response @@ -30,7 +27,7 @@ async def prepare_data_to_translation(animal_facts: tuple[str, ...], source: str 'target': target, 'format': 'text', } - translated_response = await get_translation(url=TRANSLATION_URL, json_data=params) + translated_response = await get_data_from_api(url=TRANSLATION_URL, params=params, method='post') result.append(TranslateResponse(**translated_response).translated_text) return result @@ -39,7 +36,7 @@ async def get_cats_facts(facts_number: Optional[int]) -> tuple: params = None if facts_number: params = {'count': facts_number} - response = await get_facts(CATS_URL, params) + response = await get_data_from_api(CATS_URL, params, method='get') return CatsFactsResponse(**response).data @@ -47,7 +44,7 @@ async def get_dogs_facts(facts_number: Optional[int]) -> tuple: params = None if facts_number: params = {'number': facts_number} - response = await get_facts(DOGS_URL, params) + response = await get_data_from_api(DOGS_URL, params, method='get') return DogsFactsResponse(**response).facts From 17ff9e9b12fe196a57c28d2bad6599304a40ed7e Mon Sep 17 00:00:00 2001 From: Eugene Syroezhkin Date: Sun, 11 Sep 2022 19:27:06 +0300 Subject: [PATCH 9/9] [#H26].(added http exceptions handling) --- Shops/pizzeria/homeworks/fastapi_hw/main.py | 11 +++++++++-- Shops/pizzeria/homeworks/fastapi_hw/services.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/main.py b/Shops/pizzeria/homeworks/fastapi_hw/main.py index 9dd9f0a..27a0fa9 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/main.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/main.py @@ -1,4 +1,5 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException +from http import HTTPStatus from models import RequestInfo, ResponseInfo from services import process_request import uvicorn @@ -11,7 +12,13 @@ description='Get funny facts about Cats nd Dogs', ) async def get_info(request: RequestInfo) -> dict: - data = await process_request(request=request.dict()) + try: + data = await process_request(request=request.dict()) + except Exception as error: + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail=error.__class__.__name__, + ) return ResponseInfo(**data).dict(exclude_none=True) diff --git a/Shops/pizzeria/homeworks/fastapi_hw/services.py b/Shops/pizzeria/homeworks/fastapi_hw/services.py index 74c8bec..4f7128f 100644 --- a/Shops/pizzeria/homeworks/fastapi_hw/services.py +++ b/Shops/pizzeria/homeworks/fastapi_hw/services.py @@ -50,7 +50,7 @@ async def get_dogs_facts(facts_number: Optional[int]) -> tuple: facts_about_animals = { 'cats': get_cats_facts, - 'dogs': get_dogs_facts + 'dogs': get_dogs_facts, }