Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,4 @@ There are several pytests located in `./tests`. To run the full test-suite, simp

# Calendar Generation

Calendar generation can be manually built by appending the option`--buildcalendar`. This queries our current calendar provider (Calendarific) and generates a `.ics` file per each locale specified in settings.py. For testing, you can limit this to just US by using the option `--enus`.
This option requires setting the `CALENDARIFIC_API_KEY=` environment variable. If you're using a paid plan you can also set `CALENDARIFIC_IS_FREE_TIER=false` to remove the sleep time between calls.
Calendar generation can be manually built by appending the option`--buildcalendar`. Which utilises the python library [holidays](https://github.com/vacanza/holidays) and generates a `.ics` file per each locale specified in settings.py.
9 changes: 2 additions & 7 deletions build-site.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from datetime import date

from calgen.providers.CalendarificProvider import CalendarificProvider
from calgen.providers.VacanzaHolidaysProvider import VacanzaHolidaysProvider

parser = argparse.ArgumentParser()
parser.add_argument('--enus', help='Only build the en-US language.', action='store_true')
Expand Down Expand Up @@ -61,12 +61,7 @@
elif args.buildcalendars:
print("Building calendar files")

try:
api_key = os.environ['CALENDARIFIC_API_KEY']
except KeyError:
sys.exit("No `CALENDARIFIC_API_KEY` defined.")

build_calendar.build_calendars(CalendarificProvider({'api_key': api_key}), calendar_locales)
build_calendar.build_calendars(VacanzaHolidaysProvider({}), calendar_locales)
elif args.downloadlegal:
print("Downloading legal documents")
legal = builder.Legal(settings.WEBSITE_PATH)
Expand Down
50 changes: 10 additions & 40 deletions build_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,52 +40,24 @@ def build_ical(provider: Provider, locale: str, language: str, years_to_generate

unique_holidays = {}
for calendar_type in [CalendarTypes.NATIONAL, CalendarTypes.LOCAL]:
try:
holidays = provider.build(locale, year, {'calendar_type': calendar_type.value, 'language': language})

# Sometimes we can have dupes due to varying calendar types containing the same holiday
for holiday in holidays:
if holiday.unique_id not in unique_holidays:
unique_holidays[holiday.unique_id] = holiday
ical.add_component(holiday.to_ics())

except requests.HTTPError as err:
print(f"Err: Locale: {locale} / Calendar Type: {calendar_type}")
if err.response.status_code == 500:
print('Err: 500 Internal Server Error encountered, skipping.')
continue

try:
response = err.response.json()
except requests.exceptions.JSONDecodeError as json_err:
print(f'Err: Could not decode json, using response.text. {json_err}')
response = {'meta': {'error_detail': err.response.text}}

# Generic error message
error_response = "{}: {}. ".format(err.response.status_code, err.response.reason)

# If we have the error_detail key, append that.
if response['meta'].get('error_detail'):
error_response += response['meta'].get('error_detail')

# Known errors:
# Too many requests, upgrade required are API limit reached.
# Unauthorized is malformed or bad API key.
sys.exit(error_response)
holidays = provider.build(locale, year, {'calendar_type': calendar_type, 'language': language})

# Sometimes we can have dupes due to varying calendar types containing the same holiday
for holiday in holidays:
if holiday.unique_id not in unique_holidays:
unique_holidays[holiday.unique_id] = holiday
ical.add_component(holiday.to_ics())

return ical


def build_calendars(provider: Provider, locales: dict):
"""
Entry function for build_calendar.py script, will query the provider passed, and build the actual .ics file.
While this has been cleaned up to not specifically call out Calendarific, there are still data / assumptions made on function calls that will require a small touch up if you happen to use a different Provider.
"""
if len(locales.items()) == 0:
sys.exit("No locales specified, skipping calendar generation.")

is_free_tier = helper.is_calendarific_free_tier()

years_to_generate = settings.CALDATA_YEARS_TO_GENERATE
current_year = datetime.now().year
date_span = "{}-{}".format(current_year, current_year + years_to_generate)
Expand All @@ -105,13 +77,11 @@ def build_calendars(provider: Provider, locales: dict):
country_info_list = [country_info_list]

for country_name, language_code in country_info_list:

# Wait 1 second due to free api restrictions
if is_free_tier:
time.sleep(1)

ical = build_ical(provider, locale, language_code, years_to_generate)

if len(ical.events) == 0:
continue

calendar_name_parts = [country_name.replace(' ', ''), 'Holidays']
if country_name in settings.CALENDAR_REMAP:
new_name = settings.CALENDAR_REMAP[country_name]
Expand Down
4 changes: 2 additions & 2 deletions calgen/models/Calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def to_ics(self):
data = {
'uid': self.unique_id,
'last-modified': datetime.now(),
'dtstart': self.iso_date.date(),
'dtend': self.iso_date.date() + timedelta(days=1),
'dtstart': self.iso_date,
'dtend': self.iso_date + timedelta(days=1),
'summary': self.name,
'description': self.description,
'dtstamp': datetime.now(),
Expand Down
65 changes: 0 additions & 65 deletions calgen/models/Calendarific.py

This file was deleted.

92 changes: 92 additions & 0 deletions calgen/models/VacanzaHolidays.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from datetime import datetime
import hashlib

from calgen.models.Calendar import Calendar, CalendarTypes


class VacanzaHoliday(Calendar):
def __init__(
self,
country: str,
holiday_date: datetime,
holiday_name: str,
all_subdivs: list[str],
map_subdiv_to_name: dict[str, str],
):
super(VacanzaHoliday, self).__init__({}, holiday_date.year)

# holiday information
self._country = country
self._date = holiday_date
self._name = holiday_name

# maps a subdiv e.g. Alaska to AK
self._map_subdiv_to_name: dict[str, str] = map_subdiv_to_name
self._all_subdivs: list[str] = all_subdivs

self._subdivs: list[str] = []

def add_subdiv(self, subdiv: str):
if not subdiv:
return

self._subdivs.append(subdiv)

def get_calendar_type(self) -> CalendarTypes:
if len(self._all_subdivs) == len(self._subdivs) or len(self._subdivs) == 0:
return CalendarTypes.NATIONAL

return CalendarTypes.LOCAL

def regional_label(self):
"""Handle regional naming for holidays that fall under the CalendarTypes.LOCAL"""

# national holiday -> just the name
if self.get_calendar_type() == CalendarTypes.NATIONAL:
return self._name

# keep track of our subdivs, needed when we use the excluded (except text)
subdivs = self._subdivs
excluded_text: str = ""

# check if we can shorten the list, e.g. the length
# of subdivs is bigger than half of the TOTAL subdivs
if len(subdivs) >= len(self._all_subdivs) / 2:
excluded_subdivs: list[str] = []
for subdiv in self._all_subdivs:
if subdiv in subdivs:
continue

excluded_subdivs.append(subdiv)

# insert the excluded text here
# it should be done in a better way than this...
excluded_text = "All except "
subdivs = excluded_subdivs

# only one subdiv, use the real name, not alias
if len(subdivs) == 1:
subdiv = subdivs[0]
subdivs = [self._map_subdiv_to_name.get(subdiv, subdiv)]

# concat and return
concat_subdivs = ", ".join(subdivs)
return f"{self._name} ({excluded_text}{concat_subdivs})"

def from_api(self, data: dict):
# generate unique_id. There could be a better way, but this should suffice
subdivs_hash_concat = ", ".join([v for v in self._subdivs])
unique_str = (
self._country
+ str(self._date)
+ self._name
+ str(self.year)
+ subdivs_hash_concat
)
self.unique_id = hashlib.sha1(unique_str.encode("utf8")).hexdigest()

# rest of data
self.iso_date = self._date
self.name = self.regional_label()
self.description = self.name
self.calendar_type = self.get_calendar_type()
57 changes: 0 additions & 57 deletions calgen/providers/CalendarificProvider.py

This file was deleted.

Loading