From c91d282960c600dcd7ea003302cad4aeaf6f85f2 Mon Sep 17 00:00:00 2001 From: Ivanildo Barauna de Souza Junior Date: Tue, 25 Jun 2024 18:28:57 -0300 Subject: [PATCH] feature: Initial development for Hexagonal Arch --- src/currency_quote/__init__.py | 37 ------------------- src/currency_quote/__init__2.py | 16 ++++++++ .../adapters/inbound/controller.py | 14 +++++++ .../adapters/outbound/currency_api.py | 17 +++++++++ .../outbound/currency_validator_api.py | 22 +++++++++++ .../application/ports/inbound/__init__.py | 0 .../inbound/get_currency_quote_use_case.py | 8 ++++ .../ports/outbound/currency_repository.py | 8 ++++ .../outbound/currency_validator_repository.py | 8 ++++ .../use_cases/test_validate_currency.py | 29 +++++++++++++++ .../use_cases/validate_currency.py | 10 +++++ src/currency_quote/config/endpoints.py | 2 +- .../domain/entities/currency.py | 3 ++ src/currency_quote/domain/models/currency.py | 0 .../domain/services/get_currency_quote.py | 20 ++++++++++ .../domain/services/validate_currency.py | 19 ++++++++++ .../ports/inbound => utils}/common.py | 0 .../{adapters/outbound => utils}/logs.py | 0 18 files changed, 175 insertions(+), 38 deletions(-) create mode 100644 src/currency_quote/__init__2.py create mode 100644 src/currency_quote/adapters/outbound/currency_validator_api.py delete mode 100644 src/currency_quote/application/ports/inbound/__init__.py create mode 100644 src/currency_quote/application/ports/outbound/currency_validator_repository.py create mode 100644 src/currency_quote/application/use_cases/test_validate_currency.py create mode 100644 src/currency_quote/application/use_cases/validate_currency.py create mode 100644 src/currency_quote/domain/entities/currency.py delete mode 100644 src/currency_quote/domain/models/currency.py rename src/currency_quote/{application/ports/inbound => utils}/common.py (100%) rename src/currency_quote/{adapters/outbound => utils}/logs.py (100%) diff --git a/src/currency_quote/__init__.py b/src/currency_quote/__init__.py index 38f537b..910a393 100644 --- a/src/currency_quote/__init__.py +++ b/src/currency_quote/__init__.py @@ -7,43 +7,6 @@ class CurrencyQuote: def __init__(self, currency_list: list): self.currency_code = currency_list - def _validate_currency_code(self) -> list: - client = ClientBuilder( - endpoint=API.ENDPOINT_AVALIABLE_PARITIES, - retry_strategy=RetryStrategies.LinearRetryStrategy - ) - - valid_list = client.get_api_data() - - validated_list = [] - - for currency_code in self.currency_code: - if currency_code in valid_list: - validated_list.append(currency_code) - else: - print(f"Currency code: {currency_code} is not valid") - - if len(validated_list) == 0: - raise ValueError("No valid currency code was provided") - - return validated_list - - @staticmethod - def _get_last_quote(*args): - url = API.ENDPOINT_LAST_COTATION + ','.join(args) - client = ClientBuilder( - endpoint=url, - retry_strategy=RetryStrategies.LinearRetryStrategy - ) - - response = client.get_api_data() - - return response - - def get_last_quote(self): - last_quote = self._get_last_quote(*self._validate_currency_code()) - return last_quote - @staticmethod def _get_history_quote(*args, reference_date: int): diff --git a/src/currency_quote/__init__2.py b/src/currency_quote/__init__2.py new file mode 100644 index 0000000..7a294b4 --- /dev/null +++ b/src/currency_quote/__init__2.py @@ -0,0 +1,16 @@ +from .adapters.outbound.currency_api import CurrencyAPI +from .adapters.inbound.controller import CurrencyController +from .domain.services.get_currency_quote import GetCurrencyQuote +from .domain.services.validate_currency import ValidateCurrency + + + +class CurrencyQuote: + def __init__(self): + currency_api = CurrencyAPI() + validate_currency_service = ValidateCurrency(currency_api) + get_currency_quote_service = GetCurrencyQuote(currency_api, validate_currency_service) + self.controller = CurrencyController(get_currency_quote_service) + + def get_quote(self, currency_code: str): + return self.controller.get_quote(currency_code) diff --git a/src/currency_quote/adapters/inbound/controller.py b/src/currency_quote/adapters/inbound/controller.py index e69de29..ad8a4f3 100644 --- a/src/currency_quote/adapters/inbound/controller.py +++ b/src/currency_quote/adapters/inbound/controller.py @@ -0,0 +1,14 @@ +# src/currency_quote/adapters/inbound/controller.py +from currency_quote.application.ports.inbound.get_currency_quote_use_case import GetCurrencyQuoteUseCase + + +class CurrencyController: + def __init__(self, get_currency_quote_use_case: GetCurrencyQuoteUseCase): + self.get_currency_quote_use_case = get_currency_quote_use_case + + def get_quote(self, currency_code: str): + try: + currency = self.get_currency_quote_use_case.execute(currency_code) + return {"code": currency.code, "name": currency.name, "rate": currency.rate} + except ValueError as e: + return {"error": str(e)} diff --git a/src/currency_quote/adapters/outbound/currency_api.py b/src/currency_quote/adapters/outbound/currency_api.py index e69de29..88a196a 100644 --- a/src/currency_quote/adapters/outbound/currency_api.py +++ b/src/currency_quote/adapters/outbound/currency_api.py @@ -0,0 +1,17 @@ +# src/currency_quote/adapters/outbound/currency_api.py +from api_to_dataframe import ClientBuilder, RetryStrategies +from currency_quote.application.ports.outbound.currency_repository import CurrencyRepository +from currency_quote.config.endpoints import API + + +class CurrencyAPI(CurrencyRepository): + def _get_last_quote(*args): + url = API.ENDPOINT_LAST_COTATION + ','.join(args) + client = ClientBuilder( + endpoint=url, + retry_strategy=RetryStrategies.LinearRetryStrategy + ) + + response = client.get_api_data() + + return response \ No newline at end of file diff --git a/src/currency_quote/adapters/outbound/currency_validator_api.py b/src/currency_quote/adapters/outbound/currency_validator_api.py new file mode 100644 index 0000000..398d359 --- /dev/null +++ b/src/currency_quote/adapters/outbound/currency_validator_api.py @@ -0,0 +1,22 @@ +# src/currency_quote/adapters/outbound/currency_validator_api.py +from api_to_dataframe import ClientBuilder, RetryStrategies +from currency_quote.config.endpoints import API + + +class CurrencyValidator: + @staticmethod + def validate_currency_code(currency_list: list) -> list: + client = ClientBuilder( + endpoint=API.ENDPOINT_AVALIABLE_PARITIES, + retry_strategy=RetryStrategies.LinearRetryStrategy + ) + + valid_list = client.get_api_data() + + validated_list = [] + + for currency_code in currency_list: + if currency_code in valid_list: + validated_list.append(currency_code) + + return validated_list diff --git a/src/currency_quote/application/ports/inbound/__init__.py b/src/currency_quote/application/ports/inbound/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/currency_quote/application/ports/inbound/get_currency_quote_use_case.py b/src/currency_quote/application/ports/inbound/get_currency_quote_use_case.py index e69de29..f8f78b2 100644 --- a/src/currency_quote/application/ports/inbound/get_currency_quote_use_case.py +++ b/src/currency_quote/application/ports/inbound/get_currency_quote_use_case.py @@ -0,0 +1,8 @@ +from abc import ABC, abstractmethod +from currency_quote.domain.entities.currency import Currency + + +class GetCurrencyQuoteUseCase(ABC): + @abstractmethod + def execute(self, currency_code: list) -> Currency: + pass diff --git a/src/currency_quote/application/ports/outbound/currency_repository.py b/src/currency_quote/application/ports/outbound/currency_repository.py index e69de29..1aa68c0 100644 --- a/src/currency_quote/application/ports/outbound/currency_repository.py +++ b/src/currency_quote/application/ports/outbound/currency_repository.py @@ -0,0 +1,8 @@ +# src/currency_quote/application/ports/outbound/currency_repository.py +from abc import ABC, abstractmethod + + +class CurrencyRepository(ABC): + @abstractmethod + def get_currency_quote(self, currency_code: list): + pass diff --git a/src/currency_quote/application/ports/outbound/currency_validator_repository.py b/src/currency_quote/application/ports/outbound/currency_validator_repository.py new file mode 100644 index 0000000..53a3a86 --- /dev/null +++ b/src/currency_quote/application/ports/outbound/currency_validator_repository.py @@ -0,0 +1,8 @@ +# src/currency_quote/application/ports/outbound/currency_validator_port.py +from abc import ABC, abstractmethod + + +class CurrencyValidatorPort(ABC): + @abstractmethod + def validate_currency_code(self, currency_list: list) -> list: + pass diff --git a/src/currency_quote/application/use_cases/test_validate_currency.py b/src/currency_quote/application/use_cases/test_validate_currency.py new file mode 100644 index 0000000..3d9d52e --- /dev/null +++ b/src/currency_quote/application/use_cases/test_validate_currency.py @@ -0,0 +1,29 @@ +import pytest +from currency_quote.domain.services.validate_currency import CurrencyValidatorService +from currency_quote.application.use_cases.validate_currency import ValidateCurrencyUseCase + + +def test_valid_currency(): + validator_service = CurrencyValidatorService() + currency_list = ["USD-BRL", "USD-BRLT"] + validate_currency = ValidateCurrencyUseCase(currency_validator_service=validator_service) + result = validate_currency.execute(currency_list=currency_list) + assert result == currency_list + + +def test_partial_valid_currency(): + validator_service = CurrencyValidatorService() + currency_list = ["USD-BRL", "USD-BRLT", "param1"] + expected_result = ["USD-BRL", "USD-BRLT"] + validate_currency = ValidateCurrencyUseCase(currency_validator_service=validator_service) + result = validate_currency.execute(currency_list=currency_list) + assert result == expected_result + + +def test_invalid_currency(): + validator_service = CurrencyValidatorService() + currency_list = ["param1", "param2"] + validate_currency = ValidateCurrencyUseCase(currency_validator_service=validator_service) + with pytest.raises(ValueError): + validate_currency.execute(currency_list=currency_list) + diff --git a/src/currency_quote/application/use_cases/validate_currency.py b/src/currency_quote/application/use_cases/validate_currency.py new file mode 100644 index 0000000..4a56c52 --- /dev/null +++ b/src/currency_quote/application/use_cases/validate_currency.py @@ -0,0 +1,10 @@ +# src/currency_quote/application/use_cases/validate_currency.py +from currency_quote.domain.services.validate_currency import CurrencyValidatorService + + +class ValidateCurrencyUseCase: + def __init__(self, currency_validator_service: CurrencyValidatorService): + self.currency_validator_service = currency_validator_service + + def execute(self, currency_list: list) -> list: + return self.currency_validator_service.validate_currency_code(currency_list) diff --git a/src/currency_quote/config/endpoints.py b/src/currency_quote/config/endpoints.py index d38a050..cbade23 100644 --- a/src/currency_quote/config/endpoints.py +++ b/src/currency_quote/config/endpoints.py @@ -4,4 +4,4 @@ class API: ENDPOINT_LAST_COTATION = __URL__ + "/last/" ENDPOINT_HISTORY_COTATION = __URL__ + "/json/daily/" RETRY_TIME_SECONDS = 2 - RETRY_ATTEMPTS = 3 + RETRY_ATTEMPTS = 3 \ No newline at end of file diff --git a/src/currency_quote/domain/entities/currency.py b/src/currency_quote/domain/entities/currency.py new file mode 100644 index 0000000..4b77004 --- /dev/null +++ b/src/currency_quote/domain/entities/currency.py @@ -0,0 +1,3 @@ +class Currency: + def __init__(self, currency: list): + self.currency_list = currency diff --git a/src/currency_quote/domain/models/currency.py b/src/currency_quote/domain/models/currency.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/currency_quote/domain/services/get_currency_quote.py b/src/currency_quote/domain/services/get_currency_quote.py index e69de29..9285914 100644 --- a/src/currency_quote/domain/services/get_currency_quote.py +++ b/src/currency_quote/domain/services/get_currency_quote.py @@ -0,0 +1,20 @@ +# src/currency_quote/domain/services/get_currency_quote.py +from currency_quote.application.ports.inbound.get_currency_quote_use_case import GetCurrencyQuoteUseCase +from currency_quote.application.ports.outbound.currency_repository import CurrencyRepository +from currency_quote.domain.entities.currency import Currency +from currency_quote.domain.services.validate_currency import ValidateCurrency + + +class GetCurrencyQuote(GetCurrencyQuoteUseCase): + def __init__(self, currency_repository: CurrencyRepository, validate_currency_service: ValidateCurrency): + self.currency_repository = currency_repository + self.validate_currency_service = validate_currency_service + + def execute(self, currency_code: str) -> Currency: + if not self.validate_currency_service.execute(currency_code): + raise ValueError(f"Código de moeda {currency_code} é inválido.") + + currency = self.currency_repository.get_currency_quote(currency_code) + if not currency: + raise ValueError(f"Cotação para a moeda {currency_code} não encontrada.") + return currency diff --git a/src/currency_quote/domain/services/validate_currency.py b/src/currency_quote/domain/services/validate_currency.py index e69de29..20138f5 100644 --- a/src/currency_quote/domain/services/validate_currency.py +++ b/src/currency_quote/domain/services/validate_currency.py @@ -0,0 +1,19 @@ +# src/currency_quote/application/services/currency_validator_service.py +from currency_quote.application.ports.outbound.currency_validator_repository import CurrencyValidatorPort +from currency_quote.adapters.outbound.currency_validator_api import CurrencyValidator + + +class CurrencyValidatorService(CurrencyValidatorPort): + def __init__(self): + self.currency_validator = CurrencyValidator() + + def validate_currency_code(self, currency_list: list) -> list: + validated_list = self.currency_validator.validate_currency_code(currency_list) + + if len(validated_list) == 0: + raise ValueError(f"All params: {currency_list} are invalid.") + + if len(validated_list) < len(currency_list): + print(f"Invalid currency params: {set(currency_list) - set(validated_list)}") + + return validated_list diff --git a/src/currency_quote/application/ports/inbound/common.py b/src/currency_quote/utils/common.py similarity index 100% rename from src/currency_quote/application/ports/inbound/common.py rename to src/currency_quote/utils/common.py diff --git a/src/currency_quote/adapters/outbound/logs.py b/src/currency_quote/utils/logs.py similarity index 100% rename from src/currency_quote/adapters/outbound/logs.py rename to src/currency_quote/utils/logs.py