removed 1st edition code
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 19 - "Dynamic attributes and properties"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
||||
@@ -1,48 +0,0 @@
|
||||
"""
|
||||
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
|
||||
@@ -1,37 +0,0 @@
|
||||
"""
|
||||
A line item for a bulk food order has description, weight and price fields.
|
||||
A ``subtotal`` method gives the total price for that line item::
|
||||
|
||||
>>> raisins = LineItem('Golden raisins', 10, 6.95)
|
||||
>>> raisins.weight, raisins.description, raisins.price
|
||||
(10, 'Golden raisins', 6.95)
|
||||
>>> raisins.subtotal()
|
||||
69.5
|
||||
|
||||
But, without validation, these public attributes can cause trouble::
|
||||
|
||||
# BEGIN LINEITEM_PROBLEM_V1
|
||||
|
||||
>>> raisins = LineItem('Golden raisins', 10, 6.95)
|
||||
>>> raisins.subtotal()
|
||||
69.5
|
||||
>>> raisins.weight = -20 # garbage in...
|
||||
>>> raisins.subtotal() # garbage out...
|
||||
-139.0
|
||||
|
||||
# END LINEITEM_PROBLEM_V1
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# BEGIN LINEITEM_V1
|
||||
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
|
||||
# END LINEITEM_V1
|
||||
@@ -1,63 +0,0 @@
|
||||
"""
|
||||
|
||||
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_V2
|
||||
class LineItem:
|
||||
|
||||
def __init__(self, description, weight, price):
|
||||
self.description = description
|
||||
self.weight = weight # <1>
|
||||
self.price = price
|
||||
|
||||
def subtotal(self):
|
||||
return self.weight * self.price
|
||||
|
||||
@property # <2>
|
||||
def weight(self): # <3>
|
||||
return self.__weight # <4>
|
||||
|
||||
@weight.setter # <5>
|
||||
def weight(self, value):
|
||||
if value > 0:
|
||||
self.__weight = value # <6>
|
||||
else:
|
||||
raise ValueError('value must be > 0') # <7>
|
||||
# END LINEITEM_V2
|
||||
@@ -1,64 +0,0 @@
|
||||
"""
|
||||
|
||||
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
|
||||
@@ -1,69 +0,0 @@
|
||||
"""
|
||||
|
||||
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 properties are stored in
|
||||
instance attributes, created in each ``LineItem`` instance::
|
||||
|
||||
# BEGIN LINEITEM_V2_PROP_DEMO
|
||||
>>> nutmeg = LineItem('Moluccan nutmeg', 8, 13.95)
|
||||
>>> nutmeg.weight, nutmeg.price # <1>
|
||||
(8, 13.95)
|
||||
>>> sorted(vars(nutmeg).items()) # <2>
|
||||
[('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)]
|
||||
|
||||
# END LINEITEM_V2_PROP_DEMO
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# BEGIN LINEITEM_V2_PROP_FACTORY_FUNCTION
|
||||
def quantity(storage_name): # <1>
|
||||
|
||||
def qty_getter(instance): # <2>
|
||||
return instance.__dict__[storage_name] # <3>
|
||||
|
||||
def qty_setter(instance, value): # <4>
|
||||
if value > 0:
|
||||
instance.__dict__[storage_name] = value # <5>
|
||||
else:
|
||||
raise ValueError('value must be > 0')
|
||||
|
||||
return property(qty_getter, qty_setter) # <6>
|
||||
# END LINEITEM_V2_PROP_FACTORY_FUNCTION
|
||||
|
||||
|
||||
# BEGIN LINEITEM_V2_PROP_CLASS
|
||||
class LineItem:
|
||||
weight = quantity('weight') # <1>
|
||||
price = quantity('price') # <2>
|
||||
|
||||
def __init__(self, description, weight, price):
|
||||
self.description = description
|
||||
self.weight = weight # <3>
|
||||
self.price = price
|
||||
|
||||
def subtotal(self):
|
||||
return self.weight * self.price # <4>
|
||||
# END LINEITEM_V2_PROP_CLASS
|
||||
@@ -1,23 +0,0 @@
|
||||
"""
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
import shelve
|
||||
|
||||
from schedule2 import DB_NAME, CONFERENCE, load_db
|
||||
from schedule2 import DbRecord, Event
|
||||
|
||||
with shelve.open(DB_NAME) as db:
|
||||
if CONFERENCE not in db:
|
||||
load_db(db)
|
||||
|
||||
DbRecord.set_db(db)
|
||||
event = DbRecord.fetch('event.33950')
|
||||
print(event)
|
||||
print(event.venue)
|
||||
print(event.venue.name)
|
||||
for spkr in event.speakers:
|
||||
print('{0.serial}: {0.name}'.format(spkr))
|
||||
|
||||
print(repr(Event.venue))
|
||||
|
||||
event2 = DbRecord.fetch('event.33451')
|
||||
print(event2)
|
||||
print(event2.fetch)
|
||||
print(event2.venue)
|
||||
@@ -1,63 +0,0 @@
|
||||
"""
|
||||
explore0.py: Script to explore the OSCON schedule feed
|
||||
|
||||
# BEGIN EXPLORE0_DEMO
|
||||
>>> from osconfeed import load
|
||||
>>> raw_feed = load()
|
||||
>>> 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()): # <4>
|
||||
... print('{:3} {}'.format(len(value), key))
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed.Schedule.speakers[-1].name # <5>
|
||||
'Carina C. Zona'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> type(talk) # <6>
|
||||
<class 'explore0.FrozenJSON'>
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers # <7>
|
||||
[3471, 5199]
|
||||
>>> talk.flavor # <8>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
# END EXPLORE0_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN EXPLORE0
|
||||
from collections import abc
|
||||
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.__data = dict(mapping) # <1>
|
||||
|
||||
def __getattr__(self, name): # <2>
|
||||
if hasattr(self.__data, name):
|
||||
return getattr(self.__data, name) # <3>
|
||||
else:
|
||||
return FrozenJSON.build(self.__data[name]) # <4>
|
||||
|
||||
@classmethod
|
||||
def build(cls, obj): # <5>
|
||||
if isinstance(obj, abc.Mapping): # <6>
|
||||
return cls(obj)
|
||||
elif isinstance(obj, abc.MutableSequence): # <7>
|
||||
return [cls.build(item) for item in obj]
|
||||
else: # <8>
|
||||
return obj
|
||||
# END EXPLORE0
|
||||
@@ -1,78 +0,0 @@
|
||||
"""
|
||||
explore1.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'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> type(talk)
|
||||
<class 'explore1.FrozenJSON'>
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers
|
||||
[3471, 5199]
|
||||
>>> talk.flavor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
Handle keywords by appending a `_`.
|
||||
|
||||
# BEGIN EXPLORE1_DEMO
|
||||
|
||||
>>> grad = FrozenJSON({'name': 'Jim Bo', 'class': 1982})
|
||||
>>> grad.name
|
||||
'Jim Bo'
|
||||
>>> grad.class_
|
||||
1991
|
||||
|
||||
# END EXPLORE1_DEMO
|
||||
|
||||
"""
|
||||
|
||||
from collections import abc
|
||||
import keyword
|
||||
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
# BEGIN EXPLORE1
|
||||
def __init__(self, mapping):
|
||||
self.__data = {}
|
||||
for key, value in mapping.items():
|
||||
if keyword.iskeyword(key): # <1>
|
||||
key += '_'
|
||||
self.__data[key] = value
|
||||
# END EXPLORE1
|
||||
|
||||
def __getattr__(self, name):
|
||||
if hasattr(self.__data, name):
|
||||
return getattr(self.__data, name)
|
||||
else:
|
||||
return FrozenJSON.build(self.__data[name])
|
||||
|
||||
@classmethod
|
||||
def build(cls, obj):
|
||||
if isinstance(obj, abc.Mapping):
|
||||
return cls(obj)
|
||||
elif isinstance(obj, abc.MutableSequence):
|
||||
return [cls.build(item) for item in obj]
|
||||
else: # <8>
|
||||
return obj
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
"""
|
||||
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']
|
||||
>>> feed.Schedule.speakers[-1].name
|
||||
'Carina C. Zona'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers
|
||||
[3471, 5199]
|
||||
>>> talk.flavor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN EXPLORE2
|
||||
from collections import abc
|
||||
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
def __new__(cls, arg): # <1>
|
||||
if isinstance(arg, abc.Mapping):
|
||||
return super().__new__(cls) # <2>
|
||||
elif isinstance(arg, abc.MutableSequence): # <3>
|
||||
return [cls(item) for item in arg]
|
||||
else:
|
||||
return arg
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.__data = {}
|
||||
for key, value in mapping.items():
|
||||
if iskeyword(key):
|
||||
key += '_'
|
||||
self.__data[key] = value
|
||||
|
||||
def __getattr__(self, name):
|
||||
if hasattr(self.__data, name):
|
||||
return getattr(self.__data, name)
|
||||
else:
|
||||
return FrozenJSON(self.__data[name]) # <4>
|
||||
# END EXPLORE2
|
||||
@@ -1,32 +0,0 @@
|
||||
{ "Schedule":
|
||||
{ "conferences": [{"serial": 115 }],
|
||||
"events": [
|
||||
{ "serial": 34505,
|
||||
"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...",
|
||||
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
|
||||
"speakers": [157509],
|
||||
"categories": ["Education"] }
|
||||
],
|
||||
"speakers": [
|
||||
{ "serial": 157509,
|
||||
"name": "Robert Lefkowitz",
|
||||
"photo": null,
|
||||
"url": "http://sharewave.com/",
|
||||
"position": "CTO",
|
||||
"affiliation": "Sharewave",
|
||||
"twitter": "sharewaveteam",
|
||||
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." }
|
||||
],
|
||||
"venues": [
|
||||
{ "serial": 1462,
|
||||
"name": "F151",
|
||||
"category": "Conference Venues" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
"""
|
||||
osconfeed.py: Script to download the OSCON schedule feed
|
||||
|
||||
# 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)) # <3>
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed['Schedule']['speakers'][-1]['name'] # <4>
|
||||
'Carina C. Zona'
|
||||
>>> feed['Schedule']['speakers'][-1]['serial'] # <5>
|
||||
141590
|
||||
>>> feed['Schedule']['events'][40]['name']
|
||||
'There *Will* Be Bugs'
|
||||
>>> 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 = 'data/osconfeed.json'
|
||||
|
||||
|
||||
def load():
|
||||
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) as fp:
|
||||
return json.load(fp) # <3>
|
||||
|
||||
# END OSCONFEED
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
schedule1.py: traversing OSCON schedule data
|
||||
|
||||
# BEGIN SCHEDULE1_DEMO
|
||||
>>> import shelve
|
||||
>>> 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, speaker.twitter # <6>
|
||||
('Anna Martelli Ravenscroft', 'annaraven')
|
||||
>>> db.close() # <7>
|
||||
|
||||
# END SCHEDULE1_DEMO
|
||||
|
||||
"""
|
||||
|
||||
# BEGIN SCHEDULE1
|
||||
import warnings
|
||||
|
||||
import osconfeed # <1>
|
||||
|
||||
DB_NAME = 'data/schedule1_db'
|
||||
CONFERENCE = 'conference.115'
|
||||
|
||||
|
||||
class Record:
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs) # <2>
|
||||
|
||||
|
||||
def load_db(db):
|
||||
raw_data = osconfeed.load() # <3>
|
||||
warnings.warn('loading ' + DB_NAME)
|
||||
for collection, rec_list in raw_data['Schedule'].items(): # <4>
|
||||
record_type = collection[:-1] # <5>
|
||||
for record in rec_list:
|
||||
key = '{}.{}'.format(record_type, record['serial']) # <6>
|
||||
record['serial'] = key # <7>
|
||||
db[key] = Record(**record) # <8>
|
||||
|
||||
# END SCHEDULE1
|
||||
@@ -1,132 +0,0 @@
|
||||
"""
|
||||
schedule2.py: traversing OSCON schedule data
|
||||
|
||||
>>> import shelve
|
||||
>>> db = shelve.open(DB_NAME)
|
||||
>>> if CONFERENCE not in db: load_db(db)
|
||||
|
||||
# BEGIN SCHEDULE2_DEMO
|
||||
|
||||
>>> DbRecord.set_db(db) # <1>
|
||||
>>> event = DbRecord.fetch('event.33950') # <2>
|
||||
>>> event # <3>
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> 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 # <1>
|
||||
|
||||
import osconfeed
|
||||
|
||||
DB_NAME = 'data/schedule2_db' # <2>
|
||||
CONFERENCE = 'conference.115'
|
||||
|
||||
|
||||
class Record:
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
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, 'serial'): # <11>
|
||||
cls_name = self.__class__.__name__
|
||||
return '<{} serial={!r}>'.format(cls_name, self.serial)
|
||||
else:
|
||||
return super().__repr__() # <12>
|
||||
# END SCHEDULE2_DBRECORD
|
||||
|
||||
|
||||
# BEGIN SCHEDULE2_EVENT
|
||||
class Event(DbRecord): # <1>
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = 'venue.{}'.format(self.venue_serial)
|
||||
return self.__class__.fetch(key) # <2>
|
||||
|
||||
@property
|
||||
def speakers(self):
|
||||
if not hasattr(self, '_speaker_objs'): # <3>
|
||||
spkr_serials = self.__dict__['speakers'] # <4>
|
||||
fetch = self.__class__.fetch # <5>
|
||||
self._speaker_objs = [fetch('speaker.{}'.format(key))
|
||||
for key in spkr_serials] # <6>
|
||||
return self._speaker_objs # <7>
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'): # <8>
|
||||
cls_name = self.__class__.__name__
|
||||
return '<{} {!r}>'.format(cls_name, self.name)
|
||||
else:
|
||||
return super().__repr__() # <9>
|
||||
# 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():
|
||||
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) # <8>
|
||||
# END SCHEDULE2_LOAD
|
||||
@@ -1,37 +0,0 @@
|
||||
import shelve
|
||||
import pytest
|
||||
|
||||
import schedule1 as schedule
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
def db():
|
||||
with shelve.open(schedule.DB_NAME) as the_db:
|
||||
if schedule.CONFERENCE not in the_db:
|
||||
schedule.load_db(the_db)
|
||||
yield the_db
|
||||
|
||||
|
||||
def test_record_class():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_conference_record(db):
|
||||
assert schedule.CONFERENCE in db
|
||||
|
||||
|
||||
def test_speaker_record(db):
|
||||
speaker = db['speaker.3471']
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_event_record(db):
|
||||
event = db['event.33950']
|
||||
assert event.name == 'There *Will* Be Bugs'
|
||||
|
||||
|
||||
def test_event_venue(db):
|
||||
event = db['event.33950']
|
||||
assert event.venue_serial == 1449
|
||||
@@ -1,72 +0,0 @@
|
||||
import shelve
|
||||
import pytest
|
||||
|
||||
import schedule2 as schedule
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
def db():
|
||||
with shelve.open(schedule.DB_NAME) as the_db:
|
||||
if schedule.CONFERENCE not in the_db:
|
||||
schedule.load_db(the_db)
|
||||
yield the_db
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_record_repr():
|
||||
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):
|
||||
assert schedule.CONFERENCE in db
|
||||
|
||||
|
||||
def test_speaker_record(db):
|
||||
speaker = db['speaker.3471']
|
||||
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.fetch('venue.1585')
|
||||
assert venue.name == 'Exhibit Hall B'
|
||||
|
||||
|
||||
def test_event_record(db):
|
||||
event = db['event.33950']
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
|
||||
|
||||
def test_event_venue(db):
|
||||
schedule.Event.set_db(db)
|
||||
event = db['event.33950']
|
||||
assert event.venue_serial == 1449
|
||||
assert event.venue == db['venue.1449']
|
||||
assert event.venue.name == 'Portland 251'
|
||||
|
||||
|
||||
def test_event_speakers(db):
|
||||
schedule.Event.set_db(db)
|
||||
event = db['event.33950']
|
||||
assert len(event.speakers) == 2
|
||||
anna_and_alex = [db['speaker.3471'], db['speaker.5199']]
|
||||
assert event.speakers == anna_and_alex
|
||||
|
||||
|
||||
def test_event_no_speakers(db):
|
||||
schedule.Event.set_db(db)
|
||||
event = db['event.36848']
|
||||
assert len(event.speakers) == 0
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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')
|
||||
Reference in New Issue
Block a user