#!/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). #