#!/usr/bin/env python
# coding: utf-8
# # Making a calendar from a Python release-schedule PEP
#
# Python 3.6 has its release schedule posted in [PEP 494](https://www.python.org/dev/peps/pep-0494/).
#
# I'm interested in having this information into my calendar.
# Rather than the sensible thing of manually adding a few events to my calendar,
# I'm going to build a calendar automatically in a notebook.
#
# **Why?** Because notebooks are fun,
# and I've been using [soupy](https://soupy.readthedocs.io)
# to practice some functional programming.
#
# I'm going to use [soupy](https://soupy.readthedocs.io) to extract the information,
# and [icalendar](https://icalendar.readthedocs.io) to build the calendar data.
# In[1]:
import requests
from soupy import Soupy, Q
# Fetch the page:
# In[2]:
url = 'https://www.python.org/dev/peps/pep-0494/'
soup = Soupy(requests.get(url).text, 'html.parser')
# The schedule is helpfully encapsulated in `div#schedule`:
# In[3]:
print(soup.find(id='schedule').val())
# We can see that each entry is a pretty simple `
` tag,
# with a release and date separated by a colon.
#
# We can use Soupy's functional style to quickly parse this information.
# We want to do:
#
# - find `#schedule`
# - for each `li` in `#schedule`:
# - get the text before and after the `':'`
# In[4]:
raw_data = soup \
.find(id='schedule') \
.find_all('li') \
.each(
Q.text.strip().split(':')
)
raw_data.val()
# Let's parse those dates before we get ahead of ourselves:
# In[5]:
import datetime
import re
date_pat = re.compile(r'\d{4}-\d{2}-\d{2}')
def parse_date(title_date):
title, date_string = title_date
date_part = date_pat.search(date_string).group()
date = datetime.datetime.strptime(date_part, '%Y-%m-%d').date()
return (title, date)
data = raw_data.each(Q.map(parse_date))
data.val()
# Now that we have the data, we can build the calendar with [icalendar](https://icalendar.readthedocs.io).
#
# We want each release to be an all-day event,
# and have a summary like "Python release 3.6.0 beta 1".
# For good behavior reasons, we will give each item a UID,
# so that importing the same event multiple times doesn't create duplicates.
# In[6]:
import hashlib
import icalendar
cal = icalendar.Calendar()
def add_event(cal, title, date):
evt = icalendar.Event()
# add Python to the summary
summary = 'Python ' + title
evt.add('summary', 'Python ' + title)
evt.add('dtstart', date)
evt.add('dtend', date)
# give it a UID for stability on repeated imports
key = b'python-release-%s' % title.encode('utf8')
evt.add('uid', hashlib.md5(key).hexdigest())
cal.add_component(evt)
data.each(Q.map(lambda title_date: add_event(cal, *title_date)))
print(cal.to_ical().decode())
# Now we can save this file to disk:
# In[7]:
with open('python36.ics', 'wb') as f:
f.write(cal.to_ical())
# and import the resulting `python36.ics` into your calendar application of choice.
#
# You can see the result of importing this file into Google Calendar:
#
# In[8]:
get_ipython().run_cell_magic('html', '', '\n')
#
# This notebook is by Min RK and placed in the Public Domain (or Creative Commons CC0, if you prefer).
#