Skip to content

Commit

Permalink
Merge pull request #19 from WoongyuChoi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
WoongyuChoi authored Dec 3, 2024
2 parents 8df13d1 + 23bea7b commit 657f121
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 41 deletions.
3 changes: 3 additions & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .external import *

__all__ = [name for name in globals() if not name.startswith("_")]
37 changes: 15 additions & 22 deletions api/external.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from dotenv import load_dotenv

from decorators import default_params
from handler.logger import get_logger
from util.date_utils import get_first_day_of_last_month, get_last_day_of_last_month
from util.fetch_utils import fetch_data
from models import APIParams
from utils import fetch_data, generate_statistic_url

logger = get_logger(__name__)

Expand All @@ -13,18 +14,17 @@


def check_ecos():
url = "https://ecos.bok.or.kr/api/"

try:
response = fetch_data(url, return_json=False)
response = fetch_data("https://ecos.bok.or.kr/api/", return_json=False)
logger.info(f"Response: {response.status_code} {dict(response.headers)} ")
return response.status_code == 200
except ValueError as e:
logger.warning(f"ECOS API health check failed: {e}")
return False


def fetch_exchange_rate(start_date=None, end_date=None, item_code="0000001"):
@default_params
def fetch_exchange_rate(start_date, end_date, item_code=None):
"""
외부 API를 호출하여 주요국 통화의 대원화환율 데이터를 조회합니다.
:param start_date: 검색 시작일자 (YYYYMMDD), 기본값은 지난달의 첫날
Expand All @@ -33,20 +33,13 @@ def fetch_exchange_rate(start_date=None, end_date=None, item_code="0000001"):
:return: API 응답 데이터 (JSON)
"""

if not start_date:
start_date = get_first_day_of_last_month()
if not end_date:
end_date = get_last_day_of_last_month()

base_url = "https://ecos.bok.or.kr/api"
service_name = "StatisticSearch"
api_key = os.getenv("ECOS_API_KEY")
response_format = "json"
language = "kr"
start_count = 1
end_count = 10000
table_code = "731Y001"
period = "D"

url = f"{base_url}/{service_name}/{api_key}/{response_format}/{language}/{start_count}/{end_count}/{table_code}/{period}/{start_date}/{end_date}/{item_code}"
params = APIParams(
service_name="StatisticSearch",
table_code="731Y001",
start_date=start_date,
end_date=end_date,
item_code=item_code or "0000001",
)

url = generate_statistic_url(params)
return fetch_data(url)
29 changes: 11 additions & 18 deletions api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@
from flask import Flask, jsonify, request
from flask_caching import Cache

from api.external import check_ecos, fetch_exchange_rate
from api import check_ecos, fetch_exchange_rate
from config import Config
from handler.exception_handler import register_exception_handlers
from handler.logger import get_logger

app = Flask(__name__)
app.config.from_object(Config)

cache = Cache(app)

logger = get_logger(__name__)

register_exception_handlers(app)


@app.route("/")
def health_check():
logger.info("Health check called.")
app.logger.info("Health check called.")
status = {
"status": "UP",
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
Expand All @@ -28,7 +30,7 @@ def health_check():

@app.route("/favicon.<ext>")
def favicon(ext):
logger.debug(f"Favicon request received with extension: {ext}")
app.logger.debug(f"Favicon request received with extension: {ext}")
return "", 204, {"Content-Type": "image/x-icon"}


Expand All @@ -37,22 +39,13 @@ def favicon(ext):
def get_exchange_rate():
start_date = request.args.get("start_date")
end_date = request.args.get("end_date")
item_code = request.args.get("item_code", "0000001")
item_code = request.args.get("item_code")

logger.debug(
f"Parameters received: start_date={start_date}, end_date={end_date}, item_code={item_code}"
data = fetch_exchange_rate(
start_date=start_date, end_date=end_date, item_code=item_code
)

try:
data = fetch_exchange_rate(start_date, end_date, item_code)
logger.info("Exchange rate data fetched successfully.")
return jsonify(data), 200
except ValueError as e:
logger.error(f"Error fetching exchange rate: {e}")
return jsonify({"error": str(e)}), 500
except Exception as e:
logger.error(f"Unexpected error: {e}")
return jsonify({"error": "An unexpected error occurred."}), 500
app.logger.info("Exchange rate data fetched successfully.")
return jsonify(data), 200


def handler(event, context):
Expand Down
3 changes: 3 additions & 0 deletions decorators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .param_defaults import default_params

__all__ = ["default_params"]
12 changes: 12 additions & 0 deletions decorators/param_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from functools import wraps
from utils import get_first_day_of_last_month, get_last_day_of_last_month


def default_params(func):
@wraps(func)
def wrapper(*args, **kwargs):
kwargs["start_date"] = kwargs.get("start_date") or get_first_day_of_last_month()
kwargs["end_date"] = kwargs.get("end_date") or get_last_day_of_last_month()
return func(*args, **kwargs)

return wrapper
22 changes: 22 additions & 0 deletions handler/exception_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from flask import jsonify
from werkzeug.exceptions import HTTPException


def register_exception_handlers(app):
@app.errorhandler(ValueError)
def handle_value_error(e):
app.logger.error(f"ValueError: {str(e)}")
return jsonify({"error": "Invalid input provided."}), 400

@app.errorhandler(HTTPException)
def handle_http_exception(e):
app.logger.error(f"HTTPException: {str(e)}")
return (
jsonify({"error": "A server error occurred. Please try again later."}),
e.code,
)

@app.errorhandler(Exception)
def handle_generic_exception(e):
app.logger.error(f"Unexpected error: {str(e)}")
return jsonify({"error": "An unexpected error occurred."}), 500
3 changes: 3 additions & 0 deletions models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .api_params import APIParams

__all__ = ["APIParams"]
19 changes: 19 additions & 0 deletions models/api_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os
from dataclasses import dataclass
from typing import Optional


@dataclass
class APIParams:
base_url: str = "https://ecos.bok.or.kr/api"
service_name: Optional[str] = None
api_key: str = os.getenv("ECOS_API_KEY")
response_format: str = "json"
language: str = "kr"
start_count: int = 1
end_count: int = 10000
table_code: Optional[str] = None
period: str = "D"
start_date: Optional[str] = None
end_date: Optional[str] = None
item_code: Optional[str] = None
9 changes: 9 additions & 0 deletions utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .date_utils import get_first_day_of_last_month, get_last_day_of_last_month
from .fetch_utils import fetch_data, generate_statistic_url

__all__ = [
"get_first_day_of_last_month",
"get_last_day_of_last_month",
"fetch_data",
"generate_statistic_url",
]
File renamed without changes.
19 changes: 18 additions & 1 deletion util/fetch_utils.py → utils/fetch_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import requests

from models import APIParams


def fetch_data(url, timeout=10, encoding="utf-8", return_json=True):
"""
Expand All @@ -8,7 +10,7 @@ def fetch_data(url, timeout=10, encoding="utf-8", return_json=True):
:param timeout: 요청 타임아웃 (기본값: 10초)
:param encoding: 응답 인코딩 (기본값: utf-8)
:param return_json: JSON 데이터를 반환할지 여부 (기본값: True)
:return: JSON 데이터 또는 HTTP 상태 코드
:return: JSON 데이터 또는 HTTP 데이터
"""
try:
response = requests.get(url, timeout=timeout)
Expand All @@ -20,3 +22,18 @@ def fetch_data(url, timeout=10, encoding="utf-8", return_json=True):
return response
except requests.RequestException as e:
raise ValueError(f"Failed to fetch data from API: {e}")


def generate_statistic_url(params: APIParams) -> str:
if not params.api_key:
raise ValueError("API key is missing.")

if not params.start_date or not params.end_date:
raise ValueError("Start date and end date are required.")

return (
f"{params.base_url}/{params.service_name}/{params.api_key}/"
f"{params.response_format}/{params.language}/{params.start_count}/"
f"{params.end_count}/{params.table_code}/{params.period}/"
f"{params.start_date}/{params.end_date}/{params.item_code}"
)

0 comments on commit 657f121

Please sign in to comment.