diff --git a/holidays/calendars/thai.py b/holidays/calendars/thai.py index 75147aa01..25e8ea14a 100644 --- a/holidays/calendars/thai.py +++ b/holidays/calendars/thai.py @@ -14,6 +14,8 @@ from functools import lru_cache from typing import Optional +from holidays.exceptions import CalendarError + KHMER_CALENDAR = "KHMER_CALENDAR" THAI_CALENDAR = "THAI_CALENDAR" @@ -201,8 +203,8 @@ def __verify_calendar(calendar): Verify calendar type. """ if calendar not in {KHMER_CALENDAR, THAI_CALENDAR}: - raise ValueError( - f"Unknown calendar name: {calendar}. Use `KHMER_CALENDAR` or `THAI_CALENDAR`." + raise CalendarError( + f"Unknown calendar name: {calendar}. Use KHMER_CALENDAR or THAI_CALENDAR." ) @lru_cache() diff --git a/holidays/exceptions.py b/holidays/exceptions.py new file mode 100644 index 000000000..390afd2a0 --- /dev/null +++ b/holidays/exceptions.py @@ -0,0 +1,38 @@ +# python-holidays +# --------------- +# A fast, efficient Python library for generating country, province and state +# specific sets of holidays on the fly. It aims to make determining whether a +# specific date is a holiday as fast and flexible as possible. +# +# Authors: dr-prodigy (c) 2017-2023 +# ryanss (c) 2014-2017 +# Website: https://github.com/dr-prodigy/python-holidays +# License: MIT (see LICENSE file) + + +class BaseError(Exception): + """Base exception.""" + + +class CalendarError(BaseError): + """Calendar not recognized exception.""" + + +class InvalidDateError(BaseError): + """Invalid date type or format exception.""" + + +class EntityDoesNotExist(BaseError): + """Entity not supported exception.""" + + +class CountryDoesNotExist(EntityDoesNotExist): + """Country not supported exception.""" + + +class FinancialDoesNotExist(EntityDoesNotExist): + """Financial not supported exception.""" + + +class SubdivisionDoesNotExist(BaseError): + """Subdivision not supported exception.""" diff --git a/holidays/groups/christian.py b/holidays/groups/christian.py index 7547d64db..cfb6fc8af 100644 --- a/holidays/groups/christian.py +++ b/holidays/groups/christian.py @@ -28,6 +28,7 @@ DEC, ) from holidays.calendars.julian import JULIAN_CALENDAR +from holidays.exceptions import CalendarError class ChristianHolidays: @@ -78,9 +79,8 @@ def __verify_calendar(calendar): Verify calendar type. """ if calendar not in {GREGORIAN_CALENDAR, JULIAN_CALENDAR}: - raise ValueError( - f"Unknown calendar name: {calendar}. " - "Use `GREGORIAN_CALENDAR` or `JULIAN_CALENDAR`." + raise CalendarError( + f"Unknown calendar name: {calendar}. Use GREGORIAN_CALENDAR or JULIAN_CALENDAR." ) @property diff --git a/holidays/groups/international.py b/holidays/groups/international.py index f6798b188..da35d3bc1 100644 --- a/holidays/groups/international.py +++ b/holidays/groups/international.py @@ -48,8 +48,8 @@ def _add_childrens_day(self, name, variation="JUN"): return self._add_holiday(name, NOV, 20) else: raise ValueError( - f"Unknown variaton name: {variation}. " - "This entry currently supports `JUN` and `NOV` variation only." + f"Unknown variation name: {variation}. " + "This entry currently supports JUN and NOV variations only." ) def _add_columbus_day(self, name): diff --git a/holidays/holiday_base.py b/holidays/holiday_base.py index 6fc6bd6d5..b0b25d8db 100644 --- a/holidays/holiday_base.py +++ b/holidays/holiday_base.py @@ -24,6 +24,7 @@ from holidays.calendars.gregorian import MON, TUE, WED, THU, FRI, SAT, SUN from holidays.constants import HOLIDAY_NAME_DELIMITER, ALL_CATEGORIES, PUBLIC +from holidays.exceptions import InvalidDateError, SubdivisionDoesNotExist from holidays.helpers import _normalize_tuple DateArg = Union[date, Tuple[int, int]] @@ -283,11 +284,11 @@ def __init__( if not isinstance(self, HolidaySum): if subdiv and subdiv not in set(self.subdivisions + self._deprecated_subdivisions): - if hasattr(self, "market"): - error = f"Market '{self.market}' does not have subdivision " f"'{subdiv}'" - else: - error = f"Country '{self.country}' does not have subdivision " f"'{subdiv}'" - raise NotImplementedError(error) + raise SubdivisionDoesNotExist( + f"Market {self.market} does not have subdivision {subdiv}" + if hasattr(self, "market") + else f"Country {self.country} does not have subdivision {subdiv}" + ) if subdiv and subdiv in self._deprecated_subdivisions: warnings.warn( @@ -365,7 +366,7 @@ def __contains__(self, key: object) -> bool: """ if not isinstance(key, (date, datetime, float, int, str)): - raise TypeError(f"Cannot convert type '{type(key)}' to date.") + raise InvalidDateError(f"Cannot convert type '{type(key)}' to date.") return dict.__contains__(cast("Mapping[Any, Any]", self), self.__keytransform__(key)) @@ -432,7 +433,7 @@ def __keytransform__(self, key: DateLike) -> date: try: dt = parse(key).date() except (OverflowError, ValueError): - raise ValueError(f"Cannot parse date from string '{key}'") + raise InvalidDateError(f"Cannot parse date from string '{key}'") # Check all other types. elif isinstance(key, datetime): # Key type is derived from `datetime`. @@ -447,7 +448,7 @@ def __keytransform__(self, key: DateLike) -> date: dt = datetime.fromtimestamp(key, timezone.utc).date() else: # Key type is not supported. - raise TypeError(f"Cannot convert type '{type(key)}' to date.") + raise InvalidDateError(f"Cannot convert type '{type(key)}' to date.") # Automatically expand for `expand=True` cases. if self.expand and dt.year not in self.years: @@ -799,7 +800,7 @@ def get_named( if any((holiday_name_lower == name[: len(holiday_name)].lower() for name in names)) ] - raise AttributeError(f"Unknown lookup type: {lookup}") + raise ValueError(f"Unknown lookup type: {lookup}") def pop(self, key: DateLike, default: Union[str, Any] = None) -> Union[str, Any]: """If date is a holiday, remove it and return its date, else return diff --git a/holidays/utils.py b/holidays/utils.py index f6a3833e5..fb47ab5bc 100755 --- a/holidays/utils.py +++ b/holidays/utils.py @@ -23,6 +23,7 @@ from functools import lru_cache from typing import Dict, Iterable, List, Optional, Tuple, Union +from holidays.exceptions import CountryDoesNotExist, FinancialDoesNotExist from holidays.holiday_base import HolidayBase from holidays.registry import EntityLoader @@ -184,18 +185,20 @@ def country_holidays( import holidays try: - return getattr(holidays, country)( - years=years, - subdiv=subdiv, - expand=expand, - observed=observed, - prov=prov, - state=state, - language=language, - categories=categories, - ) + cls = getattr(holidays, country) except AttributeError: - raise NotImplementedError(f"Country {country} not available") + raise CountryDoesNotExist(f"Country {country} is not available") + + return cls( + years=years, + subdiv=subdiv, + expand=expand, + observed=observed, + prov=prov, + state=state, + language=language, + categories=categories, + ) def financial_holidays( @@ -249,15 +252,17 @@ def financial_holidays( import holidays try: - return getattr(holidays, market)( - years=years, - subdiv=subdiv, - expand=expand, - observed=observed, - language=language, - ) + cls = getattr(holidays, market) except AttributeError: - raise NotImplementedError(f"Financial market {market} not available") + raise FinancialDoesNotExist(f"Financial entity {market} is not available") + + return cls( + years=years, + subdiv=subdiv, + expand=expand, + observed=observed, + language=language, + ) def CountryHoliday( diff --git a/tests/countries/test_japan.py b/tests/countries/test_japan.py index a473dd876..7a299edd0 100644 --- a/tests/countries/test_japan.py +++ b/tests/countries/test_japan.py @@ -21,11 +21,10 @@ def setUpClass(cls): def test_country_aliases(self): self.assertCountryAliases(Japan, JP, JPN) - def test_not_implemented(self): - with self.assertRaises(NotImplementedError): - Japan(years=1945) - with self.assertRaises(NotImplementedError): - Japan(years=2100) + def test_year_out_of_range(self): + for year in (1948, 2100): + with self.assertRaises(NotImplementedError): + Japan(years=year) def test_new_years_day(self): self.assertHoliday(f"{year}-01-01" for year in range(1949, 2051)) diff --git a/tests/test_calendars.py b/tests/test_calendars.py index 69f1d244d..db155b27f 100644 --- a/tests/test_calendars.py +++ b/tests/test_calendars.py @@ -15,6 +15,7 @@ from holidays import calendars from holidays.calendars.gregorian import FEB, MAR, MAY, JUN, JUL, AUG, SEP, OCT, NOV from holidays.calendars.thai import KHMER_CALENDAR +from holidays.exceptions import CalendarError class TestThaiLunisolarCalendar(unittest.TestCase): @@ -23,7 +24,7 @@ def setUp(self) -> None: self.calendar = calendars._ThaiLunisolar() def test_check_calendar(self): - self.assertRaises(ValueError, lambda: calendars._ThaiLunisolar("INVALID_CALENDAR")) + self.assertRaises(CalendarError, lambda: calendars._ThaiLunisolar("INVALID_CALENDAR")) def test_asarnha_bucha_date(self): # THAI_CALENDAR diff --git a/tests/test_holiday_base.py b/tests/test_holiday_base.py index 23a722433..3c08a7bf7 100644 --- a/tests/test_holiday_base.py +++ b/tests/test_holiday_base.py @@ -22,6 +22,7 @@ import holidays from holidays.calendars.gregorian import JAN, FEB, OCT, MON, TUE, SAT, SUN from holidays.constants import HOLIDAY_NAME_DELIMITER +from holidays.exceptions import InvalidDateError class TestBasics(unittest.TestCase): @@ -32,7 +33,7 @@ def test_contains(self): self.assertIn(date(2014, 1, 1), self.holidays) self.assertNotIn(date(2014, 1, 2), self.holidays) - def test_getitem(self): + def test_get_item(self): self.assertEqual(self.holidays[date(2014, 1, 1)], "New Year's Day") self.assertEqual(self.holidays.get(date(2014, 1, 1)), "New Year's Day") self.assertRaises(KeyError, lambda: self.holidays[date(2014, 1, 2)]) @@ -594,7 +595,7 @@ def test_get_named_startswith(self): def test_get_named_lookup_invalid(self): us = holidays.UnitedStates(years=2020) - self.assertRaises(AttributeError, lambda: us.get_named("Holiday name", lookup="invalid")) + self.assertRaises(ValueError, lambda: us.get_named("Holiday name", lookup="invalid")) class TestArgs(unittest.TestCase): @@ -757,19 +758,11 @@ def test_string(self): self.assertNotIn("01/03/2014", self.holidays) def test_exception(self): - self.assertRaises((TypeError, ValueError), lambda: "abc" in self.holidays) - self.assertRaises( - (TypeError, ValueError), - lambda: self.holidays.get("abc123"), - ) - self.assertRaises(TypeError, lambda: self.holidays.get({"123"})) - self.assertRaises( - (TypeError, ValueError), - self.holidays.__setitem__, - "abc", - "Test", - ) - self.assertRaises((TypeError, ValueError), lambda: {} in self.holidays) + self.assertRaises(InvalidDateError, lambda: "abc" in self.holidays) + self.assertRaises(InvalidDateError, lambda: self.holidays.get("abc123")) + self.assertRaises(InvalidDateError, lambda: self.holidays.get({"123"})) + self.assertRaises(InvalidDateError, self.holidays.__setitem__, "abc", "Test") + self.assertRaises(InvalidDateError, lambda: {} in self.holidays) class TestCountryHolidayDeprecation(unittest.TestCase): diff --git a/tests/test_holiday_groups.py b/tests/test_holiday_groups.py index 8c6d19749..966e3cbd8 100644 --- a/tests/test_holiday_groups.py +++ b/tests/test_holiday_groups.py @@ -11,16 +11,14 @@ from unittest import TestCase +from holidays.exceptions import CalendarError from holidays.holiday_base import HolidayBase from holidays.holiday_groups import ChristianHolidays, InternationalHolidays class TestChristianHolidays(TestCase): def test_check_calendar(self): - self.assertRaises( - ValueError, - lambda: ChristianHolidays("INVALID_CALENDAR"), - ) + self.assertRaises(CalendarError, lambda: ChristianHolidays("INVALID_CALENDAR")) def test_add_christmas_day_three(self): class TestHolidays(HolidayBase, ChristianHolidays): diff --git a/tests/test_utils.py b/tests/test_utils.py index 109649163..e77cfe39f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -19,6 +19,7 @@ import pytest import holidays +from holidays.exceptions import CountryDoesNotExist, SubdivisionDoesNotExist, FinancialDoesNotExist from holidays.utils import ( country_holidays, financial_holidays, @@ -54,9 +55,8 @@ def test_country_province(self): self.assertEqual(h.subdiv, "NT") def test_exceptions(self): - self.assertRaises(NotImplementedError, lambda: country_holidays("XXXX")) - self.assertRaises(NotImplementedError, lambda: country_holidays("US", subdiv="XXXX")) - self.assertRaises(NotImplementedError, lambda: country_holidays("US", subdiv="XXXX")) + self.assertRaises(CountryDoesNotExist, lambda: country_holidays("XXXX")) + self.assertRaises(SubdivisionDoesNotExist, lambda: country_holidays("US", subdiv="XXXX")) class TestFinancialHolidays(unittest.TestCase): @@ -75,8 +75,8 @@ def test_market_years(self): self.assertEqual(h.years, {2015, 2016}) def test_exceptions(self): - self.assertRaises(NotImplementedError, lambda: financial_holidays("XXXX")) - self.assertRaises(NotImplementedError, lambda: financial_holidays("NYSE", subdiv="XXXX")) + self.assertRaises(FinancialDoesNotExist, lambda: financial_holidays("XX")) + self.assertRaises(SubdivisionDoesNotExist, lambda: financial_holidays("NYSE", subdiv="XX")) class TestAllInSameYear(unittest.TestCase):