Skip to content

Commit c2e85c6

Browse files
committed
Fix holiday calculation URL and add ArgentinaDatos provider
1 parent afe9ce7 commit c2e85c6

4 files changed

Lines changed: 173 additions & 3 deletions

File tree

app/config/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ class Config:
1212
HOLIDAY_PROVIDER = os.getenv("HOLIDAY_PROVIDER", "ARGENTINA_WEBSITE")
1313
HOLIDAYS_BASE_URL = os.getenv(
1414
"HOLIDAYS_BASE_URL",
15-
"https://www.argentina.gob.ar/interior/feriados-nacionales-{year}",
15+
"https://www.argentina.gob.ar/jefatura/feriados-nacionales-{year}",
16+
)
17+
HOLIDAY_API_URL = os.getenv(
18+
"HOLIDAY_API_URL",
19+
"https://api.argentinadatos.com/v1/feriados/{year}",
1620
)
1721

1822
# Configuración horaria
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from datetime import datetime
2+
from typing import List
3+
4+
import requests
5+
6+
from app.models.models import Holiday
7+
8+
9+
class ArgentinaApiProvider:
10+
"""
11+
Holiday provider that fetches holidays from the ArgentinaDatos API.
12+
"""
13+
14+
def __init__(self, api_url: str):
15+
self.url_template = api_url
16+
17+
def get_holidays(self, year: int) -> List[Holiday]:
18+
"""
19+
Fetches holidays for a given year from the API and returns them as Holiday objects.
20+
"""
21+
url = self.url_template.format(year=year)
22+
try:
23+
response = requests.get(url, timeout=10)
24+
response.raise_for_status()
25+
data = response.json()
26+
except requests.RequestException as e:
27+
print(f"Error fetching holiday data from API for year {year}: {e}")
28+
return []
29+
except ValueError as e:
30+
print(f"Error decoding JSON from API for year {year}: {e}")
31+
return []
32+
33+
holidays: List[Holiday] = []
34+
for entry in data:
35+
try:
36+
date_str = entry.get("fecha")
37+
description = entry.get("nombre")
38+
type_raw = entry.get("tipo")
39+
40+
if not date_str or not description:
41+
continue
42+
43+
parsed_date = datetime.strptime(date_str, "%Y-%m-%d").date()
44+
45+
# Filter by year just in case
46+
if parsed_date.year != year:
47+
continue
48+
49+
type_map = {
50+
"inamovible": "Inamovible",
51+
"trasladable": "Trasladable",
52+
"puente": "Fines Turísticos",
53+
"nolaborable": "No Laborable",
54+
}
55+
type_ = type_map.get(type_raw, "Otro")
56+
57+
holiday = Holiday(date=parsed_date, description=description, type=type_)
58+
holidays.append(holiday)
59+
60+
except (ValueError, AttributeError) as e:
61+
print(f"Error parsing holiday entry: {entry}. Error: {e}")
62+
continue
63+
64+
return holidays

app/services/holiday_service.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from flask import Config
22

3+
from app.services.holiday_providers.argentina_api_provider import ArgentinaApiProvider
34
from app.services.holiday_providers.argentina_website_provider import (
45
ArgentinaWebsiteProvider,
56
)
67
from app.services.holiday_providers.base import HolidayProvider
78

89
# A mapping of provider names to their corresponding classes.
910
# This makes it easy to add new providers in the future.
10-
PROVIDER_MAP = {"ARGENTINA_WEBSITE": ArgentinaWebsiteProvider}
11+
PROVIDER_MAP = {
12+
"ARGENTINA_WEBSITE": ArgentinaWebsiteProvider,
13+
"ARGENTINA_API": ArgentinaApiProvider,
14+
}
1115

1216

1317
def get_holiday_provider(config: Config) -> HolidayProvider:
@@ -27,7 +31,13 @@ def get_holiday_provider(config: Config) -> HolidayProvider:
2731
base_url = getattr(config, "HOLIDAYS_BASE_URL", None)
2832
if not base_url:
2933
raise ValueError("HOLIDAYS_BASE_URL is not configured.")
30-
return provider_class(base_url=base_url)
34+
return ArgentinaWebsiteProvider(base_url=base_url)
35+
36+
if provider_name.upper() == "ARGENTINA_API":
37+
api_url = getattr(config, "HOLIDAY_API_URL", None)
38+
if not api_url:
39+
raise ValueError("HOLIDAY_API_URL is not configured.")
40+
return ArgentinaApiProvider(api_url=api_url)
3141

3242
# This part would be extended for other providers
3343
# For now, we raise an error if the provider is in the map but has no
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
import pytest
4+
import requests
5+
6+
from app.models.models import Holiday
7+
from app.services.holiday_providers.argentina_api_provider import ArgentinaApiProvider
8+
9+
10+
class TestArgentinaApiProvider:
11+
"""
12+
Tests for the ArgentinaApiProvider.
13+
"""
14+
15+
@pytest.fixture
16+
def mock_response(self):
17+
"""Creates a mock response object."""
18+
mock = MagicMock(spec=requests.Response)
19+
mock.raise_for_status.return_value = None
20+
return mock
21+
22+
def test_get_holidays_success(self, mock_response):
23+
"""
24+
Test successful holiday parsing from API JSON response.
25+
"""
26+
year = 2025
27+
mock_response.json.return_value = [
28+
{"fecha": "2025-01-01", "nombre": "Año Nuevo", "tipo": "inamovible"},
29+
{
30+
"fecha": "2025-05-01",
31+
"nombre": "Día del Trabajador",
32+
"tipo": "inamovible",
33+
},
34+
]
35+
provider = ArgentinaApiProvider(api_url="http://fake-api.com/{year}")
36+
with patch("requests.get", return_value=mock_response):
37+
holidays = provider.get_holidays(year)
38+
assert len(holidays) == 2
39+
assert holidays[0].description == "Año Nuevo"
40+
assert holidays[0].date.year == 2025
41+
42+
def test_get_holidays_filter_by_year(self, mock_response):
43+
"""
44+
Test that holidays from other years are filtered out.
45+
"""
46+
year = 2025
47+
mock_response.json.return_value = [
48+
{"fecha": "2025-01-01", "nombre": "Correct Year", "tipo": "inamovible"},
49+
{"fecha": "2024-12-31", "nombre": "Wrong Year", "tipo": "inamovible"},
50+
]
51+
provider = ArgentinaApiProvider(api_url="http://fake-api.com/{year}")
52+
with patch("requests.get", return_value=mock_response):
53+
holidays = provider.get_holidays(year)
54+
assert len(holidays) == 1
55+
assert holidays[0].description == "Correct Year"
56+
57+
def test_get_holidays_malformed_entry(self, mock_response):
58+
"""
59+
Test that malformed entries are skipped.
60+
"""
61+
year = 2025
62+
mock_response.json.return_value = [
63+
{"fecha": "2025-01-01", "nombre": "Good", "tipo": "inamovible"},
64+
{"fecha": "", "nombre": "Bad Date", "tipo": "inamovible"},
65+
{"fecha": "2025-01-02", "nombre": "", "tipo": "inamovible"},
66+
]
67+
provider = ArgentinaApiProvider(api_url="http://fake-api.com/{year}")
68+
with patch("requests.get", return_value=mock_response):
69+
holidays = provider.get_holidays(year)
70+
assert len(holidays) == 1
71+
assert holidays[0].description == "Good"
72+
73+
def test_get_holidays_network_error(self):
74+
"""
75+
Test that network errors are handled gracefully.
76+
"""
77+
provider = ArgentinaApiProvider(api_url="http://fake-api.com/{year}")
78+
with patch(
79+
"requests.get", side_effect=requests.RequestException("Network Error")
80+
):
81+
holidays = provider.get_holidays(2025)
82+
assert holidays == []
83+
84+
def test_get_holidays_json_error(self, mock_response):
85+
"""
86+
Test that JSON decoding errors are handled gracefully.
87+
"""
88+
mock_response.json = MagicMock(side_effect=ValueError("Invalid JSON"))
89+
provider = ArgentinaApiProvider(api_url="http://fake-api.com/{year}")
90+
with patch("requests.get", return_value=mock_response):
91+
holidays = provider.get_holidays(2025)
92+
assert holidays == []

0 commit comments

Comments
 (0)