update from Atlas
This commit is contained in:
@@ -1,30 +1,30 @@
|
||||
"""
|
||||
explore1.py: Script to explore the OSCON schedule feed
|
||||
|
||||
# BEGIN EXPLORE1_DEMO
|
||||
>>> from osconfeed import load
|
||||
>>> raw_feed = load()
|
||||
>>> feed = FrozenJSON(raw_feed)
|
||||
>>> sorted(feed.Schedule.keys())
|
||||
>>> feed = FrozenJSON(raw_feed) # <1>
|
||||
>>> len(feed.Schedule.speakers) # <2>
|
||||
357
|
||||
>>> sorted(feed.Schedule.keys()) # <3>
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed.Schedule.items()):
|
||||
... print('{:3} {}'.format(len(value), key))
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed.Schedule.speakers[-1].name
|
||||
>>> feed.Schedule.speakers[-1].name # <4>
|
||||
'Carina C. Zona'
|
||||
>>> carina = feed.Schedule.speakers[-1]
|
||||
>>> carina.twitter
|
||||
'cczona'
|
||||
>>> feed.Schedule.events[40].name
|
||||
>>> talk = feed.Schedule.events[40] # <5>
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> feed.Schedule.events[40].speakers
|
||||
>>> talk.speakers # <6>
|
||||
[3471, 5199]
|
||||
>>> talk.flavor # <7>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
# END EXPLORE1_DEMO
|
||||
"""
|
||||
|
||||
# BEGIN EXPLORE1
|
||||
from collections import abc
|
||||
|
||||
|
||||
@@ -34,19 +34,20 @@ class FrozenJSON:
|
||||
"""
|
||||
|
||||
def __init__(self, mapping):
|
||||
self._data = dict(mapping)
|
||||
self._data = dict(mapping) # <1>
|
||||
|
||||
def __getattr__(self, name):
|
||||
def __getattr__(self, name): # <2>
|
||||
if hasattr(self._data, name):
|
||||
return getattr(self._data, name)
|
||||
return getattr(self._data, name) # <3>
|
||||
else:
|
||||
return FrozenJSON.build(self._data[name])
|
||||
return FrozenJSON.build(self._data[name]) # <4>
|
||||
|
||||
@classmethod
|
||||
def build(cls, obj):
|
||||
if isinstance(obj, abc.Mapping):
|
||||
def build(cls, obj): # <5>
|
||||
if isinstance(obj, abc.Mapping): # <6>
|
||||
return cls(obj)
|
||||
elif isinstance(obj, abc.MutableSequence):
|
||||
elif isinstance(obj, abc.MutableSequence): # <7>
|
||||
return [cls.build(item) for item in obj]
|
||||
else:
|
||||
else: # <8>
|
||||
return obj
|
||||
# END EXPLORE1
|
||||
|
||||
@@ -4,27 +4,25 @@ explore2.py: Script to explore the OSCON schedule feed
|
||||
>>> from osconfeed import load
|
||||
>>> raw_feed = load()
|
||||
>>> feed = FrozenJSON(raw_feed)
|
||||
>>> len(feed.Schedule.speakers)
|
||||
357
|
||||
>>> sorted(feed.Schedule.keys())
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed.Schedule.items()):
|
||||
... print('{:3} {}'.format(len(value), key))
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed.Schedule.speakers[-1].name
|
||||
'Carina C. Zona'
|
||||
>>> carina = feed.Schedule.speakers[-1]
|
||||
>>> carina.twitter
|
||||
'cczona'
|
||||
>>> feed.Schedule.events[40].name
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> feed.Schedule.events[40].speakers
|
||||
>>> talk.speakers
|
||||
[3471, 5199]
|
||||
>>> talk.flavor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN EXPLORE2
|
||||
from collections import abc
|
||||
|
||||
|
||||
@@ -33,11 +31,11 @@ class FrozenJSON:
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
def __new__(cls, arg):
|
||||
def __new__(cls, arg): # <1>
|
||||
if isinstance(arg, abc.Mapping):
|
||||
return super().__new__(cls)
|
||||
elif isinstance(arg, abc.MutableSequence):
|
||||
return [FrozenJSON(item) for item in arg]
|
||||
return super().__new__(cls) # <2>
|
||||
elif isinstance(arg, abc.MutableSequence): # <3>
|
||||
return [cls(item) for item in arg]
|
||||
else:
|
||||
return arg
|
||||
|
||||
@@ -48,4 +46,5 @@ class FrozenJSON:
|
||||
if hasattr(self._data, name):
|
||||
return getattr(self._data, name)
|
||||
else:
|
||||
return FrozenJSON(self._data[name])
|
||||
return FrozenJSON(self._data[name]) # <4>
|
||||
# END EXPLORE2
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
{ "conferences": [{"serial": 115 }],
|
||||
"events": [
|
||||
{ "serial": 34505,
|
||||
"name": "Why Schools Don't Use Open Source to Teach Programming",
|
||||
"name": "Why Schools Don´t Use Open Source to Teach Programming",
|
||||
"event_type": "40-minute conference session",
|
||||
"time_start": "2014-07-23 11:30:00",
|
||||
"time_stop": "2014-07-23 12:10:00",
|
||||
"venue_serial": 1462,
|
||||
"description": "Aside from the fact that high school programming curricula often require proprietary IDEs, they also don't involve examining any source code from Open Source software projects. What changes would be required in programming curricula to incorporate Open Source? And is that a desirable objective?",
|
||||
"description": "Aside from the fact that high school programming...",
|
||||
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
|
||||
"speakers": [157509],
|
||||
"categories": ["Education"] }
|
||||
@@ -20,7 +20,7 @@
|
||||
"position": "CTO",
|
||||
"affiliation": "Sharewave",
|
||||
"twitter": "sharewaveteam",
|
||||
"bio": "Robert 'r0ml' Lefkowitz is the CTO at Sharewave, a startup building an investor management portal. This year, he was a resident at the winter session of Hacker School. He is a Distinguished Engineer of the ACM." }
|
||||
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." }
|
||||
],
|
||||
"venues": [
|
||||
{ "serial": 1462,
|
||||
|
||||
@@ -1,43 +1,49 @@
|
||||
"""
|
||||
osconfeed.py: Script to download the OSCON schedule feed
|
||||
|
||||
>>> feed = load()
|
||||
>>> sorted(feed['Schedule'].keys())
|
||||
# BEGIN OSCONFEED_DEMO
|
||||
|
||||
>>> feed = load() # <1>
|
||||
>>> sorted(feed['Schedule'].keys()) # <2>
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed['Schedule'].items()):
|
||||
... print('{:3} {}'.format(len(value), key))
|
||||
... print('{:3} {}'.format(len(value), key)) # <3>
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed['Schedule']['speakers'][-1]['name']
|
||||
>>> feed['Schedule']['speakers'][-1]['name'] # <4>
|
||||
'Carina C. Zona'
|
||||
>>> carina = feed['Schedule']['speakers'][-1]
|
||||
>>> carina['twitter']
|
||||
'cczona'
|
||||
>>> feed['Schedule']['speakers'][-1]['serial'] # <5>
|
||||
141590
|
||||
>>> feed['Schedule']['events'][40]['name']
|
||||
'There *Will* Be Bugs'
|
||||
>>> feed['Schedule']['events'][40]['speakers']
|
||||
>>> feed['Schedule']['events'][40]['speakers'] # <6>
|
||||
[3471, 5199]
|
||||
|
||||
|
||||
# END OSCONFEED_DEMO
|
||||
"""
|
||||
|
||||
# BEGIN OSCONFEED
|
||||
from urllib.request import urlopen
|
||||
import warnings
|
||||
import os
|
||||
import json
|
||||
|
||||
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
|
||||
JSON_NAME = 'data/osconfeed.json'
|
||||
JSON = 'data/osconfeed.json'
|
||||
|
||||
|
||||
def load():
|
||||
if not os.path.exists(JSON_NAME):
|
||||
msg = 'downloading {} to {}'.format(URL, JSON_NAME)
|
||||
warnings.warn(msg)
|
||||
with urlopen(URL) as remote, open(JSON_NAME, 'wb') as local:
|
||||
if not os.path.exists(JSON):
|
||||
msg = 'downloading {} to {}'.format(URL, JSON)
|
||||
warnings.warn(msg) # <1>
|
||||
with urlopen(URL) as remote, open(JSON, 'wb') as local: # <2>
|
||||
local.write(remote.read())
|
||||
|
||||
with open(JSON_NAME) as fp:
|
||||
return json.load(fp)
|
||||
with open(JSON) as fp:
|
||||
return json.load(fp) # <3>
|
||||
|
||||
# END OSCONFEED
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
"""
|
||||
schedule1.py: traversing OSCON schedule data
|
||||
|
||||
# BEGIN SCHEDULE1_DEMO
|
||||
>>> import shelve
|
||||
>>> db = shelve.open(DB_NAME)
|
||||
>>> if CONFERENCE not in db: load_db(db)
|
||||
>>> event = db['event.33950']
|
||||
>>> speaker = db['speaker.3471']
|
||||
>>> speaker.name
|
||||
>>> db = shelve.open(DB_NAME) # <1>
|
||||
>>> if CONFERENCE not in db: # <2>
|
||||
... load_db(db) # <3>
|
||||
...
|
||||
>>> speaker = db['speaker.3471'] # <4>
|
||||
>>> type(speaker) # <5>
|
||||
<class 'schedule1.Record'>
|
||||
>>> speaker.name # <6>
|
||||
'Anna Martelli Ravenscroft'
|
||||
>>> speaker.twitter
|
||||
'annaraven'
|
||||
>>> db.close()
|
||||
>>> db.close() # <7>
|
||||
|
||||
# END SCHEDULE1_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN SCHEDULE1
|
||||
import warnings
|
||||
|
||||
import osconfeed
|
||||
@@ -23,15 +30,18 @@ CONFERENCE = 'conference.115'
|
||||
|
||||
|
||||
class Record:
|
||||
def __init__(self, mapping):
|
||||
self.__dict__.update(mapping)
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs) # <1>
|
||||
|
||||
|
||||
def load_db(db):
|
||||
raw_data = osconfeed.load()
|
||||
raw_data = osconfeed.load() # <2>
|
||||
warnings.warn('loading ' + DB_NAME)
|
||||
for collection, rec_list in raw_data['Schedule'].items():
|
||||
rec_type = collection[:-1]
|
||||
for fields in rec_list:
|
||||
key = '{}.{}'.format(rec_type, fields['serial'])
|
||||
db[key] = Record(fields)
|
||||
for collection, rec_list in raw_data['Schedule'].items(): # <3>
|
||||
record_type = collection[:-1] # <4>
|
||||
for record in rec_list:
|
||||
key = '{}.{}'.format(record_type, record['serial']) # <5>
|
||||
record['serial'] = key # <6>
|
||||
db[key] = Record(**record) # <7>
|
||||
|
||||
# END SCHEDULE1
|
||||
|
||||
@@ -4,82 +4,128 @@ schedule2.py: traversing OSCON schedule data
|
||||
>>> import shelve
|
||||
>>> db = shelve.open(DB_NAME)
|
||||
>>> if CONFERENCE not in db: load_db(db)
|
||||
>>> DbRecord.set_db(db)
|
||||
>>> event = Event.get('event.33950')
|
||||
>>> event
|
||||
|
||||
# BEGIN SCHEDULE2_DEMO
|
||||
|
||||
>>> DbRecord.set_db(db) # <1>
|
||||
>>> event = DbRecord.fetch('event.33950') # <2>
|
||||
>>> event # <3>
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.speakers[0].name
|
||||
'Anna Martelli Ravenscroft'
|
||||
>>> event.venue # <4>
|
||||
<DbRecord serial='venue.1449'>
|
||||
>>> event.venue.name # <5>
|
||||
'Portland 251'
|
||||
>>> for spkr in event.speakers: # <6>
|
||||
... print('{0.serial}: {0.name}'.format(spkr))
|
||||
...
|
||||
speaker.3471: Anna Martelli Ravenscroft
|
||||
speaker.5199: Alex Martelli
|
||||
|
||||
# END SCHEDULE2_DEMO
|
||||
|
||||
>>> db.close()
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN SCHEDULE2_RECORD
|
||||
import warnings
|
||||
import inspect
|
||||
import inspect # <1>
|
||||
|
||||
import osconfeed
|
||||
|
||||
DB_NAME = 'data/schedule2_db'
|
||||
DB_NAME = 'data/schedule2_db' # <2>
|
||||
CONFERENCE = 'conference.115'
|
||||
|
||||
|
||||
class Record:
|
||||
def __init__(self, mapping):
|
||||
self.__dict__.update(mapping)
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other): # <3>
|
||||
if isinstance(other, Record):
|
||||
return self.__dict__ == other.__dict__
|
||||
else:
|
||||
return NotImplemented
|
||||
# END SCHEDULE2_RECORD
|
||||
|
||||
|
||||
# BEGIN SCHEDULE2_DBRECORD
|
||||
class MissingDatabaseError(RuntimeError):
|
||||
"""Raised when a database is required but was not set.""" # <1>
|
||||
|
||||
|
||||
class DbRecord(Record): # <2>
|
||||
|
||||
_db = None # <3>
|
||||
|
||||
@staticmethod # <4>
|
||||
def set_db(db):
|
||||
DbRecord._db = db # <5>
|
||||
|
||||
@staticmethod # <6>
|
||||
def get_db():
|
||||
return DbRecord._db
|
||||
|
||||
@classmethod # <7>
|
||||
def fetch(cls, ident):
|
||||
db = cls.get_db()
|
||||
try:
|
||||
return db[ident] # <8>
|
||||
except TypeError:
|
||||
if db is None: # <9>
|
||||
msg = "database not set; call '{}.set_db(my_db)'"
|
||||
raise MissingDatabaseError(msg.format(cls.__name__))
|
||||
else: # <10>
|
||||
raise
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'):
|
||||
ident = repr(self.name)
|
||||
if hasattr(self, 'serial'): # <11>
|
||||
cls_name = self.__class__.__name__
|
||||
return '<{} serial={!r}>'.format(cls_name, self.serial)
|
||||
else:
|
||||
ident = 'object at ' + hex(id(self))
|
||||
cls_name = self.__class__.__name__
|
||||
return '<{} {}>'.format(cls_name, ident)
|
||||
return super().__repr__() # <12>
|
||||
# END SCHEDULE2_DBRECORD
|
||||
|
||||
|
||||
class DbRecord(Record):
|
||||
|
||||
@classmethod
|
||||
def set_db(cls, db):
|
||||
cls._db = db
|
||||
|
||||
@classmethod
|
||||
def get(cls, ident):
|
||||
return cls._db[ident]
|
||||
|
||||
|
||||
class Event(DbRecord):
|
||||
# BEGIN SCHEDULE2_EVENT
|
||||
class Event(DbRecord): # <1>
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = self.venue_serial
|
||||
return self._db['venue.{}'.format(key)]
|
||||
key = 'venue.{}'.format(self.venue_serial)
|
||||
return self.fetch(key) # <2>
|
||||
|
||||
@property
|
||||
def speakers(self):
|
||||
spkr_serials = self.__dict__['speakers']
|
||||
if not hasattr(self, '_speaker_refs'):
|
||||
self._speaker_refs = [self._db['speaker.{}'.format(key)]
|
||||
for key in spkr_serials]
|
||||
return self._speaker_refs
|
||||
if not hasattr(self, '_speaker_objs'): # <3>
|
||||
spkr_serials = self.__dict__['speakers'] # <4>
|
||||
self._speaker_objs = [self.fetch('speaker.{}'.format(key))
|
||||
for key in spkr_serials] # <5>
|
||||
return self._speaker_objs # <6>
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'): # <7>
|
||||
cls_name = self.__class__.__name__
|
||||
return '<{} {!r}>'.format(cls_name, self.name)
|
||||
else:
|
||||
return super().__repr__() # <8>
|
||||
# END SCHEDULE2_EVENT
|
||||
|
||||
|
||||
# BEGIN SCHEDULE2_LOAD
|
||||
def load_db(db):
|
||||
raw_data = osconfeed.load()
|
||||
warnings.warn('loading ' + DB_NAME)
|
||||
for collection, rec_list in raw_data['Schedule'].items():
|
||||
rec_type = collection[:-1]
|
||||
for fields in rec_list:
|
||||
cls_name = rec_type.capitalize()
|
||||
cls = globals().get(cls_name, Record)
|
||||
if inspect.isclass(cls) and issubclass(cls, Record):
|
||||
record = cls(fields)
|
||||
else:
|
||||
Record(fields)
|
||||
key = '{}.{}'.format(rec_type, fields['serial'])
|
||||
db[key] = record
|
||||
record_type = collection[:-1] # <1>
|
||||
cls_name = record_type.capitalize() # <2>
|
||||
cls = globals().get(cls_name, DbRecord) # <3>
|
||||
if inspect.isclass(cls) and issubclass(cls, DbRecord): # <4>
|
||||
factory = cls # <5>
|
||||
else:
|
||||
factory = DbRecord # <6>
|
||||
for record in rec_list: # <7>
|
||||
key = '{}.{}'.format(record_type, record['serial'])
|
||||
record['serial'] = key
|
||||
db[key] = factory(**record) # <>
|
||||
# END SCHEDULE2_LOAD
|
||||
|
||||
@@ -13,7 +13,7 @@ def db():
|
||||
|
||||
|
||||
def test_record_class():
|
||||
rec = schedule.Record({'spam': 99, 'eggs': 12})
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@ def db():
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record({'spam': 99, 'eggs': 12})
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_record_repr():
|
||||
rec = schedule.Record({'spam': 99, 'eggs': 12})
|
||||
assert repr(rec).startswith('<Record object at 0x')
|
||||
rec2 = schedule.Record({'name': 'Fido'})
|
||||
assert repr(rec2) == "<Record 'Fido'>"
|
||||
rec = schedule.DbRecord(spam=99, eggs=12)
|
||||
assert 'DbRecord object at 0x' in repr(rec)
|
||||
rec2 = schedule.DbRecord(serial=13)
|
||||
assert repr(rec2) == "<DbRecord serial=13>"
|
||||
|
||||
|
||||
def test_conference_record(db):
|
||||
@@ -34,9 +34,14 @@ def test_speaker_record(db):
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_missing_db_exception():
|
||||
with pytest.raises(schedule.MissingDatabaseError):
|
||||
schedule.DbRecord.fetch('venue.1585')
|
||||
|
||||
|
||||
def test_dbrecord(db):
|
||||
schedule.DbRecord.set_db(db)
|
||||
venue = schedule.DbRecord.get('venue.1585')
|
||||
venue = schedule.DbRecord.fetch('venue.1585')
|
||||
assert venue.name == 'Exhibit Hall B'
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user