update from Atlas

This commit is contained in:
Luciano Ramalho 2015-01-05 03:20:08 -02:00
parent 1edb0cd05b
commit 0618105a47
18 changed files with 822 additions and 123 deletions

View File

@ -0,0 +1,64 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The check is also performed on instantiation::
>>> walnuts = LineItem('walnuts', 0, 10.00)
Traceback (most recent call last):
...
ValueError: value must be > 0
The proteced attribute can still be accessed if needed for some reason, such as
white box testing)::
>>> raisins._LineItem__weight
10
"""
# BEGIN LINEITEM_V2B
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
def get_weight(self): # <1>
return self.__weight
def set_weight(self, value): # <2>
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')
weight = property(get_weight, set_weight) # <3>
# END LINEITEM_V2B

View File

@ -0,0 +1,58 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
"""
# BEGIN LINEITEM_V2_PROP
def quantity(storage_name): # <1>
@property # <2>
def new_prop(self):
return self.__dict__[storage_name] # <3>
@new_prop.setter
def new_prop(self, value):
if value > 0:
self.__dict__[storage_name] = value # <4>
else:
raise ValueError('value must be > 0')
return new_prop # <5>
class LineItem:
weight = quantity('weight') # <6>
price = quantity('price') # <7>
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V2_PROP

View File

@ -0,0 +1,76 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> sorted(dir(raisins)) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
[..., '_quantity_0', '_quantity_1', 'description',
'price', 'subtotal', 'weight']
>>> raisins._quantity_0
10
>>> raisins._quantity_1
6.95
"""
# BEGIN LINEITEM_V4_PROP
def quantity(): # <1>
try:
quantity.counter += 1 # <2>
except AttributeError:
quantity.counter = 0 # <3>
storage_name = '_{}_{}'.format('quantity', quantity.counter) # <4>
@property # <5>
def prop(self):
return getattr(self, storage_name)
@prop.setter
def prop(self, value):
if value > 0:
setattr(self, storage_name, value)
else:
raise ValueError('value must be > 0')
return prop # <6>
class LineItem:
weight = quantity() # <7>
price = quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V4_PROP

View File

@ -0,0 +1,75 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
['_NonBlank_0', '_Quantity_0', '_Quantity_1', '__class__', ...
'description', 'price', 'subtotal', 'weight']
>>> raisins._Quantity_0
10
>>> raisins._Quantity_1
6.95
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.price # doctest: +ELLIPSIS
<model_v5.Quantity object at 0x...>
>>> br_nuts = LineItem('Brazil nuts', 10, 34.95)
>>> br_nuts.price
34.95
The `NonBlank` descriptor prevents empty or blank strings to be used
for the description:
>>> br_nuts.description = ' '
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
"""
# BEGIN LINEITEM_V5
import model_v5 as model # <1>
class LineItem:
description = model.NonBlank() # <2>
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V5

View File

@ -0,0 +1,166 @@
"""
Data descriptor (a.k.a. overriding or enforced descriptor):
>>> o = Model()
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
>>> Model.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = None
owner = <class 'descriptorkinds.Model'>
A data descriptor cannot be shadowed by assigning to an instance:
>>> o.data = 7 # doctest: +ELLIPSIS
DataDescriptor.__set__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
Not even by poking the attribute into the instance ``__dict__``:
>>> o.__dict__['data'] = 8
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
Data descriptor without ``__get__``:
>>> o.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
>>> Model.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
>>> o.data_no_get = 7 # doctest: +ELLIPSIS
DataDescriptorNoGet.__set__() invoked with args:
self = <descriptorkinds.DataDescriptorNoGet object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
Poking the attribute into the instance ``__dict__`` means you can read the new
value for the attribute, but setting it still triggers ``__set__``:
>>> o.__dict__['data_no_get'] = 8
>>> o.data_no_get
8
>>> o.data_no_get = 7 # doctest: +ELLIPSIS
DataDescriptorNoGet.__set__() invoked with args:
self = <descriptorkinds.DataDescriptorNoGet object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data_no_get # doctest: +ELLIPSIS
8
Non-data descriptor (a.k.a. non-overriding or shadowable descriptor):
>>> o = Model()
>>> o.non_data # doctest: +ELLIPSIS
NonDataDescriptor.__get__() invoked with args:
self = <descriptorkinds.NonDataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
>>> Model.non_data # doctest: +ELLIPSIS
NonDataDescriptor.__get__() invoked with args:
self = <descriptorkinds.NonDataDescriptor object at 0x...>
instance = None
owner = <class 'descriptorkinds.Model'>
A non-data descriptor can be shadowed by assigning to an instance:
>>> o.non_data = 7
>>> o.non_data
7
Methods are non-data descriptors:
>>> o.spam # doctest: +ELLIPSIS
<bound method Model.spam of <descriptorkinds.Model object at 0x...>>
>>> Model.spam # doctest: +ELLIPSIS
<function Model.spam at 0x...>
>>> o.spam() # doctest: +ELLIPSIS
Model.spam() invoked with arg:
self = <descriptorkinds.Model object at 0x...>
>>> o.spam = 7
>>> o.spam
7
No descriptor type survives being overwritten on the class itself:
>>> Model.data = 1
>>> o.data
1
>>> Model.data_no_get = 2
>>> o.data_no_get
2
>>> Model.non_data = 3
>>> o.non_data
7
"""
class DataDescriptor:
"a.k.a. overriding or enforced descriptor"
def __get__(self, instance, owner):
print('DataDescriptor.__get__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' owner = ', owner)
def __set__(self, instance, value):
print('DataDescriptor.__set__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' value = ', value)
class DataDescriptorNoGet:
def __set__(self, instance, value):
print('DataDescriptorNoGet.__set__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' value = ', value)
class NonDataDescriptor:
"a.k.a. non-overriding or shadowable descriptor"
def __get__(self, instance, owner):
print('NonDataDescriptor.__get__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' owner = ', owner)
class Model:
data = DataDescriptor()
data_no_get = DataDescriptorNoGet()
non_data = NonDataDescriptor()
def spam(self):
print('Model.spam() invoked with arg:')
print(' self = ', self)

View File

@ -0,0 +1,60 @@
"""
>>> f = Foo()
>>> f.bar = 77
>>> f.bar
77
>>> Foo.bar.__doc__
'The "bar" attribute'
>>> import pydoc
>>> pydoc.getdoc(Foo.bazz)
'The "bazz" attribute'
"""
def doc_descriptor_wrapper_factory(descriptor):
wrapper_cls_name = 'DocDescriptorWrapper'
wrapper_cls_attrs = descriptor.__dict__.copy()
wrapper_cls_attrs['__slots__'] = ['_wrapped']
def wrapped_getter(self):
"the wrapped descriptor instance"
return self._wrapped
def wrapper_repr(self):
return '<{} {!r}>'.format(wrapper_cls_name, self.__doc__)
wrapper_cls_attrs['wrapped'] = property(wrapped_getter)
wrapper_cls_attrs['__repr__'] = wrapper_repr
wrapper_cls = type(wrapper_cls_name, (), wrapper_cls_attrs)
wrapper = wrapper_cls()
wrapper._wrapped = descriptor
return wrapper
class DocDescriptor:
"""A documented descriptor"""
def __init__(self, documentation):
self.__doc__ = documentation
cls_name = self.__class__.__name__
self.storage_name = '_{}_{:x}'.format(cls_name, id(self))
def __get__(self, instance, owner):
"""The __get__ method"""
if instance is None:
return doc_descriptor_wrapper_factory(self)
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
class Foo:
"""The "Foo" class"""
bar = DocDescriptor('The "bar" attribute')
bazz = DocDescriptor('The "bazz" attribute')

52
descriptors/model_v5.py Normal file
View File

@ -0,0 +1,52 @@
# BEGIN MODEL_V5
import abc
class AutoStorage: # <1>
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}_{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value) # <2>
class Validated(abc.ABC, AutoStorage): # <3>
def __set__(self, instance, value):
value = self.validate(instance, value) # <4>
super().__set__(instance, value) # <5>
@abc.abstractmethod
def validate(self, instance, value): # <6>
"""return validated value or raise ValueError"""
class Quantity(Validated): # <7>
def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value
class NonBlank(Validated):
def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value # <8>
# END MODEL_V5

48
metaprog/blackknight.py Normal file
View File

@ -0,0 +1,48 @@
"""
This class is inspired by the Black Knight scene in the movie
"Monty Python and the Holy Grail", where King Arthur fights the
Black Knight, slicing off his arms and legs, but the knight
refuses to concede defeat.
# BEGIN BLACK_KNIGHT_DEMO
>>> knight = BlackKnight()
>>> knight.member
next member is:
'an arm'
>>> del knight.member
BLACK KNIGHT (loses an arm)
-- 'Tis but a scratch.
>>> del knight.member
BLACK KNIGHT (loses another arm)
-- It's just a flesh wound.
>>> del knight.member
BLACK KNIGHT (loses a leg)
-- I'm invincible!
>>> del knight.member
BLACK KNIGHT (loses another leg)
-- All right, we'll call it a draw.
# END BLACK_KNIGHT_DEMO
"""
# BEGIN BLACK_KNIGHT
class BlackKnight:
def __init__(self):
self.members = ['an arm', 'another arm',
'a leg', 'another leg']
self.phrases = ["'Tis but a scratch.",
"It's just a flesh wound.",
"I'm invincible!",
"All right, we'll call it a draw."]
@property
def member(self):
print('next member is:')
return self.members[0]
@member.deleter
def member(self):
text = 'BLACK KNIGHT (loses {})\n-- {}'
print(text.format(self.members.pop(0), self.phrases.pop(0)))
# END BLACK_KNIGHT

23
metaprog/doc_property.py Normal file
View File

@ -0,0 +1,23 @@
"""
Example of property documentation
>>> f = Foo()
>>> f.bar = 77
>>> f.bar
77
>>> Foo.bar.__doc__
'The bar attribute'
"""
# BEGIN DOC_PROPERTY
class Foo:
@property
def bar(self):
'''The bar attribute'''
return self.__dict__['bar']
@bar.setter
def bar(self, value):
self.__dict__['bar'] = value
# END DOC_PROPERTY

View File

@ -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

View File

@ -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

View File

@ -2,12 +2,12 @@
{ "conferences": [{"serial": 115 }],
"events": [
{ "serial": 34505,
"name": "Why Schools Don&#x27;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&#x27;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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -0,0 +1,10 @@
# pseudo-code for object construction
def object_maker(the_class, some_arg):
new_object = the_class.__new__(some_arg)
if isinstance(new_object, the_class):
the_class.__init__(new_object, some_arg)
return new_object
# the following statements are roughly equivalent
x = Foo('bar')
x = object_maker(Foo, 'bar')